diff --git a/avd_docs/aws/apigateway/AVD-AWS-0001/docs.md b/avd_docs/aws/apigateway/AVD-AWS-0001/docs.md index 77db11ce..171783d1 100644 --- a/avd_docs/aws/apigateway/AVD-AWS-0001/docs.md +++ b/avd_docs/aws/apigateway/AVD-AWS-0001/docs.md @@ -1,8 +1,9 @@ API Gateway stages should have access log settings block configured to track all access to a particular stage. This should be applied to both v1 and v2 gateway stages. + ### Impact -Logging provides vital information about access and usage + {{ remediationActions }} diff --git a/avd_docs/aws/apigateway/AVD-AWS-0002/docs.md b/avd_docs/aws/apigateway/AVD-AWS-0002/docs.md index 0aa2613d..22b3f5de 100644 --- a/avd_docs/aws/apigateway/AVD-AWS-0002/docs.md +++ b/avd_docs/aws/apigateway/AVD-AWS-0002/docs.md @@ -1,8 +1,9 @@ Method cache encryption ensures that any sensitive data in the cache is not vulnerable to compromise in the event of interception + ### Impact -Data stored in the cache that is unencrypted may be vulnerable to compromise + {{ remediationActions }} diff --git a/avd_docs/aws/apigateway/AVD-AWS-0003/docs.md b/avd_docs/aws/apigateway/AVD-AWS-0003/docs.md index 6054dbd2..1e3e7338 100644 --- a/avd_docs/aws/apigateway/AVD-AWS-0003/docs.md +++ b/avd_docs/aws/apigateway/AVD-AWS-0003/docs.md @@ -1,8 +1,9 @@ X-Ray tracing enables end-to-end debugging and analysis of all API Gateway HTTP requests. + ### Impact -Without full tracing enabled it is difficult to trace the flow of logs + {{ remediationActions }} diff --git a/avd_docs/aws/apigateway/AVD-AWS-0004/docs.md b/avd_docs/aws/apigateway/AVD-AWS-0004/docs.md index fd42760b..514e1c5d 100644 --- a/avd_docs/aws/apigateway/AVD-AWS-0004/docs.md +++ b/avd_docs/aws/apigateway/AVD-AWS-0004/docs.md @@ -1,8 +1,9 @@ API Gateway methods should generally be protected by authorization or api key. OPTION verb calls can be used without authorization + ### Impact -API gateway methods can be accessed without authorization. + {{ remediationActions }} diff --git a/avd_docs/aws/apigateway/AVD-AWS-0005/docs.md b/avd_docs/aws/apigateway/AVD-AWS-0005/docs.md index 6a83ec58..7b18f5bf 100644 --- a/avd_docs/aws/apigateway/AVD-AWS-0005/docs.md +++ b/avd_docs/aws/apigateway/AVD-AWS-0005/docs.md @@ -1,8 +1,9 @@ You should not use outdated/insecure TLS versions for encryption. You should be using TLS v1.2+. + ### Impact -Outdated SSL policies increase exposure to known vulnerabilities + {{ remediationActions }} diff --git a/avd_docs/aws/apigateway/AVD-AWS-0190/docs.md b/avd_docs/aws/apigateway/AVD-AWS-0190/docs.md index 77e73ce3..fc5741a3 100644 --- a/avd_docs/aws/apigateway/AVD-AWS-0190/docs.md +++ b/avd_docs/aws/apigateway/AVD-AWS-0190/docs.md @@ -1,8 +1,9 @@ A REST API in API Gateway is a collection of resources and methods that are integrated with backend HTTP endpoints, Lambda functions, or other AWS services. You can enable API caching in Amazon API Gateway to cache your endpoint responses. With caching, you can reduce the number of calls made to your endpoint and also improve the latency of requests to your API. + ### Impact -Reduce the number of calls made to your API endpoint and also improve the latency of requests to your API with response caching. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudfront/AVD-AWS-0010/docs.md b/avd_docs/aws/cloudfront/AVD-AWS-0010/docs.md index a9ee8969..75b380c0 100644 --- a/avd_docs/aws/cloudfront/AVD-AWS-0010/docs.md +++ b/avd_docs/aws/cloudfront/AVD-AWS-0010/docs.md @@ -1,8 +1,9 @@ You should configure CloudFront Access Logging to create log files that contain detailed information about every user request that CloudFront receives + ### Impact -Logging provides vital information about access and usage + {{ remediationActions }} diff --git a/avd_docs/aws/cloudfront/AVD-AWS-0011/docs.md b/avd_docs/aws/cloudfront/AVD-AWS-0011/docs.md index 0cc85bdf..ecb70288 100644 --- a/avd_docs/aws/cloudfront/AVD-AWS-0011/docs.md +++ b/avd_docs/aws/cloudfront/AVD-AWS-0011/docs.md @@ -1,8 +1,9 @@ You should configure a Web Application Firewall in front of your CloudFront distribution. This will mitigate many types of attacks on your web application. + ### Impact -Complex web application attacks can more easily be performed without a WAF + {{ remediationActions }} diff --git a/avd_docs/aws/cloudfront/AVD-AWS-0012/docs.md b/avd_docs/aws/cloudfront/AVD-AWS-0012/docs.md index f6d42224..18ad0282 100644 --- a/avd_docs/aws/cloudfront/AVD-AWS-0012/docs.md +++ b/avd_docs/aws/cloudfront/AVD-AWS-0012/docs.md @@ -1,10 +1,10 @@ Plain HTTP is unencrypted and human-readable. This means that if a malicious actor was to eavesdrop on your connection, they would be able to see all of your data flowing back and forth. - You should use HTTPS, which is HTTP over an encrypted (TLS) connection, meaning eavesdroppers cannot read your traffic. + ### Impact -CloudFront is available through an unencrypted connection + {{ remediationActions }} diff --git a/avd_docs/aws/cloudfront/AVD-AWS-0013/docs.md b/avd_docs/aws/cloudfront/AVD-AWS-0013/docs.md index 544f9975..e6cd3c60 100644 --- a/avd_docs/aws/cloudfront/AVD-AWS-0013/docs.md +++ b/avd_docs/aws/cloudfront/AVD-AWS-0013/docs.md @@ -1,12 +1,12 @@ You should not use outdated/insecure TLS versions for encryption. You should be using TLS v1.2+. - -Note: that setting *minimum_protocol_version = "TLSv1.2_2021"* is only possible when *cloudfront_default_certificate* is false (eg. you are not using the cloudfront.net domain name). -If *cloudfront_default_certificate* is true then the Cloudfront API will only allow setting *minimum_protocol_version = "TLSv1"*, and setting it to any other value will result in a perpetual diff in your *terraform plan*'s. +Note: that setting *minimum_protocol_version = "TLSv1.2_2021"* is only possible when *cloudfront_default_certificate* is false (eg. you are not using the cloudfront.net domain name). +If *cloudfront_default_certificate* is true then the Cloudfront API will only allow setting *minimum_protocol_version = "TLSv1"*, and setting it to any other value will result in a perpetual diff in your *terraform plan*'s. The only option when using the cloudfront.net domain name is to ignore this rule. + ### Impact -Outdated SSL policies increase exposure to known vulnerabilities + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0017/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0017/docs.md index 6608c428..73d6d3db 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0017/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0017/docs.md @@ -1,8 +1,9 @@ CloudWatch log groups are encrypted by default, however, to get the full benefit of controlling key rotation and other KMS aspects a KMS CMK should be used. + ### Impact -Log data may be leaked if the logs are compromised. No auditing of who have viewed the logs. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0147/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0147/docs.md index 7548c990..84defafa 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0147/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0147/docs.md @@ -1,10 +1,10 @@ You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. You can have more than one VPC in an account, and you can create a peer connection between two VPCs, enabling network traffic to route between VPCs. - CIS recommends that you create a metric filter and alarm for changes to VPCs. Monitoring these changes helps ensure that authentication and authorization controls remain intact. + ### Impact -Unauthorized API Calls may be attempted without being notified. CloudTrail logs these actions but without the alarm you aren't actively notified. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0148/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0148/docs.md index 4cf5214a..e5879c27 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0148/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0148/docs.md @@ -1,10 +1,10 @@ -You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. - - CIS recommends that you create a metric filter and alarm console logins that aren't protected by MFA. Monitoring for single-factor console logins increases visibility into accounts that aren't protected by MFA. +You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +CIS recommends that you create a metric filter and alarm console logins that aren't protected by MFA. Monitoring for single-factor console logins increases visibility into accounts that aren't protected by MFA. + ### Impact -Not alerting on logins with no MFA allows the risk to go un-notified. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0149/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0149/docs.md index ff5cd988..9a389164 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0149/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0149/docs.md @@ -1,10 +1,10 @@ - You can do real-time monitoring of API calls directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. - +You can do real-time monitoring of API calls directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. CIS recommends that you create a metric filter and alarm for root user login attempts. Monitoring for root user logins provides visibility into the use of a fully privileged account and an opportunity to reduce the use of it. + ### Impact -The root user has significant permissions and should not be used for day to day tasks. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0150/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0150/docs.md index d23d750a..cd878bd2 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0150/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0150/docs.md @@ -1,10 +1,10 @@ - You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. - +You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. CIS recommends that you create a metric filter and alarm for changes made to IAM policies. Monitoring these changes helps ensure that authentication and authorization controls remain intact. + ### Impact -IAM Policy changes could lead to excessive permissions and may have been performed maliciously. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0151/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0151/docs.md index 9e09ee7d..dadac84b 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0151/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0151/docs.md @@ -1,10 +1,10 @@ -You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. - +You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. CIS recommends that you create a metric filter and alarm for changes to CloudTrail configuration settings. Monitoring these changes helps ensure sustained visibility to activities in the account. + ### Impact -CloudTrail tracks all changes through the API, attempts to change the configuration may indicate malicious activity. Without alerting on changes, visibility of this activity is reduced. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0152/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0152/docs.md index 6def5faa..7a842320 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0152/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0152/docs.md @@ -1,10 +1,10 @@ -You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. - +You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. CIS recommends that you create a metric filter and alarm for failed console authentication attempts. Monitoring failed console logins might decrease lead time to detect an attempt to brute-force a credential, which might provide an indicator, such as source IP, that you can use in other event correlations. + ### Impact -Failed attempts to log into the Management console may indicate an attempt to maliciously access an account. Failure to alert reduces visibility of this activity. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0153/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0153/docs.md index 99b92869..27cbff00 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0153/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0153/docs.md @@ -1,10 +1,10 @@ -You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. - - CIS recommends that you create a metric filter and alarm for customer managed keys that have changed state to disabled or scheduled deletion. Data encrypted with disabled or deleted keys is no longer accessible. +You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +CIS recommends that you create a metric filter and alarm for customer managed keys that have changed state to disabled or scheduled deletion. Data encrypted with disabled or deleted keys is no longer accessible. + ### Impact -CloudTrail tracks all changes through the API, attempts to change the configuration may indicate malicious activity. Without alerting on changes, visibility of this activity is reduced. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0154/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0154/docs.md index 99c38017..3e3edc69 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0154/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0154/docs.md @@ -1,10 +1,10 @@ -You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. - +You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. CIS recommends that you create a metric filter and alarm for changes to S3 bucket policies. Monitoring these changes might reduce time to detect and correct permissive policies on sensitive S3 buckets. + ### Impact -Misconfigured policies on S3 buckets could lead to data leakage, without alerting visibility of this is reduced. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0155/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0155/docs.md index 6d66e639..03126440 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0155/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0155/docs.md @@ -1,10 +1,10 @@ -You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. - +You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. CIS recommends that you create a metric filter and alarm for changes to AWS Config configuration settings. Monitoring these changes helps ensure sustained visibility of configuration items in the account. + ### Impact -Changes to the configuration of AWS Config may indicate malicious activity. Without alerting on changes, visibility of this activity is reduced. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0156/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0156/docs.md index 920219f2..ca3c2512 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0156/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0156/docs.md @@ -1,11 +1,11 @@ -You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. -Security groups are a stateful packet filter that controls ingress and egress traffic in a VPC. - +You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +Security groups are a stateful packet filter that controls ingress and egress traffic in a VPC. CIS recommends that you create a metric filter and alarm for changes to security groups. Monitoring these changes helps ensure that resources and services aren't unintentionally exposed. + ### Impact -Security groups control the ingress and egress, changes could be made to maliciously allow egress of data or external ingress. Without alerting, this could go unnoticed. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0157/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0157/docs.md index dc46db62..8ba5ced4 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0157/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0157/docs.md @@ -1,11 +1,11 @@ -You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. -NACLs are used as a stateless packet filter to control ingress and egress traffic for subnets in a VPC. - +You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +NACLs are used as a stateless packet filter to control ingress and egress traffic for subnets in a VPC. CIS recommends that you create a metric filter and alarm for changes to NACLs. Monitoring these changes helps ensure that AWS resources and services aren't unintentionally exposed. + ### Impact -Network ACLs control the ingress and egress, changes could be made to maliciously allow egress of data or external ingress. Without alerting, this could go unnoticed. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0158/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0158/docs.md index 1d7efd6a..72fa3ee1 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0158/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0158/docs.md @@ -1,11 +1,11 @@ -You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. -Network gateways are required to send and receive traffic to a destination outside a VPC. - +You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +Network gateways are required to send and receive traffic to a destination outside a VPC. CIS recommends that you create a metric filter and alarm for changes to network gateways. Monitoring these changes helps ensure that all ingress and egress traffic traverses the VPC border via a controlled path. + ### Impact -Network gateways control the ingress and egress, changes could be made to maliciously allow egress of data or external ingress. Without alerting, this could go unnoticed. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0159/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0159/docs.md index 0f0b72a4..7b628ef1 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0159/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0159/docs.md @@ -1,11 +1,11 @@ -You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. -Routing tables route network traffic between subnets and to network gateways. - +You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +Routing tables route network traffic between subnets and to network gateways. CIS recommends that you create a metric filter and alarm for changes to route tables. Monitoring these changes helps ensure that all VPC traffic flows through an expected path. + ### Impact -Route tables control the flow of network traffic, changes could be made to maliciously allow egress of data or external ingress. Without alerting, this could go unnoticed. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0160/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0160/docs.md index cfa5a41e..d9ce137b 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0160/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0160/docs.md @@ -1,11 +1,11 @@ -You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. You can have more than one VPC in an account, and you can create a peer connection between two VPCs, enabling network traffic to route between VPCs. - -CIS recommends that you create a metric filter and alarm for changes to VPCs. Monitoring these changes helps ensure that authentication and authorization controls remain intact. +CIS recommends that you create a metric filter and alarm for changes to VPCs. Monitoring these changes helps ensure that authentication and authorization controls remain intact. + ### Impact -Route tables control the flow of network traffic, changes could be made to maliciously allow egress of data or external ingress. Without alerting, this could go unnoticed. + {{ remediationActions }} diff --git a/avd_docs/aws/cloudwatch/AVD-AWS-0174/docs.md b/avd_docs/aws/cloudwatch/AVD-AWS-0174/docs.md index 7a298f54..787d430a 100644 --- a/avd_docs/aws/cloudwatch/AVD-AWS-0174/docs.md +++ b/avd_docs/aws/cloudwatch/AVD-AWS-0174/docs.md @@ -1,5 +1,4 @@ - Monitoring AWS Organizations changes can help you prevent any unwanted, accidental or intentional modifications that may lead to unauthorized access or other security breaches. This monitoring technique helps you to ensure that any unexpected changes performed @@ -8,7 +7,7 @@ rolled back. ### Impact -Lack of observability into critical organisation changes + {{ remediationActions }} diff --git a/checks/cloud/aws/apigateway/enable_access_logging.go b/checks/cloud/aws/apigateway/enable_access_logging.go index ee698b0f..376fdec4 100755 --- a/checks/cloud/aws/apigateway/enable_access_logging.go +++ b/checks/cloud/aws/apigateway/enable_access_logging.go @@ -33,7 +33,8 @@ var CheckEnableAccessLogging = rules.Register( Links: cloudFormationEnableAccessLoggingLinks, RemediationMarkdown: cloudFormationEnableAccessLoggingRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, api := range s.AWS.APIGateway.V1.APIs { diff --git a/checks/cloud/aws/apigateway/enable_access_logging.rego b/checks/cloud/aws/apigateway/enable_access_logging.rego new file mode 100644 index 00000000..2825ca94 --- /dev/null +++ b/checks/cloud/aws/apigateway/enable_access_logging.rego @@ -0,0 +1,51 @@ +# METADATA +# title: API Gateway stages for V1 and V2 should have access logging enabled +# description: | +# API Gateway stages should have access log settings block configured to track all access to a particular stage. This should be applied to both v1 and v2 gateway stages. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html +# custom: +# id: AVD-AWS-0001 +# avd_id: AVD-AWS-0001 +# provider: aws +# service: apigateway +# severity: MEDIUM +# short_code: enable-access-logging +# recommended_action: Enable logging for API Gateway stages +# input: +# selector: +# - type: cloud +# subtypes: +# - service: apigateway +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_stage#access_log_settings +# good_examples: checks/cloud/aws/apigateway/enable_access_logging.tf.go +# bad_examples: checks/cloud/aws/apigateway/enable_access_logging.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/apigateway/enable_access_logging.cf.go +# bad_examples: checks/cloud/aws/apigateway/enable_access_logging.cf.go +package builtin.aws.apigateway.aws0001 + +import rego.v1 + +deny contains res if { + some stage in input.aws.apigateway.v1.apis[_].stages + not logging_is_configured(stage) + res := result.new("Access logging is not configured.", stage.accesslogging.cloudwatchloggrouparn) +} + +deny contains res if { + some stage in input.aws.apigateway.v2.apis[_].stages + not logging_is_configured(stage) + res := result.new("Access logging is not configured.", stage.accesslogging.cloudwatchloggrouparn) +} + +logging_is_configured(stage) if { + isManaged(stage) + stage.accesslogging.cloudwatchloggrouparn.value != "" +} diff --git a/checks/cloud/aws/apigateway/enable_access_logging_test.go b/checks/cloud/aws/apigateway/enable_access_logging_test.go deleted file mode 100644 index 390805d7..00000000 --- a/checks/cloud/aws/apigateway/enable_access_logging_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package apigateway - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableAccessLogging(t *testing.T) { - tests := []struct { - name string - input v1.APIGateway - expected bool - }{ - { - name: "API Gateway stage with no log group ARN", - input: v1.APIGateway{ - APIs: []v1.API{ - { - Metadata: trivyTypes.NewTestMetadata(), - Stages: []v1.Stage{ - { - Metadata: trivyTypes.NewTestMetadata(), - AccessLogging: v1.AccessLogging{ - Metadata: trivyTypes.NewTestMetadata(), - CloudwatchLogGroupARN: trivyTypes.String("", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "API Gateway stage with log group ARN", - input: v1.APIGateway{ - APIs: []v1.API{ - { - Metadata: trivyTypes.NewTestMetadata(), - Stages: []v1.Stage{ - { - Metadata: trivyTypes.NewTestMetadata(), - AccessLogging: v1.AccessLogging{ - Metadata: trivyTypes.NewTestMetadata(), - CloudwatchLogGroupARN: trivyTypes.String("log-group-arn", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.APIGateway.V1 = test.input - results := CheckEnableAccessLogging.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableAccessLogging.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/apigateway/enable_access_logging_test.rego b/checks/cloud/aws/apigateway/enable_access_logging_test.rego new file mode 100644 index 00000000..356d4044 --- /dev/null +++ b/checks/cloud/aws/apigateway/enable_access_logging_test.rego @@ -0,0 +1,16 @@ +package builtin.aws.apigateway.aws0001_test + +import rego.v1 + +import data.builtin.aws.apigateway.aws0001 as check +import data.lib.test + +test_deny_api_gateway_without_log_group_arn if { + inp := {"aws": {"apigateway": {"v1": {"apis": [{"stages": [{"accesslogging": {"cloudwatchloggrouparn": {"value": ""}}}]}]}}}} + test.assert_count(check.deny, 1) with input as inp +} + +test_allow_api_gateway_with_log_group_arn if { + inp := {"aws": {"apigateway": {"v1": {"apis": [{"stages": [{"accesslogging": {"cloudwatchloggrouparn": {"value": "log-group-arn"}}}]}]}}}} + test.assert_empty(check.deny) with input as inp +} diff --git a/checks/cloud/aws/apigateway/enable_cache.go b/checks/cloud/aws/apigateway/enable_cache.go index cf7a0052..32308e62 100644 --- a/checks/cloud/aws/apigateway/enable_cache.go +++ b/checks/cloud/aws/apigateway/enable_cache.go @@ -25,7 +25,8 @@ var CheckEnableCache = rules.Register( Links: terraformEnableCacheLinks, RemediationMarkdown: terraformEnableCacheRemediationMarkdown, }, - Severity: severity.Low, + Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, api := range s.AWS.APIGateway.V1.APIs { diff --git a/checks/cloud/aws/apigateway/enable_cache.rego b/checks/cloud/aws/apigateway/enable_cache.rego new file mode 100644 index 00000000..42dfcd62 --- /dev/null +++ b/checks/cloud/aws/apigateway/enable_cache.rego @@ -0,0 +1,45 @@ +# METADATA +# title: Ensure that response caching is enabled for your Amazon API Gateway REST APIs. +# description: | +# A REST API in API Gateway is a collection of resources and methods that are integrated with backend HTTP endpoints, Lambda functions, or other AWS services. You can enable API caching in Amazon API Gateway to cache your endpoint responses. With caching, you can reduce the number of calls made to your endpoint and also improve the latency of requests to your API. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html +# custom: +# id: AVD-AWS-0190 +# avd_id: AVD-AWS-0190 +# provider: aws +# service: apigateway +# severity: LOW +# short_code: enable-cache +# recommended_action: Enable cache +# input: +# selector: +# - type: cloud +# subtypes: +# - service: apigateway +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_method_settings#cache_enabled +# good_examples: checks/cloud/aws/apigateway/enable_cache.tf.go +# bad_examples: checks/cloud/aws/apigateway/enable_cache.tf.go +package builtin.aws.apigateway.aws0190 + +import rego.v1 + +deny contains res if { + some api in input.aws.apigateway.v1.apis + isManaged(api) + some stage in api.stages + isManaged(stage) + some settings in stage.restmethodsettings + isManaged(settings) + not settings.cacheenabled.value + res := result.new( + "Cache data is not enabled.", + object.get(settings, "cacheenabled", settings), + ) +} diff --git a/checks/cloud/aws/apigateway/enable_cache_encryption.go b/checks/cloud/aws/apigateway/enable_cache_encryption.go index 95942361..5d28b5c4 100755 --- a/checks/cloud/aws/apigateway/enable_cache_encryption.go +++ b/checks/cloud/aws/apigateway/enable_cache_encryption.go @@ -25,7 +25,8 @@ var CheckEnableCacheEncryption = rules.Register( Links: terraformEnableCacheEncryptionLinks, RemediationMarkdown: terraformEnableCacheEncryptionRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, api := range s.AWS.APIGateway.V1.APIs { diff --git a/checks/cloud/aws/apigateway/enable_cache_encryption.rego b/checks/cloud/aws/apigateway/enable_cache_encryption.rego new file mode 100644 index 00000000..8d39f3e5 --- /dev/null +++ b/checks/cloud/aws/apigateway/enable_cache_encryption.rego @@ -0,0 +1,44 @@ +# METADATA +# title: API Gateway must have cache enabled +# description: | +# Method cache encryption ensures that any sensitive data in the cache is not vulnerable to compromise in the event of interception +# scope: package +# schemas: +# - input: schema["cloud"] +# custom: +# id: AVD-AWS-0002 +# avd_id: AVD-AWS-0002 +# provider: aws +# service: apigateway +# severity: MEDIUM +# short_code: enable-cache-encryption +# recommended_action: Enable cache encryption +# input: +# selector: +# - type: cloud +# subtypes: +# - service: apigateway +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_method_settings#cache_data_encrypted +# good_examples: checks/cloud/aws/apigateway/enable_cache_encryption.tf.go +# bad_examples: checks/cloud/aws/apigateway/enable_cache_encryption.tf.go +package builtin.aws.apigateway.aws0002 + +import rego.v1 + +deny contains res if { + some api in input.aws.apigateway.v1.apis + isManaged(api) + some stage in api.stages + isManaged(stage) + some settings in stage.restmethodsettings + isManaged(settings) + settings.cacheenabled.value + not settings.cachedataencrypted.value + res := result.new( + "Cache data is not encrypted.", + object.get(settings, "cachedataencrypted", settings), + ) +} diff --git a/checks/cloud/aws/apigateway/enable_cache_encryption_test.go b/checks/cloud/aws/apigateway/enable_cache_encryption_test.go deleted file mode 100644 index 70fbec68..00000000 --- a/checks/cloud/aws/apigateway/enable_cache_encryption_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package apigateway - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableCacheEncryption(t *testing.T) { - tests := []struct { - name string - input v1.APIGateway - expected bool - }{ - { - name: "API Gateway stage with unencrypted cache", - input: v1.APIGateway{ - APIs: []v1.API{ - { - Metadata: trivyTypes.NewTestMetadata(), - Stages: []v1.Stage{ - { - Metadata: trivyTypes.NewTestMetadata(), - RESTMethodSettings: []v1.RESTMethodSettings{ - { - Metadata: trivyTypes.NewTestMetadata(), - CacheDataEncrypted: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - CacheEnabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "API Gateway stage with encrypted cache", - input: v1.APIGateway{ - APIs: []v1.API{ - { - Metadata: trivyTypes.NewTestMetadata(), - Stages: []v1.Stage{ - { - Metadata: trivyTypes.NewTestMetadata(), - RESTMethodSettings: []v1.RESTMethodSettings{ - { - Metadata: trivyTypes.NewTestMetadata(), - CacheDataEncrypted: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - CacheEnabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "API Gateway stage with caching disabled", - input: v1.APIGateway{ - APIs: []v1.API{ - { - Metadata: trivyTypes.NewTestMetadata(), - Stages: []v1.Stage{ - { - Metadata: trivyTypes.NewTestMetadata(), - RESTMethodSettings: []v1.RESTMethodSettings{ - { - Metadata: trivyTypes.NewTestMetadata(), - CacheDataEncrypted: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - CacheEnabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.APIGateway.V1 = test.input - results := CheckEnableCacheEncryption.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableCacheEncryption.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/apigateway/enable_cache_encryption_test.rego b/checks/cloud/aws/apigateway/enable_cache_encryption_test.rego new file mode 100644 index 00000000..e5e682e3 --- /dev/null +++ b/checks/cloud/aws/apigateway/enable_cache_encryption_test.rego @@ -0,0 +1,19 @@ +package builtin.aws.apigateway.aws0002_test + +import rego.v1 + +import data.builtin.aws.apigateway.aws0002 as check +import data.lib.test + +test_allow_api_gateway_with_cache_encryption if { + test.assert_empty(check.deny) with input as build_input(true) +} + +test_deny_api_gateway_without_cache_encryption if { + test.assert_count(check.deny, 1) with input as build_input(false) +} + +build_input(cachedataencrypted) := {"aws": {"apigateway": {"v1": {"apis": [{"stages": [{"restmethodsettings": [{ + "cacheenabled": {"value": true}, + "cachedataencrypted": {"value": cachedataencrypted}, +}]}]}]}}}} diff --git a/checks/cloud/aws/apigateway/enable_cache_test.go b/checks/cloud/aws/apigateway/enable_cache_test.go deleted file mode 100644 index d3083807..00000000 --- a/checks/cloud/aws/apigateway/enable_cache_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package apigateway - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableCache(t *testing.T) { - tests := []struct { - name string - input v1.APIGateway - expected bool - }{ - { - name: "API Gateway stage with caching disabled", - input: v1.APIGateway{ - APIs: []v1.API{ - { - Metadata: trivyTypes.NewTestMetadata(), - Stages: []v1.Stage{ - { - Metadata: trivyTypes.NewTestMetadata(), - RESTMethodSettings: []v1.RESTMethodSettings{ - { - Metadata: trivyTypes.NewTestMetadata(), - CacheEnabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - }, - expected: true, - }, - - { - name: "API Gateway stage with caching enabled", - input: v1.APIGateway{ - APIs: []v1.API{ - { - Metadata: trivyTypes.NewTestMetadata(), - Stages: []v1.Stage{ - { - Metadata: trivyTypes.NewTestMetadata(), - RESTMethodSettings: []v1.RESTMethodSettings{ - { - Metadata: trivyTypes.NewTestMetadata(), - CacheEnabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.APIGateway.V1 = test.input - results := CheckEnableCache.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableCache.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/apigateway/enable_cache_test.rego b/checks/cloud/aws/apigateway/enable_cache_test.rego new file mode 100644 index 00000000..4376f9ac --- /dev/null +++ b/checks/cloud/aws/apigateway/enable_cache_test.rego @@ -0,0 +1,16 @@ +package builtin.aws.apigateway.aws0190_test + +import rego.v1 + +import data.builtin.aws.apigateway.aws0190 as check +import data.lib.test + +test_allow_cache_enabled if { + test.assert_empty(check.deny) with input as build_input(true) +} + +test_deny_cache_disabled if { + test.assert_count(check.deny, 1) with input as build_input(false) +} + +build_input(cacheenabled) := {"aws": {"apigateway": {"v1": {"apis": [{"stages": [{"restmethodsettings": [{"cacheenabled": {"value": cacheenabled}}]}]}]}}}} diff --git a/checks/cloud/aws/apigateway/enable_tracing.go b/checks/cloud/aws/apigateway/enable_tracing.go index 9d3b30b3..c1882010 100755 --- a/checks/cloud/aws/apigateway/enable_tracing.go +++ b/checks/cloud/aws/apigateway/enable_tracing.go @@ -25,7 +25,8 @@ var CheckEnableTracing = rules.Register( Links: terraformEnableTracingLinks, RemediationMarkdown: terraformEnableTracingRemediationMarkdown, }, - Severity: severity.Low, + Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, api := range s.AWS.APIGateway.V1.APIs { diff --git a/checks/cloud/aws/apigateway/enable_tracing.rego b/checks/cloud/aws/apigateway/enable_tracing.rego new file mode 100644 index 00000000..b35c3c19 --- /dev/null +++ b/checks/cloud/aws/apigateway/enable_tracing.rego @@ -0,0 +1,41 @@ +# METADATA +# title: API Gateway must have X-Ray tracing enabled +# description: | +# X-Ray tracing enables end-to-end debugging and analysis of all API Gateway HTTP requests. +# scope: package +# schemas: +# - input: schema["cloud"] +# custom: +# id: AVD-AWS-0003 +# avd_id: AVD-AWS-0003 +# provider: aws +# service: apigateway +# severity: LOW +# short_code: enable-tracing +# recommended_action: Enable tracing +# input: +# selector: +# - type: cloud +# subtypes: +# - service: apigateway +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_stage#xray_tracing_enabled +# good_examples: checks/cloud/aws/apigateway/enable_tracing.tf.go +# bad_examples: checks/cloud/aws/apigateway/enable_tracing.tf.go +package builtin.aws.apigateway.aws0003 + +import rego.v1 + +deny contains res if { + some api in input.aws.apigateway.v1.apis + isManaged(api) + some stage in api.stages + isManaged(stage) + not stage.xraytracingenabled.value + res := result.new( + "X-Ray tracing is not enabled.", + object.get(stage, "xraytracingenabled", stage), + ) +} diff --git a/checks/cloud/aws/apigateway/enable_tracing_test.go b/checks/cloud/aws/apigateway/enable_tracing_test.go deleted file mode 100644 index 1a82b01f..00000000 --- a/checks/cloud/aws/apigateway/enable_tracing_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package apigateway - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableTracing(t *testing.T) { - tests := []struct { - name string - input v1.APIGateway - expected bool - }{ - { - name: "API Gateway stage with X-Ray tracing disabled", - input: v1.APIGateway{ - APIs: []v1.API{ - { - Metadata: trivyTypes.NewTestMetadata(), - Stages: []v1.Stage{ - { - Metadata: trivyTypes.NewTestMetadata(), - XRayTracingEnabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "API Gateway stage with X-Ray tracing enabled", - input: v1.APIGateway{ - APIs: []v1.API{ - { - Metadata: trivyTypes.NewTestMetadata(), - Stages: []v1.Stage{ - { - Metadata: trivyTypes.NewTestMetadata(), - XRayTracingEnabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.APIGateway.V1 = test.input - results := CheckEnableTracing.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableTracing.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/apigateway/enable_tracing_test.rego b/checks/cloud/aws/apigateway/enable_tracing_test.rego new file mode 100644 index 00000000..dfaef26a --- /dev/null +++ b/checks/cloud/aws/apigateway/enable_tracing_test.rego @@ -0,0 +1,16 @@ +package builtin.aws.apigateway.aws0003_test + +import rego.v1 + +import data.builtin.aws.apigateway.aws0003 as check +import data.lib.test + +test_allow_tracing_enabled if { + test.assert_empty(check.deny) with input as build_input(true) +} + +test_deny_tracing_disabled if { + test.assert_count(check.deny, 1) with input as build_input(false) +} + +build_input(xraytracingenabled) := {"aws": {"apigateway": {"v1": {"apis": [{"stages": [{"xraytracingenabled": {"value": xraytracingenabled}}]}]}}}} diff --git a/checks/cloud/aws/apigateway/no_public_access.go b/checks/cloud/aws/apigateway/no_public_access.go index 4a51871b..976c5e51 100755 --- a/checks/cloud/aws/apigateway/no_public_access.go +++ b/checks/cloud/aws/apigateway/no_public_access.go @@ -26,7 +26,8 @@ var CheckNoPublicAccess = rules.Register( Links: terraformNoPublicAccessLinks, RemediationMarkdown: terraformNoPublicAccessRemediationMarkdown, }, - Severity: severity.Low, + Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, api := range s.AWS.APIGateway.V1.APIs { diff --git a/checks/cloud/aws/apigateway/no_public_access.rego b/checks/cloud/aws/apigateway/no_public_access.rego new file mode 100644 index 00000000..fad776c7 --- /dev/null +++ b/checks/cloud/aws/apigateway/no_public_access.rego @@ -0,0 +1,45 @@ +# METADATA +# title: No unauthorized access to API Gateway methods +# description: | +# API Gateway methods should generally be protected by authorization or api key. OPTION verb calls can be used without authorization +# scope: package +# schemas: +# - input: schema["cloud"] +# custom: +# id: AVD-AWS-0004 +# avd_id: AVD-AWS-0004 +# provider: aws +# service: apigateway +# severity: LOW +# short_code: no-public-access +# recommended_action: Use and authorization method or require API Key +# input: +# selector: +# - type: cloud +# subtypes: +# - service: apigateway +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_method#authorization +# good_examples: checks/cloud/aws/apigateway/no_public_access.tf.go +# bad_examples: checks/cloud/aws/apigateway/no_public_access.tf.go +package builtin.aws.apigateway.aws0004 + +import rego.v1 + +authorization_none := "NONE" + +deny contains res if { + some api in input.aws.apigateway.v1.apis + isManaged(api) + some method in api.resources[_].methods + not method_is_option(method) + not is_apikey_required(api) + method.authorizationtype.value == authorization_none + res := result.new("Authorization is not enabled for this method.", method.authorizationtype) +} + +method_is_option(method) := method.httpmethod.value == "OPTION" + +is_apikey_required(api) := api.apikeyrequired.value diff --git a/checks/cloud/aws/apigateway/no_public_access_test.go b/checks/cloud/aws/apigateway/no_public_access_test.go deleted file mode 100644 index 043b5345..00000000 --- a/checks/cloud/aws/apigateway/no_public_access_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package apigateway - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckNoPublicAccess(t *testing.T) { - tests := []struct { - name string - input v1.APIGateway - expected bool - }{ - { - name: "API GET method without authorization", - input: v1.APIGateway{ - APIs: []v1.API{ - { - Metadata: trivyTypes.NewTestMetadata(), - Resources: []v1.Resource{ - { - Methods: []v1.Method{ - { - Metadata: trivyTypes.NewTestMetadata(), - HTTPMethod: trivyTypes.String("GET", trivyTypes.NewTestMetadata()), - APIKeyRequired: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - AuthorizationType: trivyTypes.String(v1.AuthorizationNone, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "API OPTION method without authorization", - input: v1.APIGateway{ - APIs: []v1.API{ - { - Metadata: trivyTypes.NewTestMetadata(), - Resources: []v1.Resource{ - { - Methods: []v1.Method{ - { - Metadata: trivyTypes.NewTestMetadata(), - HTTPMethod: trivyTypes.String("OPTION", trivyTypes.NewTestMetadata()), - APIKeyRequired: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - AuthorizationType: trivyTypes.String(v1.AuthorizationNone, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "API GET method with IAM authorization", - input: v1.APIGateway{ - APIs: []v1.API{ - { - Metadata: trivyTypes.NewTestMetadata(), - Resources: []v1.Resource{ - { - Methods: []v1.Method{ - { - Metadata: trivyTypes.NewTestMetadata(), - HTTPMethod: trivyTypes.String("GET", trivyTypes.NewTestMetadata()), - APIKeyRequired: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - AuthorizationType: trivyTypes.String(v1.AuthorizationIAM, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.APIGateway.V1 = test.input - results := CheckNoPublicAccess.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckNoPublicAccess.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/apigateway/no_public_access_test.rego b/checks/cloud/aws/apigateway/no_public_access_test.rego new file mode 100644 index 00000000..1fad77fd --- /dev/null +++ b/checks/cloud/aws/apigateway/no_public_access_test.rego @@ -0,0 +1,25 @@ +package builtin.aws.apigateway.aws0004_test + +import rego.v1 + +import data.builtin.aws.apigateway.aws0004 as check +import data.lib.test + +test_deny_get_method_without_auth if { + inp := input_with_method({"httpmethod": {"value": "GET"}, "authorizationtype": {"value": "NONE"}}) + test.assert_count(check.deny, 1) with input as inp +} + +test_allow_option_method if { + test.assert_empty(check.deny) with input as input_with_method({"httpmethod": {"value": "OPTION"}}) +} + +test_allow_get_method_with_auth if { + test.assert_empty(check.deny) with input as input_with_method({"methods": [{"httpmethod": {"value": "GET"}, "authorizationtype": {"value": "AWS_IAM"}}]}) +} + +test_allow_if_api_required if { + test.assert_empty(check.deny) with input as input_with_method({"httpmethod": {"value": "GET"}, "authorizationtype": {"value": "AWS_IAM"}}) +} + +input_with_method(method) = {"aws": {"apigateway": {"v1": {"apis": [{"resources": [{"methods": [method]}]}]}}}} diff --git a/checks/cloud/aws/apigateway/use_secure_tls_policy.go b/checks/cloud/aws/apigateway/use_secure_tls_policy.go index 000e607a..7ce35af3 100755 --- a/checks/cloud/aws/apigateway/use_secure_tls_policy.go +++ b/checks/cloud/aws/apigateway/use_secure_tls_policy.go @@ -27,7 +27,8 @@ var CheckUseSecureTlsPolicy = rules.Register( Links: terraformUseSecureTlsPolicyLinks, RemediationMarkdown: terraformUseSecureTlsPolicyRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, domain := range s.AWS.APIGateway.V1.DomainNames { diff --git a/checks/cloud/aws/apigateway/use_secure_tls_policy.rego b/checks/cloud/aws/apigateway/use_secure_tls_policy.rego new file mode 100644 index 00000000..44a9711a --- /dev/null +++ b/checks/cloud/aws/apigateway/use_secure_tls_policy.rego @@ -0,0 +1,51 @@ +# METADATA +# title: API Gateway domain name uses outdated SSL/TLS protocols. +# description: | +# You should not use outdated/insecure TLS versions for encryption. You should be using TLS v1.2+. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-custom-domain-tls-version.html +# custom: +# id: AVD-AWS-0005 +# avd_id: AVD-AWS-0005 +# provider: aws +# service: apigateway +# severity: HIGH +# short_code: use-secure-tls-policy +# recommended_action: Use the most modern TLS/SSL policies available +# input: +# selector: +# - type: cloud +# subtypes: +# - service: apigateway +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_domain_name#security_policy +# good_examples: checks/cloud/aws/apigateway/use_secure_tls_policy.tf.go +# bad_examples: checks/cloud/aws/apigateway/use_secure_tls_policy.tf.go +package builtin.aws.apigateway.aws0005 + +import rego.v1 + +deny contains res if { + some domain in input.aws.apigateway.v1.domainnames + not is_tls_1_2(domain) + res := result.new( + "Domain name is configured with an outdated TLS policy.", + object.get(domain, "securitypolicy", domain), + ) +} + +deny contains res if { + some domain in input.aws.apigateway.v2.domainnames + not is_tls_1_2(domain) + res := result.new( + "Domain name is configured with an outdated TLS policy.", + object.get(domain, "securitypolicy", domain), + ) +} + +is_tls_1_2(domain) := domain.securitypolicy.value == "TLS_1_2" diff --git a/checks/cloud/aws/apigateway/use_secure_tls_policy_test.go b/checks/cloud/aws/apigateway/use_secure_tls_policy_test.go deleted file mode 100644 index ec072a42..00000000 --- a/checks/cloud/aws/apigateway/use_secure_tls_policy_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package apigateway - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckUseSecureTlsPolicy(t *testing.T) { - tests := []struct { - name string - input v1.APIGateway - expected bool - }{ - { - name: "API Gateway domain name with TLS version 1.0", - input: v1.APIGateway{ - DomainNames: []v1.DomainName{ - { - Metadata: trivyTypes.NewTestMetadata(), - SecurityPolicy: trivyTypes.String("TLS_1_0", trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "API Gateway domain name with TLS version 1.2", - input: v1.APIGateway{ - DomainNames: []v1.DomainName{ - { - Metadata: trivyTypes.NewTestMetadata(), - SecurityPolicy: trivyTypes.String("TLS_1_2", trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.APIGateway.V1 = test.input - results := CheckUseSecureTlsPolicy.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckUseSecureTlsPolicy.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/apigateway/use_secure_tls_policy_test.rego b/checks/cloud/aws/apigateway/use_secure_tls_policy_test.rego new file mode 100644 index 00000000..5bc6001b --- /dev/null +++ b/checks/cloud/aws/apigateway/use_secure_tls_policy_test.rego @@ -0,0 +1,15 @@ +package builtin.aws.apigateway.aws0005_test + +import rego.v1 + +import data.builtin.aws.apigateway.aws0005 as check +import data.lib.test + +test_allow_with_tls_1_2 if { + test.assert_empty(check.deny) with input as {"aws": {"apigateway": {"v1": {"domainnames": [{"securitypolicy": {"value": "TLS_1_2"}}]}}}} +} + +test_deny_with_tls_1_0 if { + inp := {"aws": {"apigateway": {"v1": {"domainnames": [{"securitypolicy": {"value": "TLS_1_0"}}]}}}} + test.assert_count(check.deny, 1) with input as inp +} diff --git a/checks/cloud/aws/cloudfront/enable_logging.go b/checks/cloud/aws/cloudfront/enable_logging.go index 76a1e9da..f40ff916 100755 --- a/checks/cloud/aws/cloudfront/enable_logging.go +++ b/checks/cloud/aws/cloudfront/enable_logging.go @@ -33,7 +33,8 @@ var CheckEnableLogging = rules.Register( Links: cloudFormationEnableLoggingLinks, RemediationMarkdown: cloudFormationEnableLoggingRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, dist := range s.AWS.Cloudfront.Distributions { diff --git a/checks/cloud/aws/cloudfront/enable_logging.rego b/checks/cloud/aws/cloudfront/enable_logging.rego new file mode 100644 index 00000000..abd63d41 --- /dev/null +++ b/checks/cloud/aws/cloudfront/enable_logging.rego @@ -0,0 +1,45 @@ +# METADATA +# title: Cloudfront distribution should have Access Logging configured +# description: | +# You should configure CloudFront Access Logging to create log files that contain detailed information about every user request that CloudFront receives +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html +# custom: +# id: AVD-AWS-0010 +# avd_id: AVD-AWS-0010 +# provider: aws +# service: cloudfront +# severity: MEDIUM +# short_code: enable-logging +# recommended_action: Enable logging for CloudFront distributions +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudfront +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution#logging_config +# good_examples: checks/cloud/aws/cloudfront/enable_logging.tf.go +# bad_examples: checks/cloud/aws/cloudfront/enable_logging.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/cloudfront/enable_logging.cf.go +# bad_examples: checks/cloud/aws/cloudfront/enable_logging.cf.go +package builtin.aws.cloudfront.aws0010 + +import rego.v1 + +deny contains res if { + some dist in input.aws.cloudfront.distributions + not has_logging_bucket(dist) + res := result.new( + "Distribution does not have logging enabled", + object.get(dist, ["logging", "bucket"], dist), + ) +} + +has_logging_bucket(dist) := dist.logging.bucket.value != "" diff --git a/checks/cloud/aws/cloudfront/enable_logging_test.go b/checks/cloud/aws/cloudfront/enable_logging_test.go deleted file mode 100644 index f40030ab..00000000 --- a/checks/cloud/aws/cloudfront/enable_logging_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package cloudfront - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudfront" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableLogging(t *testing.T) { - tests := []struct { - name string - input cloudfront.Cloudfront - expected bool - }{ - { - name: "CloudFront distribution missing logging configuration", - input: cloudfront.Cloudfront{ - Distributions: []cloudfront.Distribution{ - { - Metadata: trivyTypes.NewTestMetadata(), - Logging: cloudfront.Logging{ - Metadata: trivyTypes.NewTestMetadata(), - Bucket: trivyTypes.String("", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "CloudFront distribution with logging configured", - input: cloudfront.Cloudfront{ - Distributions: []cloudfront.Distribution{ - { - Metadata: trivyTypes.NewTestMetadata(), - Logging: cloudfront.Logging{ - Metadata: trivyTypes.NewTestMetadata(), - Bucket: trivyTypes.String("mylogs.s3.amazonaws.com", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.Cloudfront = test.input - results := CheckEnableLogging.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableLogging.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudfront/enable_logging_test.rego b/checks/cloud/aws/cloudfront/enable_logging_test.rego new file mode 100644 index 00000000..3c58447a --- /dev/null +++ b/checks/cloud/aws/cloudfront/enable_logging_test.rego @@ -0,0 +1,16 @@ +package builtin.aws.cloudfront.aws0010_test + +import rego.v1 + +import data.builtin.aws.cloudfront.aws0010 as check +import data.lib.test + +test_allow_distribution_with_logging if { + inp := {"aws": {"cloudfront": {"distributions": [{"logging": {"bucket": {"value": "somebucket"}}}]}}} + test.assert_empty(check.deny) with input as inp +} + +test_deny_distribution_without_logging if { + inp := {"aws": {"cloudfront": {"distributions": [{"logging": {"bucket": {"value": ""}}}]}}} + test.assert_count(check.deny, 1) with input as inp +} diff --git a/checks/cloud/aws/cloudfront/enable_waf.go b/checks/cloud/aws/cloudfront/enable_waf.go index 38e94b0e..e28ec9e0 100755 --- a/checks/cloud/aws/cloudfront/enable_waf.go +++ b/checks/cloud/aws/cloudfront/enable_waf.go @@ -33,7 +33,8 @@ var CheckEnableWaf = rules.Register( Links: cloudFormationEnableWafLinks, RemediationMarkdown: cloudFormationEnableWafRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, dist := range s.AWS.Cloudfront.Distributions { diff --git a/checks/cloud/aws/cloudfront/enable_waf.rego b/checks/cloud/aws/cloudfront/enable_waf.rego new file mode 100644 index 00000000..2818a7e3 --- /dev/null +++ b/checks/cloud/aws/cloudfront/enable_waf.rego @@ -0,0 +1,45 @@ +# METADATA +# title: CloudFront distribution does not have a WAF in front. +# description: | +# You should configure a Web Application Firewall in front of your CloudFront distribution. This will mitigate many types of attacks on your web application. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/waf/latest/developerguide/cloudfront-features.html +# custom: +# id: AVD-AWS-0011 +# avd_id: AVD-AWS-0011 +# provider: aws +# service: cloudfront +# severity: HIGH +# short_code: enable-waf +# recommended_action: Enable WAF for the CloudFront distribution +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudfront +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution#web_acl_id +# good_examples: checks/cloud/aws/cloudfront/enable_waf.tf.go +# bad_examples: checks/cloud/aws/cloudfront/enable_waf.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/cloudfront/enable_waf.cf.go +# bad_examples: checks/cloud/aws/cloudfront/enable_waf.cf.go +package builtin.aws.cloudfront.aws0011 + +import rego.v1 + +deny contains res if { + some dist in input.aws.cloudfront.distributions + not is_waf_enabled(dist) + res := result.new( + "Distribution does not utilise a WAF.", + object.get(dist, "wafid", dist), + ) +} + +is_waf_enabled(dist) := dist.wafid.value != "" diff --git a/checks/cloud/aws/cloudfront/enable_waf_test.go b/checks/cloud/aws/cloudfront/enable_waf_test.go deleted file mode 100644 index 9752ef4f..00000000 --- a/checks/cloud/aws/cloudfront/enable_waf_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package cloudfront - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudfront" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableWaf(t *testing.T) { - tests := []struct { - name string - input cloudfront.Cloudfront - expected bool - }{ - { - name: "CloudFront distribution missing WAF", - input: cloudfront.Cloudfront{ - Distributions: []cloudfront.Distribution{ - { - Metadata: trivyTypes.NewTestMetadata(), - WAFID: trivyTypes.String("", trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "CloudFront distribution with WAF provided", - input: cloudfront.Cloudfront{ - Distributions: []cloudfront.Distribution{ - { - Metadata: trivyTypes.NewTestMetadata(), - WAFID: trivyTypes.String("waf_id", trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.Cloudfront = test.input - results := CheckEnableWaf.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableWaf.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudfront/enable_waf_test.rego b/checks/cloud/aws/cloudfront/enable_waf_test.rego new file mode 100644 index 00000000..13f5f0e5 --- /dev/null +++ b/checks/cloud/aws/cloudfront/enable_waf_test.rego @@ -0,0 +1,14 @@ +package builtin.aws.cloudfront.aws0011_test + +import rego.v1 + +import data.builtin.aws.cloudfront.aws0011 as check +import data.lib.test + +test_allow_distribution_with_waf if { + test.assert_empty(check.deny) with input as {"aws": {"cloudfront": {"distributions": [{"wafid": {"value": true}}]}}} +} + +test_deny_distribution_without_waf if { + test.assert_equal_message("CloudFront distribution does not have a WAF in front.", check.deny) with input as {"aws": {"cloudfront": {"distributions": [{"wafid": {"value": ""}}]}}} +} diff --git a/checks/cloud/aws/cloudfront/enforce_https.go b/checks/cloud/aws/cloudfront/enforce_https.go index 6134f21b..f9cba9c0 100755 --- a/checks/cloud/aws/cloudfront/enforce_https.go +++ b/checks/cloud/aws/cloudfront/enforce_https.go @@ -36,7 +36,8 @@ You should use HTTPS, which is HTTP over an encrypted (TLS) connection, meaning Links: cloudFormationEnforceHttpsLinks, RemediationMarkdown: cloudFormationEnforceHttpsRemediationMarkdown, }, - Severity: severity.Critical, + Severity: severity.Critical, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, dist := range s.AWS.Cloudfront.Distributions { diff --git a/checks/cloud/aws/cloudfront/enforce_https.rego b/checks/cloud/aws/cloudfront/enforce_https.rego new file mode 100644 index 00000000..4ef7813c --- /dev/null +++ b/checks/cloud/aws/cloudfront/enforce_https.rego @@ -0,0 +1,49 @@ +# METADATA +# title: CloudFront distribution allows unencrypted (HTTP) communications. +# description: | +# Plain HTTP is unencrypted and human-readable. This means that if a malicious actor was to eavesdrop on your connection, they would be able to see all of your data flowing back and forth. +# You should use HTTPS, which is HTTP over an encrypted (TLS) connection, meaning eavesdroppers cannot read your traffic. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https-cloudfront-to-s3-origin.html +# custom: +# id: AVD-AWS-0012 +# avd_id: AVD-AWS-0012 +# provider: aws +# service: cloudfront +# severity: CRITICAL +# short_code: enforce-https +# recommended_action: Only allow HTTPS for CloudFront distribution communication +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudfront +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution#viewer_protocol_policy +# good_examples: checks/cloud/aws/cloudfront/enforce_https.tf.go +# bad_examples: checks/cloud/aws/cloudfront/enforce_https.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/cloudfront/enforce_https.cf.go +# bad_examples: checks/cloud/aws/cloudfront/enforce_https.cf.go +package builtin.aws.cloudfront.aws0012 + +import rego.v1 + +viewer_protocol_policy_allow_all := "allow-all" + +deny contains res if { + some cachebehavior in input.aws.cloudfront.distributions[_] + cachebehavior.viewerprotocolpolicy.value == viewer_protocol_policy_allow_all + res := result.new("Distribution allows unencrypted communications.", cachebehavior.viewerprotocolpolicy) +} + +deny contains res if { + some cachebehavior in input.aws.cloudfront.distributions[_].orderercachebehaviours + cachebehavior.viewerprotocolpolicy.value == viewer_protocol_policy_allow_all + res := result.new("Distribution allows unencrypted communications.", cachebehavior.viewerprotocolpolicy) +} diff --git a/checks/cloud/aws/cloudfront/enforce_https_test.go b/checks/cloud/aws/cloudfront/enforce_https_test.go deleted file mode 100644 index fd0723a8..00000000 --- a/checks/cloud/aws/cloudfront/enforce_https_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package cloudfront - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudfront" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnforceHttps(t *testing.T) { - tests := []struct { - name string - input cloudfront.Cloudfront - expected bool - }{ - { - name: "CloudFront distribution default cache behaviour with allow all policy", - input: cloudfront.Cloudfront{ - Distributions: []cloudfront.Distribution{ - { - Metadata: trivyTypes.NewTestMetadata(), - DefaultCacheBehaviour: cloudfront.CacheBehaviour{ - Metadata: trivyTypes.NewTestMetadata(), - ViewerProtocolPolicy: trivyTypes.String(cloudfront.ViewerPolicyProtocolAllowAll, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "CloudFront distribution ordered cache behaviour with allow all policy", - input: cloudfront.Cloudfront{ - Distributions: []cloudfront.Distribution{ - { - Metadata: trivyTypes.NewTestMetadata(), - DefaultCacheBehaviour: cloudfront.CacheBehaviour{ - Metadata: trivyTypes.NewTestMetadata(), - ViewerProtocolPolicy: trivyTypes.String(cloudfront.ViewerPolicyProtocolHTTPSOnly, trivyTypes.NewTestMetadata()), - }, - OrdererCacheBehaviours: []cloudfront.CacheBehaviour{ - { - Metadata: trivyTypes.NewTestMetadata(), - ViewerProtocolPolicy: trivyTypes.String(cloudfront.ViewerPolicyProtocolAllowAll, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "CloudFront distribution cache behaviours allowing HTTPS only", - input: cloudfront.Cloudfront{ - Distributions: []cloudfront.Distribution{ - { - Metadata: trivyTypes.NewTestMetadata(), - DefaultCacheBehaviour: cloudfront.CacheBehaviour{ - Metadata: trivyTypes.NewTestMetadata(), - ViewerProtocolPolicy: trivyTypes.String(cloudfront.ViewerPolicyProtocolHTTPSOnly, trivyTypes.NewTestMetadata()), - }, - OrdererCacheBehaviours: []cloudfront.CacheBehaviour{ - { - Metadata: trivyTypes.NewTestMetadata(), - ViewerProtocolPolicy: trivyTypes.String(cloudfront.ViewerPolicyProtocolHTTPSOnly, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.Cloudfront = test.input - results := CheckEnforceHttps.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnforceHttps.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudfront/enforce_https_test.rego b/checks/cloud/aws/cloudfront/enforce_https_test.rego new file mode 100644 index 00000000..176cede0 --- /dev/null +++ b/checks/cloud/aws/cloudfront/enforce_https_test.rego @@ -0,0 +1,28 @@ +package builtin.aws.cloudfront.aws0012_test + +import rego.v1 + +import data.builtin.aws.cloudfront.aws0012 as check +import data.lib.test + +test_deny_default_cache_behavior_with_allow_all if { + r := check.deny with input as build_input({"defaultcachebehaviour": {"viewerprotocolpolicy": {"value": "allow-all"}}}) + test.assert_equal_message("Distribution allows unencrypted communications.", r) +} + +test_deny_ordered_cache_behaviors_with_allow_all if { + r := check.deny with input as build_input({"orderercachebehaviours": [{"viewerprotocolpolicy": {"value": "allow-all"}}]}) + test.assert_equal_message("Distribution allows unencrypted communications.", r) +} + +test_allow_default_cache_behavior_with_https if { + inp := build_input({"defaultcachebehavior": {"viewerprotocolpolicy": {"value": "https-only"}}}) + test.assert_empty(check.deny) with input as inp +} + +test_allow_ordered_cache_behaviors_with_https if { + inp := build_input({"orderercachebehaviours": [{"viewerprotocolpolicy": {"value": "https-only"}}]}) + test.assert_empty(check.deny) with input as inp +} + +build_input(body) = {"aws": {"cloudfront": {"distributions": [body]}}} diff --git a/checks/cloud/aws/cloudfront/use_secure_tls_policy.go b/checks/cloud/aws/cloudfront/use_secure_tls_policy.go index 825aa970..12675dce 100755 --- a/checks/cloud/aws/cloudfront/use_secure_tls_policy.go +++ b/checks/cloud/aws/cloudfront/use_secure_tls_policy.go @@ -39,7 +39,8 @@ The only option when using the cloudfront.net domain name is to ignore this rule Links: cloudFormationUseSecureTlsPolicyLinks, RemediationMarkdown: cloudFormationUseSecureTlsPolicyRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, dist := range s.AWS.Cloudfront.Distributions { diff --git a/checks/cloud/aws/cloudfront/use_secure_tls_policy.rego b/checks/cloud/aws/cloudfront/use_secure_tls_policy.rego new file mode 100644 index 00000000..7bb65d70 --- /dev/null +++ b/checks/cloud/aws/cloudfront/use_secure_tls_policy.rego @@ -0,0 +1,52 @@ +# METADATA +# title: CloudFront distribution uses outdated SSL/TLS protocols. +# description: | +# You should not use outdated/insecure TLS versions for encryption. You should be using TLS v1.2+. +# Note: that setting *minimum_protocol_version = "TLSv1.2_2021"* is only possible when *cloudfront_default_certificate* is false (eg. you are not using the cloudfront.net domain name). +# If *cloudfront_default_certificate* is true then the Cloudfront API will only allow setting *minimum_protocol_version = "TLSv1"*, and setting it to any other value will result in a perpetual diff in your *terraform plan*'s. +# The only option when using the cloudfront.net domain name is to ignore this rule. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/secure-connections-supported-viewer-protocols-ciphers.html +# - https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html#DownloadDistValuesGeneral +# custom: +# id: AVD-AWS-0013 +# avd_id: AVD-AWS-0013 +# provider: aws +# service: cloudfront +# severity: HIGH +# short_code: use-secure-tls-policy +# recommended_action: Use the most modern TLS/SSL policies available +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudfront +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution#minimum_protocol_version +# good_examples: checks/cloud/aws/cloudfront/use_secure_tls_policy.tf.go +# bad_examples: checks/cloud/aws/cloudfront/use_secure_tls_policy.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/cloudfront/use_secure_tls_policy.cf.go +# bad_examples: checks/cloud/aws/cloudfront/use_secure_tls_policy.cf.go +package builtin.aws.cloudfront.aws0013 + +import rego.v1 + +protocol_version_tls1_2_2021 = "TLSv1.2_2021" + +deny contains res if { + some dist in input.aws.cloudfront.distributions + not dist.viewercertificate.cloudfrontdefaultcertificate.value + not is_tls_1_2(dist) + res := result.new( + "Distribution allows unencrypted communications.", + object.get(dist, ["viewercertificate", "minimumprotocolversion"], dist), + ) +} + +is_tls_1_2(dist) := dist.viewercertificate.minimumprotocolversion.value == protocol_version_tls1_2_2021 diff --git a/checks/cloud/aws/cloudfront/use_secure_tls_policy_test.go b/checks/cloud/aws/cloudfront/use_secure_tls_policy_test.go deleted file mode 100644 index 56a42dce..00000000 --- a/checks/cloud/aws/cloudfront/use_secure_tls_policy_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package cloudfront - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudfront" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckUseSecureTlsPolicy(t *testing.T) { - tests := []struct { - name string - input cloudfront.Cloudfront - expected bool - }{ - { - name: "CloudFront distribution using TLS v1.0", - input: cloudfront.Cloudfront{ - Distributions: []cloudfront.Distribution{ - { - Metadata: trivyTypes.NewTestMetadata(), - ViewerCertificate: cloudfront.ViewerCertificate{ - Metadata: trivyTypes.NewTestMetadata(), - MinimumProtocolVersion: trivyTypes.String("TLSv1.0", trivyTypes.NewTestMetadata()), - SSLSupportMethod: trivyTypes.String("sni-only", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "CloudFront distribution using TLS v1.2", - input: cloudfront.Cloudfront{ - Distributions: []cloudfront.Distribution{ - { - Metadata: trivyTypes.NewTestMetadata(), - ViewerCertificate: cloudfront.ViewerCertificate{ - Metadata: trivyTypes.NewTestMetadata(), - MinimumProtocolVersion: trivyTypes.String(cloudfront.ProtocolVersionTLS1_2, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - { - name: "CloudFrontDefaultCertificate is true", - input: cloudfront.Cloudfront{ - Distributions: []cloudfront.Distribution{ - { - Metadata: trivyTypes.NewTestMetadata(), - ViewerCertificate: cloudfront.ViewerCertificate{ - Metadata: trivyTypes.NewTestMetadata(), - MinimumProtocolVersion: trivyTypes.String("TLSv1.0", trivyTypes.NewTestMetadata()), - CloudfrontDefaultCertificate: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - { - name: "SSLSupportMethod is not `sny-only`", - input: cloudfront.Cloudfront{ - Distributions: []cloudfront.Distribution{ - { - Metadata: trivyTypes.NewTestMetadata(), - ViewerCertificate: cloudfront.ViewerCertificate{ - Metadata: trivyTypes.NewTestMetadata(), - MinimumProtocolVersion: trivyTypes.String("TLSv1.0", trivyTypes.NewTestMetadata()), - SSLSupportMethod: trivyTypes.String("vip", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.Cloudfront = test.input - results := CheckUseSecureTlsPolicy.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckUseSecureTlsPolicy.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudfront/use_secure_tls_policy_test.rego b/checks/cloud/aws/cloudfront/use_secure_tls_policy_test.rego new file mode 100644 index 00000000..5d370e00 --- /dev/null +++ b/checks/cloud/aws/cloudfront/use_secure_tls_policy_test.rego @@ -0,0 +1,29 @@ +package builtin.aws.cloudfront.aws0013_test + +import rego.v1 + +import data.builtin.aws.cloudfront.aws0013 as check +import data.lib.test + +test_deny_distribution_using_tls_1_0 if { + test.assert_equal_message("Distribution allows unencrypted communications", check.deny) with input as build_input({"viewercertificate": { + "cloudfrontdefaultcertificate": {"value": false}, + "minimumprotocolversion": {"value": "TLSv1.0"}, + }}) +} + +test_allow_distribution_using_tls_1_2 if { + test.assert_empty(check.deny) with input as build_input({"viewercertificate": { + "cloudfrontdefaultcertificate": {"value": false}, + "minimumprotocolversion": {"value": check.protocol_version_tls1_2_2021}, + }}) +} + +test_allow_distribution_with_default_certificate_and_tls_1_0 if { + test.assert_empty(check.deny) with input as build_input({"viewercertificate": { + "cloudfrontdefaultcertificate": {"value": true}, + "minimumprotocolversion": {"value": "TLSv1.0"}, + }}) +} + +build_input(body) = {"aws": {"cloudfront": {"distributions": [body]}}} diff --git a/checks/cloud/aws/cloudwatch/log_group_customer_key.go b/checks/cloud/aws/cloudwatch/log_group_customer_key.go index 1337f521..21155ac3 100755 --- a/checks/cloud/aws/cloudwatch/log_group_customer_key.go +++ b/checks/cloud/aws/cloudwatch/log_group_customer_key.go @@ -33,7 +33,8 @@ var CheckLogGroupCustomerKey = rules.Register( Links: cloudFormationLogGroupCustomerKeyLinks, RemediationMarkdown: cloudFormationLogGroupCustomerKeyRemediationMarkdown, }, - Severity: severity.Low, + Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, group := range s.AWS.CloudWatch.LogGroups { diff --git a/checks/cloud/aws/cloudwatch/log_group_customer_key.rego b/checks/cloud/aws/cloudwatch/log_group_customer_key.rego new file mode 100644 index 00000000..f2c8a738 --- /dev/null +++ b/checks/cloud/aws/cloudwatch/log_group_customer_key.rego @@ -0,0 +1,40 @@ +# METADATA +# title: CloudWatch log groups should be encrypted using CMK +# description: | +# CloudWatch log groups are encrypted by default, however, to get the full benefit of controlling key rotation and other KMS aspects a KMS CMK should be used. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html +# custom: +# id: AVD-AWS-0017 +# avd_id: AVD-AWS-0017 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: log-group-customer-key +# recommended_action: Enable CMK encryption of CloudWatch Log Groups +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group#kms_key_id +# good_examples: checks/cloud/aws/cloudwatch/log_group_customer_key.tf.go +# bad_examples: checks/cloud/aws/cloudwatch/log_group_customer_key.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/cloudwatch/log_group_customer_key.cf.go +# bad_examples: checks/cloud/aws/cloudwatch/log_group_customer_key.cf.go +package builtin.aws.cloudwatch.aws0017 + +import rego.v1 + +deny contains res if { + some group in input.aws.cloudwatch.loggroups + group.kmskeyid.value == "" + res := result.new("Log group is not encrypted.", group) +} diff --git a/checks/cloud/aws/cloudwatch/log_group_customer_key_test.go b/checks/cloud/aws/cloudwatch/log_group_customer_key_test.go deleted file mode 100644 index 9060a0c3..00000000 --- a/checks/cloud/aws/cloudwatch/log_group_customer_key_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package cloudwatch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckLogGroupCustomerKey(t *testing.T) { - tests := []struct { - name string - input cloudwatch.CloudWatch - expected bool - }{ - { - name: "AWS CloudWatch with unencrypted log group", - input: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - KMSKeyID: trivyTypes.String("", trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "AWS CloudWatch with encrypted log group", - input: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - KMSKeyID: trivyTypes.String("some-kms-key", trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudWatch = test.input - results := CheckLogGroupCustomerKey.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckLogGroupCustomerKey.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudwatch/log_group_customer_key_test.rego b/checks/cloud/aws/cloudwatch/log_group_customer_key_test.rego new file mode 100644 index 00000000..070f62fb --- /dev/null +++ b/checks/cloud/aws/cloudwatch/log_group_customer_key_test.rego @@ -0,0 +1,18 @@ +package builtin.aws.cloudwatch.aws0017_test + +import rego.v1 + +import data.builtin.aws.cloudwatch.aws0017 as check +import data.lib.test + +test_allow_log_group_with_cmk if { + inp := {"aws": {"cloudwatch": {"loggroups": [{"kmskeyid": {"value": "some-key-id"}}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_log_group_without_cmk if { + inp := {"aws": {"cloudwatch": {"loggroups": [{"kmskeyid": {"value": ""}}]}}} + + test.assert_equal_message("Log group is not encrypted.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/cloudwatch/require_cloudtrail_change_alarm.go b/checks/cloud/aws/cloudwatch/require_cloudtrail_change_alarm.go index 5f904101..65b9cb19 100644 --- a/checks/cloud/aws/cloudwatch/require_cloudtrail_change_alarm.go +++ b/checks/cloud/aws/cloudwatch/require_cloudtrail_change_alarm.go @@ -37,6 +37,7 @@ CIS recommends that you create a metric filter and alarm for changes to CloudTra Terraform: &scan.EngineMetadata{}, CloudFormation: &scan.EngineMetadata{}, Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { diff --git a/checks/cloud/aws/cloudwatch/require_cloudtrail_change_alarm.rego b/checks/cloud/aws/cloudwatch/require_cloudtrail_change_alarm.rego new file mode 100644 index 00000000..f85f526d --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_cloudtrail_change_alarm.rego @@ -0,0 +1,46 @@ +# METADATA +# title: Ensure a log metric filter and alarm exist for CloudTrail configuration changes +# description: | +# You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +# CIS recommends that you create a metric filter and alarm for changes to CloudTrail configuration settings. Monitoring these changes helps ensure sustained visibility to activities in the account. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html +# custom: +# id: AVD-AWS-0151 +# avd_id: AVD-AWS-0151 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: require-cloud-trail-change-alarm +# recommended_action: Create an alarm to alert on CloudTrail configuration changes +# frameworks: +# cis-aws-1.2: +# - "3.5" +# cis-aws-1.4: +# - "4.5" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +package builtin.aws.cloudwatch.aws0151 + +import rego.v1 + +import data.lib.aws.trails + +config_changes_filter_pattern := `{($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}` + +deny contains res if { + some trail in trails.trails_without_filter(config_changes_filter_pattern) + res := result.new("Cloudtrail has no IAM policy change log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter(config_changes_filter_pattern) + res := result.new("Cloudtrail has no IAM Policy change alarm", trail) +} diff --git a/checks/cloud/aws/cloudwatch/require_cloudtrail_change_alarm_test.go b/checks/cloud/aws/cloudwatch/require_cloudtrail_change_alarm_test.go deleted file mode 100644 index e79d94fe..00000000 --- a/checks/cloud/aws/cloudwatch/require_cloudtrail_change_alarm_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package cloudwatch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/stretchr/testify/assert" -) - -func TestCheckRequireCloudTrailChangeAlarm(t *testing.T) { - tests := []struct { - name string - cloudtrail cloudtrail.CloudTrail - cloudwatch cloudwatch.CloudWatch - expected bool - }{ - { - name: "Multi-region CloudTrail alarms on CloudTrail configuration change", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - Metadata: trivyTypes.NewTestMetadata(), - FilterName: trivyTypes.String("CloudTrailConfigurationChange", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String(` {($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}`, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("CloudTrailConfigurationChange", trivyTypes.NewTestMetadata()), - MetricName: trivyTypes.String("CloudTrailConfigurationChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - { - Metadata: trivyTypes.NewTestMetadata(), - ID: trivyTypes.String("CloudTrailConfigurationChange", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Multi-region CloudTrail has no filter for CloudTrail configuration change", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{}, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("CloudTrailConfigurationChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - {}, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudWatch = test.cloudwatch - testState.AWS.CloudTrail = test.cloudtrail - results := requireCloudTrailChangeAlarm.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == requireCloudTrailChangeAlarm.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudwatch/require_cloudtrail_change_alarm_test.rego b/checks/cloud/aws/cloudwatch/require_cloudtrail_change_alarm_test.rego new file mode 100644 index 00000000..1aa068ed --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_cloudtrail_change_alarm_test.rego @@ -0,0 +1,117 @@ +package builtin.aws.cloudwatch.aws0151_test + +import rego.v1 + +import data.builtin.aws.cloudwatch.aws0151 as check +import data.lib.test + +test_allow_trail_alarms_on_configuration_change if { + inp := {"aws": { + "cloudtrail": {"trails": [{ + "ismultiregion": {"value": true}, + "islogging": {"value": true}, + "cloudwatchlogsloggrouparn": {"value": "arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging"}, + }]}, + "cloudwatch": { + "loggroups": [{ + "arn": {"value": "arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging"}, + "metricfilters": [{ + "filterpattern": {"value": "{($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}"}, + "filtername": {"value": "CloudTrailConfigurationChange"}, + }], + }], + "alarms": [{ + "alarmname": {"value": "CloudTrailConfigurationChange"}, + "metricname": {"value": "CloudTrailConfigurationChange"}, + "metrics": [{"id": {"value": "CloudTrailConfigurationChange"}}], + }], + }, + }} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_trail_does_not_have_filter_for_configuration_change if { + inp := {"aws": { + "cloudtrail": {"trails": [{ + "ismultiregion": {"value": true}, + "islogging": {"value": true}, + "cloudwatchlogsloggrouparn": {"value": "arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging"}, + }]}, + "cloudwatch": { + "loggroups": [{"arn": {"value": "arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging"}}], + "alarms": [{"alarmname": {"value": "OtherAlarm"}}], + }, + }} + + test.assert_count(check.deny, 1) with input as inp +} + +test_deny_trail_does_not_have_alarm_for_configuration_change if { + inp := {"aws": { + "cloudtrail": {"trails": [{ + "ismultiregion": {"value": true}, + "islogging": {"value": true}, + "cloudwatchlogsloggrouparn": {"value": "arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging"}, + }]}, + "cloudwatch": { + "loggroups": [{ + "arn": {"value": "arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging"}, + "metricfilters": [{ + "filterpattern": {"value": "{($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}"}, + "filtername": {"value": "CloudTrailConfigurationChange"}, + }], + }], + "alarms": [{"metricname": {"value": "OtherAlarm"}}], + }, + }} + + test.assert_equal_message("Cloudtrail has no IAM Policy change alarm", check.deny) with input as inp +} + +test_allow_trail_is_not_multiregion if { + inp := {"aws": { + "cloudtrail": {"trails": [{ + "ismultiregion": {"value": false}, + "islogging": {"value": true}, + "cloudwatchlogsloggrouparn": {"value": "arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging"}, + }]}, + "cloudwatch": { + "loggroups": [{"arn": {"value": "arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging"}}], + "alarms": [], + }, + }} + + test.assert_empty(check.deny) with input as inp +} + +test_allow_trail_is_not_logging if { + inp := {"aws": { + "cloudtrail": {"trails": [{ + "ismultiregion": {"value": true}, + "islogging": {"value": false}, + "cloudwatchlogsloggrouparn": {"value": "arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging"}, + }]}, + "cloudwatch": { + "loggroups": [{"arn": {"value": "arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging"}}], + "alarms": [], + }, + }} + + test.assert_empty(check.deny) with input as inp +} + +test_allow_trail_without_loggroup if { + inp := {"aws": { + "cloudtrail": {"trails": [{ + "ismultiregion": {"value": true}, + "islogging": {"value": true}, + }]}, + "cloudwatch": { + "loggroups": [{"arn": {"value": "arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging"}}], + "alarms": [], + }, + }} + + test.assert_empty(check.deny) with input as inp +} diff --git a/checks/cloud/aws/cloudwatch/require_cmk_disabled_alarm.go b/checks/cloud/aws/cloudwatch/require_cmk_disabled_alarm.go index d05a5071..e67f00f1 100644 --- a/checks/cloud/aws/cloudwatch/require_cmk_disabled_alarm.go +++ b/checks/cloud/aws/cloudwatch/require_cmk_disabled_alarm.go @@ -37,6 +37,7 @@ var requireCMKDisabledAlarm = rules.Register( Terraform: &scan.EngineMetadata{}, CloudFormation: &scan.EngineMetadata{}, Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { diff --git a/checks/cloud/aws/cloudwatch/require_cmk_disabled_alarm.rego b/checks/cloud/aws/cloudwatch/require_cmk_disabled_alarm.rego new file mode 100644 index 00000000..038d5a39 --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_cmk_disabled_alarm.rego @@ -0,0 +1,46 @@ +# METADATA +# title: Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer managed keys +# description: | +# You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +# CIS recommends that you create a metric filter and alarm for customer managed keys that have changed state to disabled or scheduled deletion. Data encrypted with disabled or deleted keys is no longer accessible. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html +# custom: +# id: AVD-AWS-0153 +# avd_id: AVD-AWS-0153 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: require-cmk-disabled-alarm +# recommended_action: Create an alarm to alert on CMKs being disabled or scheduled for deletion +# frameworks: +# cis-aws-1.2: +# - "3.7" +# cis-aws-1.4: +# - "4.7" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +package builtin.aws.cloudwatch.aws0153 + +import rego.v1 + +import data.lib.aws.trails + +disabled_filter_pattern := `{($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion))}` + +deny contains res if { + some trail in trails.trails_without_filter(disabled_filter_pattern) + res := result.new("Cloudtrail has no CMK disabled log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter(disabled_filter_pattern) + res := result.new("Cloudtrail has no CMK disabled of scheduled deletion alarm", trail) +} diff --git a/checks/cloud/aws/cloudwatch/require_cmk_disabled_alarm_test.go b/checks/cloud/aws/cloudwatch/require_cmk_disabled_alarm_test.go deleted file mode 100644 index 46b21df9..00000000 --- a/checks/cloud/aws/cloudwatch/require_cmk_disabled_alarm_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package cloudwatch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/stretchr/testify/assert" -) - -func TestCheckCMKDisabledAlarm(t *testing.T) { - tests := []struct { - name string - cloudtrail cloudtrail.CloudTrail - cloudwatch cloudwatch.CloudWatch - expected bool - }{ - { - name: "Multi-region CloudTrail alarms on CMK disabled or scheduled deletion", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - Metadata: trivyTypes.NewTestMetadata(), - FilterName: trivyTypes.String("CMKDisbledOrScheduledDelete", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String(`{($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion))}`, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("CMKDisbledOrScheduledDelete", trivyTypes.NewTestMetadata()), - MetricName: trivyTypes.String("CMKDisbledOrScheduledDelete", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - { - Metadata: trivyTypes.NewTestMetadata(), - ID: trivyTypes.String("CMKDisbledOrScheduledDelete", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Multi-region CloudTrail has no filter for CMK Disabled or scheduled deletion", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{}, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("CMKDisbledOrScheduledDelete", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - {}, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudWatch = test.cloudwatch - testState.AWS.CloudTrail = test.cloudtrail - results := requireCMKDisabledAlarm.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == requireCMKDisabledAlarm.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudwatch/require_config_configuration_change_alarm.go b/checks/cloud/aws/cloudwatch/require_config_configuration_change_alarm.go index 7f0d3aad..83e681f3 100644 --- a/checks/cloud/aws/cloudwatch/require_config_configuration_change_alarm.go +++ b/checks/cloud/aws/cloudwatch/require_config_configuration_change_alarm.go @@ -37,6 +37,7 @@ CIS recommends that you create a metric filter and alarm for changes to AWS Conf Terraform: &scan.EngineMetadata{}, CloudFormation: &scan.EngineMetadata{}, Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { diff --git a/checks/cloud/aws/cloudwatch/require_config_configuration_change_alarm.rego b/checks/cloud/aws/cloudwatch/require_config_configuration_change_alarm.rego new file mode 100644 index 00000000..53557c69 --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_config_configuration_change_alarm.rego @@ -0,0 +1,46 @@ +# METADATA +# title: Ensure a log metric filter and alarm exist for AWS Config configuration changes +# description: | +# You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +# CIS recommends that you create a metric filter and alarm for changes to AWS Config configuration settings. Monitoring these changes helps ensure sustained visibility of configuration items in the account. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html +# custom: +# id: AVD-AWS-0155 +# avd_id: AVD-AWS-0155 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: require-config-configuration-changes-alarm +# recommended_action: Create an alarm to alert on AWS Config configuration changes +# frameworks: +# cis-aws-1.2: +# - "3.9" +# cis-aws-1.4: +# - "4.9" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +package builtin.aws.cloudwatch.aws0155 + +import rego.v1 + +import data.lib.aws.trails + +filter_pattern := `{($.eventSource=config.amazonaws.com) && (($.eventName=StopConfigurationRecorder) || ($.eventName=DeleteDeliveryChannel) || ($.eventName=PutDeliveryChannel) || ($.eventName=PutConfigurationRecorder))}` + +deny contains res if { + some trail in trails.trails_without_filter(filter_pattern) + res := result.new("Cloudtrail has no Config configuration change log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter(filter_pattern) + res := result.new("Cloudtrail has no Config configuration change alarm", trail) +} diff --git a/checks/cloud/aws/cloudwatch/require_config_configuration_change_alarm_test.go b/checks/cloud/aws/cloudwatch/require_config_configuration_change_alarm_test.go deleted file mode 100644 index 2cd3d988..00000000 --- a/checks/cloud/aws/cloudwatch/require_config_configuration_change_alarm_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package cloudwatch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/stretchr/testify/assert" -) - -func TestCheckConfigConfigurationChangeAlarm(t *testing.T) { - tests := []struct { - name string - cloudtrail cloudtrail.CloudTrail - cloudwatch cloudwatch.CloudWatch - expected bool - }{ - { - name: "Multi-region CloudTrail alarms on Config configuration change", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - Metadata: trivyTypes.NewTestMetadata(), - FilterName: trivyTypes.String("ConfigConfigurationChange", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String(`{($.eventSource=config.amazonaws.com) && (($.eventName=StopConfigurationRecorder) || ($.eventName=DeleteDeliveryChannel) || ($.eventName=PutDeliveryChannel) || ($.eventName=PutConfigurationRecorder))}`, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("ConfigConfigurationChange", trivyTypes.NewTestMetadata()), - MetricName: trivyTypes.String("ConfigConfigurationChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - { - Metadata: trivyTypes.NewTestMetadata(), - ID: trivyTypes.String("ConfigConfigurationChange", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Multi-region CloudTrail has no filter for Config configuration change", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{}, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("ConfigConfigurationChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - {}, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudWatch = test.cloudwatch - testState.AWS.CloudTrail = test.cloudtrail - results := requireConfigConfigurationChangeAlarm.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == requireConfigConfigurationChangeAlarm.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudwatch/require_console_login_failure_alarm.go b/checks/cloud/aws/cloudwatch/require_console_login_failure_alarm.go index cf9da510..ca497ede 100644 --- a/checks/cloud/aws/cloudwatch/require_console_login_failure_alarm.go +++ b/checks/cloud/aws/cloudwatch/require_console_login_failure_alarm.go @@ -37,6 +37,7 @@ CIS recommends that you create a metric filter and alarm for failed console auth Terraform: &scan.EngineMetadata{}, CloudFormation: &scan.EngineMetadata{}, Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { diff --git a/checks/cloud/aws/cloudwatch/require_console_login_failure_alarm.rego b/checks/cloud/aws/cloudwatch/require_console_login_failure_alarm.rego new file mode 100644 index 00000000..3fcfc404 --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_console_login_failure_alarm.rego @@ -0,0 +1,46 @@ +# METADATA +# title: Ensure a log metric filter and alarm exist for AWS Management Console authentication failures +# description: | +# You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +# CIS recommends that you create a metric filter and alarm for failed console authentication attempts. Monitoring failed console logins might decrease lead time to detect an attempt to brute-force a credential, which might provide an indicator, such as source IP, that you can use in other event correlations. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-aws-console-sign-in-events.html +# custom: +# id: AVD-AWS-0152 +# avd_id: AVD-AWS-0152 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: require-console-login-failures-alarm +# recommended_action: Create an alarm to alert on console login failures +# frameworks: +# cis-aws-1.4: +# - "4.6" +# cis-aws-1.2: +# - "3.6" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +package builtin.aws.cloudwatch.aws0152 + +import rego.v1 + +import data.lib.aws.trails + +filter_pattern := `{($.eventName=ConsoleLogin) && ($.errorMessage="Failed authentication")}` + +deny contains res if { + some trail in trails.trails_without_filter(filter_pattern) + res := result.new("Cloudtrail has no console login failure log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter(filter_pattern) + res := result.new("ClouCloudtrail has no console login failure alarm", trail) +} diff --git a/checks/cloud/aws/cloudwatch/require_console_login_failure_alarm_test.go b/checks/cloud/aws/cloudwatch/require_console_login_failure_alarm_test.go deleted file mode 100644 index 68530985..00000000 --- a/checks/cloud/aws/cloudwatch/require_console_login_failure_alarm_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package cloudwatch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/stretchr/testify/assert" -) - -func TestCheckRequireConsoleLoginFailureAlarm(t *testing.T) { - tests := []struct { - name string - cloudtrail cloudtrail.CloudTrail - cloudwatch cloudwatch.CloudWatch - expected bool - }{ - { - name: "Multi-region CloudTrail alarms on Console login failure", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - Metadata: trivyTypes.NewTestMetadata(), - FilterName: trivyTypes.String("ConsoleLoginFailure", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String(`{($.eventName=ConsoleLogin) && ($.errorMessage="Failed authentication")}`, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("ConsoleLoginFailure", trivyTypes.NewTestMetadata()), - MetricName: trivyTypes.String("ConsoleLoginFailure", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - { - Metadata: trivyTypes.NewTestMetadata(), - ID: trivyTypes.String("ConsoleLoginFailure", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Multi-region CloudTrail has no filter for console login failure", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{}, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("ConsoleLoginFailure", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - {}, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudWatch = test.cloudwatch - testState.AWS.CloudTrail = test.cloudtrail - results := requireConsoleLoginFailureAlarm.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == requireConsoleLoginFailureAlarm.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudwatch/require_iam_policy_change_alarm.go b/checks/cloud/aws/cloudwatch/require_iam_policy_change_alarm.go index 1ab2cd88..437e51c0 100644 --- a/checks/cloud/aws/cloudwatch/require_iam_policy_change_alarm.go +++ b/checks/cloud/aws/cloudwatch/require_iam_policy_change_alarm.go @@ -37,6 +37,7 @@ CIS recommends that you create a metric filter and alarm for changes made to IAM Terraform: &scan.EngineMetadata{}, CloudFormation: &scan.EngineMetadata{}, Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { diff --git a/checks/cloud/aws/cloudwatch/require_iam_policy_change_alarm.rego b/checks/cloud/aws/cloudwatch/require_iam_policy_change_alarm.rego new file mode 100644 index 00000000..5597f038 --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_iam_policy_change_alarm.rego @@ -0,0 +1,61 @@ +# METADATA +# title: Ensure a log metric filter and alarm exist for IAM policy changes +# description: | +# You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +# CIS recommends that you create a metric filter and alarm for changes made to IAM policies. Monitoring these changes helps ensure that authentication and authorization controls remain intact. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html +# custom: +# id: AVD-AWS-0150 +# avd_id: AVD-AWS-0150 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: require-iam-policy-change-alarm +# recommended_action: Create an alarm to alert on IAM Policy changes +# frameworks: +# cis-aws-1.2: +# - "3.4" +# cis-aws-1.4: +# - "4.4" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +package builtin.aws.cloudwatch.aws0150 + +import rego.v1 + +import data.lib.aws.trails + +filter_pattern := `{($.eventName=DeleteGroupPolicy) || +($.eventName=DeleteRolePolicy) || +($.eventName=DeleteUserPolicy) || +($.eventName=PutGroupPolicy) || +($.eventName=PutRolePolicy) || +($.eventName=PutUserPolicy) || +($.eventName=CreatePolicy) || +($.eventName=DeletePolicy) || +($.eventName=CreatePolicyVersion) || +($.eventName=DeletePolicyVersion) || +($.eventName=AttachRolePolicy) || +($.eventName=DetachRolePolicy) || +($.eventName=AttachUserPolicy) || +($.eventName=DetachUserPolicy) || +($.eventName=AttachGroupPolicy) || +($.eventName=DetachGroupPolicy)}` + +deny contains res if { + some trail in trails.trails_without_filter(filter_pattern) + res := result.new("Cloudtrail has no IAM policy change log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter(filter_pattern) + res := result.new("Cloudtrail has no IAM Policy change alarm", trail) +} diff --git a/checks/cloud/aws/cloudwatch/require_iam_policy_change_alarm_test.go b/checks/cloud/aws/cloudwatch/require_iam_policy_change_alarm_test.go deleted file mode 100644 index 797ad461..00000000 --- a/checks/cloud/aws/cloudwatch/require_iam_policy_change_alarm_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package cloudwatch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/stretchr/testify/assert" -) - -func TestCheckRequireIAMPolicyChangeAlarm(t *testing.T) { - tests := []struct { - name string - cloudtrail cloudtrail.CloudTrail - cloudwatch cloudwatch.CloudWatch - expected bool - }{ - { - name: "Multi-region CloudTrail alarms on IAM Policy change", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - Metadata: trivyTypes.NewTestMetadata(), - FilterName: trivyTypes.String("IAMPolicyChanged", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String(`{($.eventName=DeleteGroupPolicy) || -($.eventName=DeleteRolePolicy) || -($.eventName=DeleteUserPolicy) || -($.eventName=PutGroupPolicy) || -($.eventName=PutRolePolicy) || -($.eventName=PutUserPolicy) || -($.eventName=CreatePolicy) || -($.eventName=DeletePolicy) || -($.eventName=CreatePolicyVersion) || -($.eventName=DeletePolicyVersion) || -($.eventName=AttachRolePolicy) || -($.eventName=DetachRolePolicy) || -($.eventName=AttachUserPolicy) || -($.eventName=DetachUserPolicy) || -($.eventName=AttachGroupPolicy) || -($.eventName=DetachGroupPolicy)}`, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("IAMPolicyChanged", trivyTypes.NewTestMetadata()), - MetricName: trivyTypes.String("IAMPolicyChanged", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - { - Metadata: trivyTypes.NewTestMetadata(), - ID: trivyTypes.String("IAMPolicyChanged", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Multi-region CloudTrail has no filter for IAM Policy change", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{}, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("CloudTrail_Unauthorized_API_Call", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - {}, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudWatch = test.cloudwatch - testState.AWS.CloudTrail = test.cloudtrail - results := requireIAMPolicyChangeAlarm.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == requireIAMPolicyChangeAlarm.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudwatch/require_nacl_change_alarm.go b/checks/cloud/aws/cloudwatch/require_nacl_change_alarm.go index 8f73563b..b2ea0921 100644 --- a/checks/cloud/aws/cloudwatch/require_nacl_change_alarm.go +++ b/checks/cloud/aws/cloudwatch/require_nacl_change_alarm.go @@ -38,6 +38,7 @@ CIS recommends that you create a metric filter and alarm for changes to NACLs. M Terraform: &scan.EngineMetadata{}, CloudFormation: &scan.EngineMetadata{}, Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { diff --git a/checks/cloud/aws/cloudwatch/require_nacl_change_alarm.rego b/checks/cloud/aws/cloudwatch/require_nacl_change_alarm.rego new file mode 100644 index 00000000..ee2ec701 --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_nacl_change_alarm.rego @@ -0,0 +1,50 @@ +# METADATA +# title: Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) +# description: | +# You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +# NACLs are used as a stateless packet filter to control ingress and egress traffic for subnets in a VPC. +# CIS recommends that you create a metric filter and alarm for changes to NACLs. Monitoring these changes helps ensure that AWS resources and services aren't unintentionally exposed. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html +# custom: +# id: AVD-AWS-0157 +# avd_id: AVD-AWS-0157 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: require-nacl-changes-alarm +# recommended_action: Create an alarm to alert on network acl changes +# frameworks: +# cis-aws-1.4: +# - "4.11" +# cis-aws-1.2: +# - "3.11" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +package builtin.aws.cloudwatch.aws0157 + +import rego.v1 + +import data.lib.aws.trails + +filter_pattern := `{($.eventName=CreateNetworkAcl) || + ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || + ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || + ($.eventName=ReplaceNetworkAclAssociation)}` + +deny contains res if { + some trail in trails.trails_without_filter(filter_pattern) + res := result.new("Cloudtrail has no network ACL change log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter(filter_pattern) + res := result.new("Cloudtrail has no network ACL change alarm", trail) +} diff --git a/checks/cloud/aws/cloudwatch/require_nacl_change_alarm_test.go b/checks/cloud/aws/cloudwatch/require_nacl_change_alarm_test.go deleted file mode 100644 index e88201c2..00000000 --- a/checks/cloud/aws/cloudwatch/require_nacl_change_alarm_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package cloudwatch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/stretchr/testify/assert" -) - -func TestCheckNACLChangeAlarm(t *testing.T) { - tests := []struct { - name string - cloudtrail cloudtrail.CloudTrail - cloudwatch cloudwatch.CloudWatch - expected bool - }{ - { - name: "Multi-region CloudTrail alarms on network acl changes", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - Metadata: trivyTypes.NewTestMetadata(), - FilterName: trivyTypes.String("NACLChange", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String(`{($.eventName=CreateNetworkAcl) || - ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || - ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || - ($.eventName=ReplaceNetworkAclAssociation)}`, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("NACLChange", trivyTypes.NewTestMetadata()), - MetricName: trivyTypes.String("NACLChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - { - Metadata: trivyTypes.NewTestMetadata(), - ID: trivyTypes.String("NACLChange", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Multi-region CloudTrail has no filter for network acl changes", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{}, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("NACLChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - {}, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudWatch = test.cloudwatch - testState.AWS.CloudTrail = test.cloudtrail - results := requireNACLChangeAlarm.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == requireNACLChangeAlarm.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudwatch/require_nacl_change_alarm_test.rego b/checks/cloud/aws/cloudwatch/require_nacl_change_alarm_test.rego new file mode 100644 index 00000000..f53f2eb6 --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_nacl_change_alarm_test.rego @@ -0,0 +1,64 @@ +package builtin.aws.cloudwatch.aws0157_test + +import rego.v1 + +import data.builtin.aws.cloudwatch.aws0157 as check +import data.lib.test + +test_allow_change_alarm if { + inp := {"aws": { + "cloudtrail": {"trails": [multiregion_trail]}, + "cloudwatch": { + "loggroups": [{ + "arn": {"value": log_group_arn}, + "metricfilters": [change_metric_filter], + }], + "alarms": [change_alarm], + }, + }} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_filter_does_not_exist if { + inp := {"aws": { + "cloudtrail": {"trails": [multiregion_trail]}, + "cloudwatch": { + "loggroups": [{"arn": {"value": log_group_arn}}], + "alarms": [change_alarm], + }, + }} + + test.assert_count(check.deny, 1) with input as inp +} + +test_deny_alarm_does_not_exist if { + inp := {"aws": { + "cloudtrail": {"trails": [multiregion_trail]}, + "cloudwatch": {"loggroups": [{ + "arn": {"value": log_group_arn}, + "metricfilters": [change_metric_filter], + }]}, + }} + + test.assert_count(check.deny, 1) with input as inp +} + +multiregion_trail := { + "ismultiregion": {"value": true}, + "islogging": {"value": true}, + "cloudwatchlogsloggrouparn": {"value": log_group_arn}, +} + +change_alarm := { + "alarmname": {"value": "ConsoleLoginFailure"}, + "metricname": {"value": "ConsoleLoginFailure"}, + "metrics": [{"id": {"value": "ConsoleLoginFailure"}}], +} + +change_metric_filter := { + "filterpattern": {"value": check.filter_pattern}, + "filtername": {"value": "ConsoleLoginFailure"}, +} + +log_group_arn := "arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging" diff --git a/checks/cloud/aws/cloudwatch/require_network_gateway_change_alarm.go b/checks/cloud/aws/cloudwatch/require_network_gateway_change_alarm.go index 9844359e..a455e0b5 100644 --- a/checks/cloud/aws/cloudwatch/require_network_gateway_change_alarm.go +++ b/checks/cloud/aws/cloudwatch/require_network_gateway_change_alarm.go @@ -38,6 +38,7 @@ CIS recommends that you create a metric filter and alarm for changes to network Terraform: &scan.EngineMetadata{}, CloudFormation: &scan.EngineMetadata{}, Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { diff --git a/checks/cloud/aws/cloudwatch/require_network_gateway_change_alarm.rego b/checks/cloud/aws/cloudwatch/require_network_gateway_change_alarm.rego new file mode 100644 index 00000000..f98e158e --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_network_gateway_change_alarm.rego @@ -0,0 +1,50 @@ +# METADATA +# title: Ensure a log metric filter and alarm exist for changes to network gateways +# description: | +# You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +# Network gateways are required to send and receive traffic to a destination outside a VPC. +# CIS recommends that you create a metric filter and alarm for changes to network gateways. Monitoring these changes helps ensure that all ingress and egress traffic traverses the VPC border via a controlled path. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html +# custom: +# id: AVD-AWS-0158 +# avd_id: AVD-AWS-0158 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: require-network-gateway-changes-alarm +# recommended_action: Create an alarm to alert on network gateway changes +# frameworks: +# cis-aws-1.2: +# - "3.12" +# cis-aws-1.4: +# - "4.12" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +package builtin.aws.cloudwatch.aws0158 + +import rego.v1 + +import data.lib.aws.trails + +filter_pattern := `{($.eventName=CreateCustomerGateway) || + ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || + ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || + ($.eventName=DetachInternetGateway)}` + +deny contains res if { + some trail in trails.trails_without_filter(filter_pattern) + res := result.new("Cloudtrail has no network gateway change log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter(filter_pattern) + res := result.new("Cloudtrail has no network gateway change alarm", trail) +} diff --git a/checks/cloud/aws/cloudwatch/require_network_gateway_change_alarm_test.go b/checks/cloud/aws/cloudwatch/require_network_gateway_change_alarm_test.go deleted file mode 100644 index 0db899cd..00000000 --- a/checks/cloud/aws/cloudwatch/require_network_gateway_change_alarm_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package cloudwatch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/stretchr/testify/assert" -) - -func TestCheckNetworkGatewayChangeAlarm(t *testing.T) { - tests := []struct { - name string - cloudtrail cloudtrail.CloudTrail - cloudwatch cloudwatch.CloudWatch - expected bool - }{ - { - name: "Multi-region CloudTrail alarms on network gateway changes", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - Metadata: trivyTypes.NewTestMetadata(), - FilterName: trivyTypes.String("NetworkGatewayChange", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String(`{($.eventName=CreateCustomerGateway) || - ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || - ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || - ($.eventName=DetachInternetGateway)}`, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("NetworkGatewayChange", trivyTypes.NewTestMetadata()), - MetricName: trivyTypes.String("NetworkGatewayChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - { - Metadata: trivyTypes.NewTestMetadata(), - ID: trivyTypes.String("NetworkGatewayChange", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Multi-region CloudTrail has no filter for network gateway changes", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{}, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("NetworkGatewayChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - {}, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudWatch = test.cloudwatch - testState.AWS.CloudTrail = test.cloudtrail - results := requireNetworkGatewayChangeAlarm.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == requireNetworkGatewayChangeAlarm.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudwatch/require_non_mfa_login_alarm.go b/checks/cloud/aws/cloudwatch/require_non_mfa_login_alarm.go index 5d3f79e2..6f9b02a6 100644 --- a/checks/cloud/aws/cloudwatch/require_non_mfa_login_alarm.go +++ b/checks/cloud/aws/cloudwatch/require_non_mfa_login_alarm.go @@ -37,6 +37,7 @@ var requireNonMFALoginAlarm = rules.Register( Terraform: &scan.EngineMetadata{}, CloudFormation: &scan.EngineMetadata{}, Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { diff --git a/checks/cloud/aws/cloudwatch/require_non_mfa_login_alarm.rego b/checks/cloud/aws/cloudwatch/require_non_mfa_login_alarm.rego new file mode 100644 index 00000000..fb2e4bfc --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_non_mfa_login_alarm.rego @@ -0,0 +1,47 @@ +# METADATA +# title: Ensure a log metric filter and alarm exist for AWS Management Console sign-in without MFA +# description: | +# You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +# CIS recommends that you create a metric filter and alarm console logins that aren't protected by MFA. Monitoring for single-factor console logins increases visibility into accounts that aren't protected by MFA. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://aws.amazon.com/iam/features/mfa/ +# custom: +# id: AVD-AWS-0148 +# avd_id: AVD-AWS-0148 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: require-non-mfa-login-alarm +# recommended_action: Create an alarm to alert on non MFA logins +# frameworks: +# cis-aws-1.2: +# - "3.2" +# cis-aws-1.4: +# - "4.2" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +package builtin.aws.cloudwatch.aws0148 + +import rego.v1 + +import data.lib.aws.trails + +# TODO: https://github.com/cisagov/crossfeed/blob/7da8691c302267015bb45a9ad4485dd87e554fab/infrastructure/log_filters.tf#L28 +filter_pattern := `{ ($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") && ($.userIdentity.type = "IAMUser") && ($.responseElements.ConsoleLogin = "Success") }` + +deny contains res if { + some trail in trails.trails_without_filter(filter_pattern) + res := result.new("Cloudtrail has no non-MFA login log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter(filter_pattern) + res := result.new("Cloudtrail has no non-MFA login alarm", trail) +} diff --git a/checks/cloud/aws/cloudwatch/require_non_mfa_login_alarm_test.go b/checks/cloud/aws/cloudwatch/require_non_mfa_login_alarm_test.go deleted file mode 100644 index accb00a9..00000000 --- a/checks/cloud/aws/cloudwatch/require_non_mfa_login_alarm_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package cloudwatch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/stretchr/testify/assert" -) - -func TestCheckRequireNonMFALoginAlarm(t *testing.T) { - tests := []struct { - name string - cloudtrail cloudtrail.CloudTrail - cloudwatch cloudwatch.CloudWatch - expected bool - }{ - { - name: "Multi-region CloudTrail alarms on Non-MFA login", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - Metadata: trivyTypes.NewTestMetadata(), - FilterName: trivyTypes.String("NonMFALogin", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String(`($.eventName = "ConsoleLogin") && -($.additionalEventData.MFAUsed != "Yes") && -($.userIdentity.type=="IAMUser") && -($.responseElements.ConsoleLogin == "Success")`, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("NonMFALogin", trivyTypes.NewTestMetadata()), - MetricName: trivyTypes.String("NonMFALogin", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - { - Metadata: trivyTypes.NewTestMetadata(), - ID: trivyTypes.String("NonMFALogin", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Multi-region CloudTrail has no filter for Unauthorized API calls", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{}, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("CloudTrail_Unauthorized_API_Call", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - {}, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudWatch = test.cloudwatch - testState.AWS.CloudTrail = test.cloudtrail - results := requireNonMFALoginAlarm.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == requireNonMFALoginAlarm.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudwatch/require_org_changes_alarm.go b/checks/cloud/aws/cloudwatch/require_org_changes_alarm.go index e3b9b277..df48678d 100644 --- a/checks/cloud/aws/cloudwatch/require_org_changes_alarm.go +++ b/checks/cloud/aws/cloudwatch/require_org_changes_alarm.go @@ -35,7 +35,8 @@ rolled back. Links: []string{ "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_security_incident-response.html", }, - Severity: severity.Low, + Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, trail := range s.AWS.CloudTrail.MultiRegionTrails() { diff --git a/checks/cloud/aws/cloudwatch/require_org_changes_alarm.rego b/checks/cloud/aws/cloudwatch/require_org_changes_alarm.rego new file mode 100644 index 00000000..294fdcc0 --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_org_changes_alarm.rego @@ -0,0 +1,55 @@ +# METADATA +# title: Ensure a log metric filter and alarm exist for organisation changes +# description: | +# Monitoring AWS Organizations changes can help you prevent any unwanted, accidental or +# intentional modifications that may lead to unauthorized access or other security breaches. +# This monitoring technique helps you to ensure that any unexpected changes performed +# within your AWS Organizations can be investigated and any unwanted changes can be +# rolled back. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/organizations/latest/userguide/orgs_security_incident-response.html +# custom: +# id: AVD-AWS-0174 +# avd_id: AVD-AWS-0174 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: require-org-changes-alarm +# recommended_action: Create an alarm to alert on organisation changes +# frameworks: +# cis-aws-1.4: +# - "4.15" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +package builtin.aws.cloudwatch.aws0174 + +import rego.v1 + +import data.lib.aws.trails + +deny contains res if { + some trail in trails.trails_without_filter("$.eventSource = organizations.amazonaws.com") + res := result.new("Cloudwatch has no organisation changes log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter("$.eventSource = organizations.amazonaws.com") + res := result.new("Cloudtrail has no network gateway change alarm", trail) +} + +deny contains res if { + some trail in trails.trails_without_filter(`$.eventSource = "organizations.amazonaws.com"`) + res := result.new("Cloudwatch has no organisation changes log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter(`$.eventSource = "organizations.amazonaws.com"`) + res := result.new("Cloudtrail has no network gateway change alarm", trail) +} diff --git a/checks/cloud/aws/cloudwatch/require_org_changes_alarm_test.go b/checks/cloud/aws/cloudwatch/require_org_changes_alarm_test.go deleted file mode 100644 index b02f1213..00000000 --- a/checks/cloud/aws/cloudwatch/require_org_changes_alarm_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package cloudwatch - -import ( - "testing" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/stretchr/testify/assert" -) - -func TestCheckRequireOrgChangesAlarm(t *testing.T) { - tests := []struct { - name string - cloudtrail cloudtrail.CloudTrail - cloudwatch cloudwatch.CloudWatch - expected bool - }{ - { - name: "alarm exists", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - FilterName: trivyTypes.String("OrganizationEvents", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String("{ $.eventSource = \"organizations.amazonaws.com\" }", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - MetricName: trivyTypes.String("OrganizationEvents", trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - { - name: "metric filter does not exist", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "alarm does not exist", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - FilterName: trivyTypes.String("OrganizationEvents", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String("{ $.eventSource = \"organizations.amazonaws.com\" }", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudTrail = test.cloudtrail - testState.AWS.CloudWatch = test.cloudwatch - results := CheckRequireOrgChangesAlarm.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckRequireOrgChangesAlarm.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudwatch/require_root_user_usage_alarm.go b/checks/cloud/aws/cloudwatch/require_root_user_usage_alarm.go index 3fbd0ec2..6cefaa84 100644 --- a/checks/cloud/aws/cloudwatch/require_root_user_usage_alarm.go +++ b/checks/cloud/aws/cloudwatch/require_root_user_usage_alarm.go @@ -37,6 +37,7 @@ CIS recommends that you create a metric filter and alarm for root user login att Terraform: &scan.EngineMetadata{}, CloudFormation: &scan.EngineMetadata{}, Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { diff --git a/checks/cloud/aws/cloudwatch/require_root_user_usage_alarm.rego b/checks/cloud/aws/cloudwatch/require_root_user_usage_alarm.rego new file mode 100644 index 00000000..7729258a --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_root_user_usage_alarm.rego @@ -0,0 +1,46 @@ +# METADATA +# title: Ensure a log metric filter and alarm exist for usage of root user +# description: | +# You can do real-time monitoring of API calls directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +# CIS recommends that you create a metric filter and alarm for root user login attempts. Monitoring for root user logins provides visibility into the use of a fully privileged account and an opportunity to reduce the use of it. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://aws.amazon.com/iam/features/mfa/ +# custom: +# id: AVD-AWS-0149 +# avd_id: AVD-AWS-0149 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: require-root-user-usage-alarm +# recommended_action: Create an alarm to alert on root user login +# frameworks: +# cis-aws-1.2: +# - "3.3" +# cis-aws-1.4: +# - "4.3" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +package builtin.aws.cloudwatch.aws0149 + +import rego.v1 + +import data.lib.aws.trails + +pattern := `$.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && &.eventType != "AwsServiceEvent"` + +deny contains res if { + some trail in trails.trails_without_filter(pattern) + res := result.new("Cloudtrail has no root user usage log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter(pattern) + res := result.new("Cloudtrail has no root user usage alarm", trail) +} diff --git a/checks/cloud/aws/cloudwatch/require_root_user_usage_alarm_test.go b/checks/cloud/aws/cloudwatch/require_root_user_usage_alarm_test.go deleted file mode 100644 index 0ed7919d..00000000 --- a/checks/cloud/aws/cloudwatch/require_root_user_usage_alarm_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package cloudwatch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/stretchr/testify/assert" -) - -func TestCheckRequireRootUserUsageAlarm(t *testing.T) { - tests := []struct { - name string - cloudtrail cloudtrail.CloudTrail - cloudwatch cloudwatch.CloudWatch - expected bool - }{ - { - name: "Multi-region CloudTrail alarms on Non-MFA login", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - Metadata: trivyTypes.NewTestMetadata(), - FilterName: trivyTypes.String("RootUserUsage", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String(`$.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && &.eventType != "AwsServiceEvent"`, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("RootUserUsage", trivyTypes.NewTestMetadata()), - MetricName: trivyTypes.String("RootUserUsage", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - { - Metadata: trivyTypes.NewTestMetadata(), - ID: trivyTypes.String("RootUserUsage", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Multi-region CloudTrail alarms on Non-MFA login", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{}, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("RootUserUsage", trivyTypes.NewTestMetadata()), - MetricName: trivyTypes.String("RootUserUsage", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - { - Metadata: trivyTypes.NewTestMetadata(), - ID: trivyTypes.String("RootUserUsage", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudWatch = test.cloudwatch - testState.AWS.CloudTrail = test.cloudtrail - results := requireRootUserUsageAlarm.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == requireRootUserUsageAlarm.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudwatch/require_route_table_change_alarm.go b/checks/cloud/aws/cloudwatch/require_route_table_change_alarm.go index 56f1a104..7cfc115b 100644 --- a/checks/cloud/aws/cloudwatch/require_route_table_change_alarm.go +++ b/checks/cloud/aws/cloudwatch/require_route_table_change_alarm.go @@ -38,6 +38,7 @@ CIS recommends that you create a metric filter and alarm for changes to route ta Terraform: &scan.EngineMetadata{}, CloudFormation: &scan.EngineMetadata{}, Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { diff --git a/checks/cloud/aws/cloudwatch/require_route_table_change_alarm.rego b/checks/cloud/aws/cloudwatch/require_route_table_change_alarm.rego new file mode 100644 index 00000000..5b05f025 --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_route_table_change_alarm.rego @@ -0,0 +1,47 @@ +# METADATA +# title: Ensure a log metric filter and alarm exist for route table changes +# description: | +# You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +# Routing tables route network traffic between subnets and to network gateways. +# CIS recommends that you create a metric filter and alarm for changes to route tables. Monitoring these changes helps ensure that all VPC traffic flows through an expected path. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html +# custom: +# id: AVD-AWS-0159 +# avd_id: AVD-AWS-0159 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: require-network-gateway-changes-alarm +# recommended_action: Create an alarm to alert on route table changes +# frameworks: +# cis-aws-1.2: +# - "3.13" +# cis-aws-1.4: +# - "4.13" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +package builtin.aws.cloudwatch.aws0159 + +import rego.v1 + +import data.lib.aws.trails + +pattern := `{($.eventName=CreateRoute) || ($.eventName=CreateRouteTable) || ($.eventName=ReplaceRoute) || ($.eventName=ReplaceRouteTableAssociation) || ($.eventName=DeleteRouteTable) || ($.eventName=DeleteRoute) || ($.eventName=DisassociateRouteTable)}` + +deny contains res if { + some trail in trails.trails_without_filter(pattern) + res := result.new("Cloudtrail has no route table change log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter(pattern) + res := result.new("Cloudtrail has no route table change alarm", trail) +} diff --git a/checks/cloud/aws/cloudwatch/require_route_table_change_alarm_test.go b/checks/cloud/aws/cloudwatch/require_route_table_change_alarm_test.go deleted file mode 100644 index 00c94701..00000000 --- a/checks/cloud/aws/cloudwatch/require_route_table_change_alarm_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package cloudwatch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/stretchr/testify/assert" -) - -func TestCheckRouteTableChangeAlarm(t *testing.T) { - tests := []struct { - name string - cloudtrail cloudtrail.CloudTrail - cloudwatch cloudwatch.CloudWatch - expected bool - }{ - { - name: "Multi-region CloudTrail alarms on route table changes", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - Metadata: trivyTypes.NewTestMetadata(), - FilterName: trivyTypes.String("RouteTableChange", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String(`{($.eventName=CreateRoute) || - ($.eventName=CreateRouteTable) || ($.eventName=ReplaceRoute) || - ($.eventName=ReplaceRouteTableAssociation) || ($.eventName=DeleteRouteTable) || - ($.eventName=DeleteRoute) || ($.eventName=DisassociateRouteTable)}`, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("RouteTableChange", trivyTypes.NewTestMetadata()), - MetricName: trivyTypes.String("RouteTableChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - { - Metadata: trivyTypes.NewTestMetadata(), - ID: trivyTypes.String("RouteTableChange", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Multi-region CloudTrail has no filter for route table changes", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{}, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("RouteTableChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - {}, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudWatch = test.cloudwatch - testState.AWS.CloudTrail = test.cloudtrail - results := requireRouteTableChangeAlarm.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == requireRouteTableChangeAlarm.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudwatch/require_s3_bucket_policy_change_alarm.go b/checks/cloud/aws/cloudwatch/require_s3_bucket_policy_change_alarm.go index f0fc516c..94186e10 100644 --- a/checks/cloud/aws/cloudwatch/require_s3_bucket_policy_change_alarm.go +++ b/checks/cloud/aws/cloudwatch/require_s3_bucket_policy_change_alarm.go @@ -37,6 +37,7 @@ CIS recommends that you create a metric filter and alarm for changes to S3 bucke Terraform: &scan.EngineMetadata{}, CloudFormation: &scan.EngineMetadata{}, Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { diff --git a/checks/cloud/aws/cloudwatch/require_s3_bucket_policy_change_alarm.rego b/checks/cloud/aws/cloudwatch/require_s3_bucket_policy_change_alarm.rego new file mode 100644 index 00000000..5530b4a4 --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_s3_bucket_policy_change_alarm.rego @@ -0,0 +1,46 @@ +# METADATA +# title: Ensure a log metric filter and alarm exist for S3 bucket policy changes +# description: | +# You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +# CIS recommends that you create a metric filter and alarm for changes to S3 bucket policies. Monitoring these changes might reduce time to detect and correct permissive policies on sensitive S3 buckets. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html +# custom: +# id: AVD-AWS-0154 +# avd_id: AVD-AWS-0154 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: require-s3-bucket-policy-change-alarm +# recommended_action: Create an alarm to alert on S3 Bucket policy changes +# frameworks: +# cis-aws-1.2: +# - "3.8" +# cis-aws-1.4: +# - "4.8" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +package builtin.aws.cloudwatch.aws0154 + +import rego.v1 + +import data.lib.aws.trails + +pattern := `{($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl) || ($.eventName=PutBucketPolicy) || ($.eventName=PutBucketCors) || ($.eventName=PutBucketLifecycle) || ($.eventName=PutBucketReplication) || ($.eventName=DeleteBucketPolicy) || ($.eventName=DeleteBucketCors) || ($.eventName=DeleteBucketLifecycle) || ($.eventName=DeleteBucketReplication))}` + +deny contains res if { + some trail in trails.trails_without_filter(pattern) + res := result.new("Cloudtrail has no S3 bucket policy change log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter(pattern) + res := result.new("Cloudtrail has no S3 bucket policy change alarm", trail) +} diff --git a/checks/cloud/aws/cloudwatch/require_s3_bucket_policy_change_alarm_test.go b/checks/cloud/aws/cloudwatch/require_s3_bucket_policy_change_alarm_test.go deleted file mode 100644 index 6708270d..00000000 --- a/checks/cloud/aws/cloudwatch/require_s3_bucket_policy_change_alarm_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package cloudwatch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/stretchr/testify/assert" -) - -func TestCheckRequireS3BucketPolicyChangeAlarm(t *testing.T) { - tests := []struct { - name string - cloudtrail cloudtrail.CloudTrail - cloudwatch cloudwatch.CloudWatch - expected bool - }{ - { - name: "Multi-region CloudTrail alarms on S3 bucket policy change", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - Metadata: trivyTypes.NewTestMetadata(), - FilterName: trivyTypes.String("BucketPolicyChange", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String(`{($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl) || - ($.eventName=PutBucketPolicy) || ($.eventName=PutBucketCors) || ($.eventName=PutBucketLifecycle) || - ($.eventName=PutBucketReplication) || ($.eventName=DeleteBucketPolicy) || ($.eventName=DeleteBucketCors) || - ($.eventName=DeleteBucketLifecycle) || ($.eventName=DeleteBucketReplication))}`, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("BucketPolicyChange", trivyTypes.NewTestMetadata()), - MetricName: trivyTypes.String("BucketPolicyChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - { - Metadata: trivyTypes.NewTestMetadata(), - ID: trivyTypes.String("BucketPolicyChange", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Multi-region CloudTrail has no filter for S3 bucket policy change", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{}, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("BucketPolicyChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - {}, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudWatch = test.cloudwatch - testState.AWS.CloudTrail = test.cloudtrail - results := requireS3BucketPolicyChangeAlarm.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == requireS3BucketPolicyChangeAlarm.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudwatch/require_security_group_change_alarm.go b/checks/cloud/aws/cloudwatch/require_security_group_change_alarm.go index e93c509f..98d2d5b3 100644 --- a/checks/cloud/aws/cloudwatch/require_security_group_change_alarm.go +++ b/checks/cloud/aws/cloudwatch/require_security_group_change_alarm.go @@ -38,6 +38,7 @@ CIS recommends that you create a metric filter and alarm for changes to security Terraform: &scan.EngineMetadata{}, CloudFormation: &scan.EngineMetadata{}, Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { diff --git a/checks/cloud/aws/cloudwatch/require_security_group_change_alarm.rego b/checks/cloud/aws/cloudwatch/require_security_group_change_alarm.rego new file mode 100644 index 00000000..9a72a884 --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_security_group_change_alarm.rego @@ -0,0 +1,47 @@ +# METADATA +# title: Ensure a log metric filter and alarm exist for security group changes +# description: | +# You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +# Security groups are a stateful packet filter that controls ingress and egress traffic in a VPC. +# CIS recommends that you create a metric filter and alarm for changes to security groups. Monitoring these changes helps ensure that resources and services aren't unintentionally exposed. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html +# custom: +# id: AVD-AWS-0156 +# avd_id: AVD-AWS-0156 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: require-sg-change-alarms +# recommended_action: Create an alarm to alert on security group changes +# frameworks: +# cis-aws-1.2: +# - "3.10" +# cis-aws-1.4: +# - "4.10" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +package builtin.aws.cloudwatch.aws0156 + +import rego.v1 + +import data.lib.aws.trails + +pattern := `{($.eventName=AuthorizeSecurityGroupIngress) || ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || ($.eventName=DeleteSecurityGroup)}` + +deny contains res if { + some trail in trails.trails_without_filter(pattern) + res := result.new("Cloudtrail has no Security Group change log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter(pattern) + res := result.new("Cloudtrail has no Security Group change alarm", trail) +} diff --git a/checks/cloud/aws/cloudwatch/require_security_group_change_alarm_test.go b/checks/cloud/aws/cloudwatch/require_security_group_change_alarm_test.go deleted file mode 100644 index 661d2d70..00000000 --- a/checks/cloud/aws/cloudwatch/require_security_group_change_alarm_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package cloudwatch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/stretchr/testify/assert" -) - -func TestCheckSecurityGroupChangeAlarm(t *testing.T) { - tests := []struct { - name string - cloudtrail cloudtrail.CloudTrail - cloudwatch cloudwatch.CloudWatch - expected bool - }{ - { - name: "Multi-region CloudTrail alarms on security group changes", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - Metadata: trivyTypes.NewTestMetadata(), - FilterName: trivyTypes.String("SecurityGroupChange", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String(`{($.eventName=AuthorizeSecurityGroupIngress) || - ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || - ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || - ($.eventName=DeleteSecurityGroup)}`, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("SecurityGroupChange", trivyTypes.NewTestMetadata()), - MetricName: trivyTypes.String("SecurityGroupChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - { - Metadata: trivyTypes.NewTestMetadata(), - ID: trivyTypes.String("SecurityGroupChange", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Multi-region CloudTrail has no filter for security group changes", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{}, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("SecurityGroupChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - {}, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudWatch = test.cloudwatch - testState.AWS.CloudTrail = test.cloudtrail - results := requireSecurityGroupChangeAlarm.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == requireSecurityGroupChangeAlarm.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudwatch/require_unauthorised_api_call_alarm.go b/checks/cloud/aws/cloudwatch/require_unauthorised_api_call_alarm.go index db55fb31..eee04afc 100644 --- a/checks/cloud/aws/cloudwatch/require_unauthorised_api_call_alarm.go +++ b/checks/cloud/aws/cloudwatch/require_unauthorised_api_call_alarm.go @@ -37,6 +37,7 @@ CIS recommends that you create a metric filter and alarm for changes to VPCs. Mo Terraform: &scan.EngineMetadata{}, CloudFormation: &scan.EngineMetadata{}, Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { diff --git a/checks/cloud/aws/cloudwatch/require_unauthorised_api_call_alarm.rego b/checks/cloud/aws/cloudwatch/require_unauthorised_api_call_alarm.rego new file mode 100644 index 00000000..8cf3e007 --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_unauthorised_api_call_alarm.rego @@ -0,0 +1,46 @@ +# METADATA +# title: Ensure a log metric filter and alarm exist for unauthorized API calls +# description: | +# You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. You can have more than one VPC in an account, and you can create a peer connection between two VPCs, enabling network traffic to route between VPCs. +# CIS recommends that you create a metric filter and alarm for changes to VPCs. Monitoring these changes helps ensure that authentication and authorization controls remain intact. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html +# custom: +# id: AVD-AWS-0147 +# avd_id: AVD-AWS-0147 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: require-unauthorised-api-call-alarm +# recommended_action: Create an alarm to alert on unauthorized API calls +# frameworks: +# cis-aws-1.2: +# - "3.1" +# cis-aws-1.4: +# - "4.1" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +package builtin.aws.cloudwatch.aws0147 + +import rego.v1 + +import data.lib.aws.trails + +pattern := `($.errorCode = "*UnauthorizedOperation") || ($.errorCode = "AccessDenied*")` + +deny contains res if { + some trail in trails.trails_without_filter(pattern) + res := result.new("Cloudtrail has no unauthorized API log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter(pattern) + res := result.new("Cloudtrail has no unauthorized API alarm", trail) +} diff --git a/checks/cloud/aws/cloudwatch/require_unauthorised_api_call_alarm_test.go b/checks/cloud/aws/cloudwatch/require_unauthorised_api_call_alarm_test.go deleted file mode 100644 index 40f6f7a2..00000000 --- a/checks/cloud/aws/cloudwatch/require_unauthorised_api_call_alarm_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package cloudwatch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/stretchr/testify/assert" -) - -func TestCheckRequireUnauthorisedApiCallAlarm(t *testing.T) { - tests := []struct { - name string - cloudtrail cloudtrail.CloudTrail - cloudwatch cloudwatch.CloudWatch - expected bool - }{ - { - name: "Multi-region CloudTrail alarms on Unauthorized API calls", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - Metadata: trivyTypes.NewTestMetadata(), - FilterName: trivyTypes.String("UnauthorizedAPIUsage", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String(`($.errorCode = "*UnauthorizedOperation") || ($.errorCode = "AccessDenied*")`, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("CloudTrail_Unauthorized_API_Call", trivyTypes.NewTestMetadata()), - MetricName: trivyTypes.String("UnauthorizedAPIUsage", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - { - Metadata: trivyTypes.NewTestMetadata(), - ID: trivyTypes.String("UnauthorizedAPIUsage", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Multi-region CloudTrail has no filter for Unauthorized API calls", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{}, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("CloudTrail_Unauthorized_API_Call", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - {}, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudWatch = test.cloudwatch - testState.AWS.CloudTrail = test.cloudtrail - results := requireUnauthorizedApiCallAlarm.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == requireUnauthorizedApiCallAlarm.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/aws/cloudwatch/require_vpc_change_alarm.rego b/checks/cloud/aws/cloudwatch/require_vpc_change_alarm.rego new file mode 100644 index 00000000..b83015db --- /dev/null +++ b/checks/cloud/aws/cloudwatch/require_vpc_change_alarm.rego @@ -0,0 +1,47 @@ +# METADATA +# title: Ensure a log metric filter and alarm exist for VPC changes +# description: | +# You can do real-time monitoring of API calls by directing CloudTrail logs to CloudWatch Logs and establishing corresponding metric filters and alarms. +# You can have more than one VPC in an account, and you can create a peer connection between two VPCs, enabling network traffic to route between VPCs. +# CIS recommends that you create a metric filter and alarm for changes to VPCs. Monitoring these changes helps ensure that authentication and authorization controls remain intact. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html +# custom: +# id: AVD-AWS-0160 +# avd_id: AVD-AWS-0160 +# provider: aws +# service: cloudwatch +# severity: LOW +# short_code: require-vpc-changes-alarm +# recommended_action: Create an alarm to alert on route table changes +# frameworks: +# cis-aws-1.2: +# - "3.14" +# cis-aws-1.4: +# - "4.14" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: cloudwatch +# provider: aws +package builtin.aws.cloudwatch.aws0160 + +import rego.v1 + +import data.lib.aws.trails + +pattern := `{($.eventName=CreateVpc) || ($.eventName=DeleteVpc) || ($.eventName=ModifyVpcAttribute) || ($.eventName=AcceptVpcPeeringConnection) || ($.eventName=CreateVpcPeeringConnection) || ($.eventName=DeleteVpcPeeringConnection) || ($.eventName=RejectVpcPeeringConnection) || ($.eventName=AttachClassicLinkVpc) || ($.eventName=DetachClassicLinkVpc) || ($.eventName=DisableVpcClassicLink) || ($.eventName=EnableVpcClassicLink)}` + +deny contains res if { + some trail in trails.trails_without_filter(pattern) + res := result.new("Cloudtrail has no vpc change log filter", trail) +} + +deny contains res if { + some trail in trails.trails_without_alarm_for_filter(pattern) + res := result.new("Cloudtrail has no vpc change alarm", trail) +} diff --git a/checks/cloud/aws/cloudwatch/require_vpc_change_alarm_test.go b/checks/cloud/aws/cloudwatch/require_vpc_change_alarm_test.go deleted file mode 100644 index 3b55db4b..00000000 --- a/checks/cloud/aws/cloudwatch/require_vpc_change_alarm_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package cloudwatch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/stretchr/testify/assert" -) - -func TestCheckVPCChangeAlarm(t *testing.T) { - tests := []struct { - name string - cloudtrail cloudtrail.CloudTrail - cloudwatch cloudwatch.CloudWatch - expected bool - }{ - { - name: "Multi-region CloudTrail alarms on VPC changes", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{ - { - Metadata: trivyTypes.NewTestMetadata(), - FilterName: trivyTypes.String("VPCChange", trivyTypes.NewTestMetadata()), - FilterPattern: trivyTypes.String(`{($.eventName=CreateVpc) || - ($.eventName=DeleteVpc) || ($.eventName=ModifyVpcAttribute) || - ($.eventName=AcceptVpcPeeringConnection) || ($.eventName=CreateVpcPeeringConnection) || - ($.eventName=DeleteVpcPeeringConnection) || ($.eventName=RejectVpcPeeringConnection) || - ($.eventName=AttachClassicLinkVpc) || ($.eventName=DetachClassicLinkVpc) || - ($.eventName=DisableVpcClassicLink) || ($.eventName=EnableVpcClassicLink)}`, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("VPCChange", trivyTypes.NewTestMetadata()), - MetricName: trivyTypes.String("VPCChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - { - Metadata: trivyTypes.NewTestMetadata(), - ID: trivyTypes.String("VPCChange", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Multi-region CloudTrail has no filter for VPC changes", - cloudtrail: cloudtrail.CloudTrail{ - Trails: []cloudtrail.Trail{ - { - Metadata: trivyTypes.NewTestMetadata(), - CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - cloudwatch: cloudwatch.CloudWatch{ - LogGroups: []cloudwatch.LogGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), - MetricFilters: []cloudwatch.MetricFilter{}, - }, - }, - Alarms: []cloudwatch.Alarm{ - { - Metadata: trivyTypes.NewTestMetadata(), - AlarmName: trivyTypes.String("VPCChange", trivyTypes.NewTestMetadata()), - Metrics: []cloudwatch.MetricDataQuery{ - {}, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.CloudWatch = test.cloudwatch - testState.AWS.CloudTrail = test.cloudtrail - results := requireVPCChangeAlarm.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == requireVPCChangeAlarm.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/lib/cloud/aws_trails.rego b/lib/cloud/aws_trails.rego new file mode 100644 index 00000000..1ae30252 --- /dev/null +++ b/lib/cloud/aws_trails.rego @@ -0,0 +1,45 @@ +package lib.aws.trails + +import rego.v1 + +multiregion_log_trails := [ +trail | + some trail in input.aws.cloudtrail.trails + trail.ismultiregion.value + trail.islogging.value +] + +trails_without_filter(pattern) := trails if { + p := _cleanup_pattern(pattern) + trails := [ + trail | + some trail in multiregion_log_trails + loggroup := _has_loggroup_for_trail(trail) + not _has_log_filter(loggroup, pattern) + ] +} + +trails_without_alarm_for_filter(pattern) := [ +trail | + some trail in multiregion_log_trails + loggroup := _has_loggroup_for_trail(trail) + filter := _has_log_filter(loggroup, pattern) + not _has_alarm_for_filter(filter) +] + +_has_alarm_for_filter(filter) if { + some alarm in input.aws.cloudwatch.alarms + alarm.metricname.value == filter.filtername.value +} + +_has_loggroup_for_trail(trail) := loggroup if { + some loggroup in input.aws.cloudwatch.loggroups + loggroup.arn.value == trail.cloudwatchlogsloggrouparn.value +} + +_has_log_filter(loggroup, pattern) := filter if { + some filter in loggroup.metricfilters + _cleanup_pattern(filter.filterpattern.value) == pattern +} + +_cleanup_pattern(pattern) := replace(replace(pattern, " ", ""), "\n", "") diff --git a/test/rego/aws_apigateway_test.go b/test/rego/aws_apigateway_test.go new file mode 100644 index 00000000..0c408776 --- /dev/null +++ b/test/rego/aws_apigateway_test.go @@ -0,0 +1,309 @@ +package test + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway" + v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" + "github.com/aquasecurity/trivy/pkg/iac/state" + trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func init() { + addTests(awsApigatewayTestCases) +} + +var awsApigatewayTestCases = testCases{ + "AVD-AWS-0001": { + { + name: "API Gateway stage with no log group ARN", + input: state.State{AWS: aws.AWS{APIGateway: apigateway.APIGateway{V1: v1.APIGateway{ + APIs: []v1.API{ + { + Metadata: trivyTypes.NewTestMetadata(), + Stages: []v1.Stage{ + { + Metadata: trivyTypes.NewTestMetadata(), + AccessLogging: v1.AccessLogging{ + Metadata: trivyTypes.NewTestMetadata(), + CloudwatchLogGroupARN: trivyTypes.String("", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}}}, + expected: true, + }, + { + name: "API Gateway stage with log group ARN", + input: state.State{AWS: aws.AWS{APIGateway: apigateway.APIGateway{V1: v1.APIGateway{ + APIs: []v1.API{ + { + Metadata: trivyTypes.NewTestMetadata(), + Stages: []v1.Stage{ + { + Metadata: trivyTypes.NewTestMetadata(), + AccessLogging: v1.AccessLogging{ + Metadata: trivyTypes.NewTestMetadata(), + CloudwatchLogGroupARN: trivyTypes.String("log-group-arn", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}}}, + expected: false, + }, + }, + "AVD-AWS-0002": { + { + name: "API Gateway stage with unencrypted cache", + input: state.State{AWS: aws.AWS{APIGateway: apigateway.APIGateway{V1: v1.APIGateway{ + APIs: []v1.API{ + { + Metadata: trivyTypes.NewTestMetadata(), + Stages: []v1.Stage{ + { + Metadata: trivyTypes.NewTestMetadata(), + RESTMethodSettings: []v1.RESTMethodSettings{ + { + Metadata: trivyTypes.NewTestMetadata(), + CacheDataEncrypted: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + CacheEnabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }}}}, + expected: true, + }, + { + name: "API Gateway stage with encrypted cache", + input: state.State{AWS: aws.AWS{APIGateway: apigateway.APIGateway{V1: v1.APIGateway{ + APIs: []v1.API{ + { + Metadata: trivyTypes.NewTestMetadata(), + Stages: []v1.Stage{ + { + Metadata: trivyTypes.NewTestMetadata(), + RESTMethodSettings: []v1.RESTMethodSettings{ + { + Metadata: trivyTypes.NewTestMetadata(), + CacheDataEncrypted: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + CacheEnabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }}}}, + expected: false, + }, + { + name: "API Gateway stage with caching disabled", + input: state.State{AWS: aws.AWS{APIGateway: apigateway.APIGateway{V1: v1.APIGateway{ + APIs: []v1.API{ + { + Metadata: trivyTypes.NewTestMetadata(), + Stages: []v1.Stage{ + { + Metadata: trivyTypes.NewTestMetadata(), + RESTMethodSettings: []v1.RESTMethodSettings{ + { + Metadata: trivyTypes.NewTestMetadata(), + CacheDataEncrypted: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + CacheEnabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }}}}, + expected: false, + }, + }, + "AVD-AWS-0003": { + { + name: "API Gateway stage with X-Ray tracing disabled", + input: state.State{AWS: aws.AWS{APIGateway: apigateway.APIGateway{V1: v1.APIGateway{ + APIs: []v1.API{ + { + Metadata: trivyTypes.NewTestMetadata(), + Stages: []v1.Stage{ + { + Metadata: trivyTypes.NewTestMetadata(), + XRayTracingEnabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}}, + expected: true, + }, + { + name: "API Gateway stage with X-Ray tracing enabled", + input: state.State{AWS: aws.AWS{APIGateway: apigateway.APIGateway{V1: v1.APIGateway{ + APIs: []v1.API{ + { + Metadata: trivyTypes.NewTestMetadata(), + Stages: []v1.Stage{ + { + Metadata: trivyTypes.NewTestMetadata(), + XRayTracingEnabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}}, + expected: false, + }, + }, + "AVD-AWS-0004": { + { + name: "API GET method without authorization", + input: state.State{AWS: aws.AWS{APIGateway: apigateway.APIGateway{V1: v1.APIGateway{ + APIs: []v1.API{ + { + Metadata: trivyTypes.NewTestMetadata(), + Resources: []v1.Resource{ + { + Methods: []v1.Method{ + { + Metadata: trivyTypes.NewTestMetadata(), + HTTPMethod: trivyTypes.String("GET", trivyTypes.NewTestMetadata()), + APIKeyRequired: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + AuthorizationType: trivyTypes.String(v1.AuthorizationNone, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }}}}, + expected: true, + }, + { + name: "API OPTION method without authorization", + input: state.State{AWS: aws.AWS{APIGateway: apigateway.APIGateway{V1: v1.APIGateway{ + APIs: []v1.API{ + { + Metadata: trivyTypes.NewTestMetadata(), + Resources: []v1.Resource{ + { + Methods: []v1.Method{ + { + Metadata: trivyTypes.NewTestMetadata(), + HTTPMethod: trivyTypes.String("OPTION", trivyTypes.NewTestMetadata()), + APIKeyRequired: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + AuthorizationType: trivyTypes.String(v1.AuthorizationNone, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }}}}, + expected: false, + }, + { + name: "API GET method with IAM authorization", + input: state.State{AWS: aws.AWS{APIGateway: apigateway.APIGateway{V1: v1.APIGateway{ + APIs: []v1.API{ + { + Metadata: trivyTypes.NewTestMetadata(), + Resources: []v1.Resource{ + { + Methods: []v1.Method{ + { + Metadata: trivyTypes.NewTestMetadata(), + HTTPMethod: trivyTypes.String("GET", trivyTypes.NewTestMetadata()), + APIKeyRequired: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + AuthorizationType: trivyTypes.String(v1.AuthorizationIAM, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }}}}, + expected: false, + }, + }, + "AVD-AWS-0005": { + { + name: "API Gateway domain name with TLS version 1.0", + input: state.State{AWS: aws.AWS{APIGateway: apigateway.APIGateway{V1: v1.APIGateway{ + DomainNames: []v1.DomainName{ + { + Metadata: trivyTypes.NewTestMetadata(), + SecurityPolicy: trivyTypes.String("TLS_1_0", trivyTypes.NewTestMetadata()), + }, + }, + }}}}, + expected: true, + }, + { + name: "API Gateway domain name with TLS version 1.2", + input: state.State{AWS: aws.AWS{APIGateway: apigateway.APIGateway{V1: v1.APIGateway{ + DomainNames: []v1.DomainName{ + { + Metadata: trivyTypes.NewTestMetadata(), + SecurityPolicy: trivyTypes.String("TLS_1_2", trivyTypes.NewTestMetadata()), + }, + }, + }}}}, + expected: false, + }, + }, + "AVD-AWS-0190": { + { + name: "API Gateway stage with caching disabled", + input: state.State{AWS: aws.AWS{APIGateway: apigateway.APIGateway{V1: v1.APIGateway{ + APIs: []v1.API{ + { + Metadata: trivyTypes.NewTestMetadata(), + Stages: []v1.Stage{ + { + Metadata: trivyTypes.NewTestMetadata(), + RESTMethodSettings: []v1.RESTMethodSettings{ + { + Metadata: trivyTypes.NewTestMetadata(), + CacheEnabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }}}}, + expected: true, + }, + + { + name: "API Gateway stage with caching enabled", + input: state.State{AWS: aws.AWS{APIGateway: apigateway.APIGateway{V1: v1.APIGateway{ + APIs: []v1.API{ + { + Metadata: trivyTypes.NewTestMetadata(), + Stages: []v1.Stage{ + { + Metadata: trivyTypes.NewTestMetadata(), + RESTMethodSettings: []v1.RESTMethodSettings{ + { + Metadata: trivyTypes.NewTestMetadata(), + CacheEnabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }}}}, + expected: false, + }, + }, +} diff --git a/test/rego/aws_cloudfront_test.go b/test/rego/aws_cloudfront_test.go new file mode 100644 index 00000000..4f7cad8f --- /dev/null +++ b/test/rego/aws_cloudfront_test.go @@ -0,0 +1,197 @@ +package test + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudfront" + "github.com/aquasecurity/trivy/pkg/iac/state" + trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func init() { + addTests(awsCloudfrontTestCases) +} + +var awsCloudfrontTestCases = testCases{ + "AVD-AWS-0010": { + { + name: "CloudFront distribution missing logging configuration", + input: state.State{AWS: aws.AWS{Cloudfront: cloudfront.Cloudfront{ + Distributions: []cloudfront.Distribution{ + { + Metadata: trivyTypes.NewTestMetadata(), + Logging: cloudfront.Logging{ + Metadata: trivyTypes.NewTestMetadata(), + Bucket: trivyTypes.String("", trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + { + name: "CloudFront distribution with logging configured", + input: state.State{AWS: aws.AWS{Cloudfront: cloudfront.Cloudfront{ + Distributions: []cloudfront.Distribution{ + { + Metadata: trivyTypes.NewTestMetadata(), + Logging: cloudfront.Logging{ + Metadata: trivyTypes.NewTestMetadata(), + Bucket: trivyTypes.String("mylogs.s3.amazonaws.com", trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0011": { + { + name: "CloudFront distribution missing WAF", + input: state.State{AWS: aws.AWS{Cloudfront: cloudfront.Cloudfront{ + Distributions: []cloudfront.Distribution{ + { + Metadata: trivyTypes.NewTestMetadata(), + WAFID: trivyTypes.String("", trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "CloudFront distribution with WAF provided", + input: state.State{AWS: aws.AWS{Cloudfront: cloudfront.Cloudfront{ + Distributions: []cloudfront.Distribution{ + { + Metadata: trivyTypes.NewTestMetadata(), + WAFID: trivyTypes.String("waf_id", trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0012": { + { + name: "CloudFront distribution default cache behaviour with allow all policy", + input: state.State{AWS: aws.AWS{Cloudfront: cloudfront.Cloudfront{ + Distributions: []cloudfront.Distribution{ + { + Metadata: trivyTypes.NewTestMetadata(), + DefaultCacheBehaviour: cloudfront.CacheBehaviour{ + Metadata: trivyTypes.NewTestMetadata(), + ViewerProtocolPolicy: trivyTypes.String(cloudfront.ViewerPolicyProtocolAllowAll, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + { + name: "CloudFront distribution ordered cache behaviour with allow all policy", + input: state.State{AWS: aws.AWS{Cloudfront: cloudfront.Cloudfront{ + Distributions: []cloudfront.Distribution{ + { + Metadata: trivyTypes.NewTestMetadata(), + DefaultCacheBehaviour: cloudfront.CacheBehaviour{ + Metadata: trivyTypes.NewTestMetadata(), + ViewerProtocolPolicy: trivyTypes.String(cloudfront.ViewerPolicyProtocolHTTPSOnly, trivyTypes.NewTestMetadata()), + }, + OrdererCacheBehaviours: []cloudfront.CacheBehaviour{ + { + Metadata: trivyTypes.NewTestMetadata(), + ViewerProtocolPolicy: trivyTypes.String(cloudfront.ViewerPolicyProtocolAllowAll, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: true, + }, + { + name: "CloudFront distribution cache behaviours allowing HTTPS only", + input: state.State{AWS: aws.AWS{Cloudfront: cloudfront.Cloudfront{ + Distributions: []cloudfront.Distribution{ + { + Metadata: trivyTypes.NewTestMetadata(), + DefaultCacheBehaviour: cloudfront.CacheBehaviour{ + Metadata: trivyTypes.NewTestMetadata(), + ViewerProtocolPolicy: trivyTypes.String(cloudfront.ViewerPolicyProtocolHTTPSOnly, trivyTypes.NewTestMetadata()), + }, + OrdererCacheBehaviours: []cloudfront.CacheBehaviour{ + { + Metadata: trivyTypes.NewTestMetadata(), + ViewerProtocolPolicy: trivyTypes.String(cloudfront.ViewerPolicyProtocolHTTPSOnly, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0013": { + { + name: "CloudFront distribution using TLS v1.0", + input: state.State{AWS: aws.AWS{Cloudfront: cloudfront.Cloudfront{ + Distributions: []cloudfront.Distribution{ + { + Metadata: trivyTypes.NewTestMetadata(), + ViewerCertificate: cloudfront.ViewerCertificate{ + Metadata: trivyTypes.NewTestMetadata(), + MinimumProtocolVersion: trivyTypes.String("TLSv1.0", trivyTypes.NewTestMetadata()), + SSLSupportMethod: trivyTypes.String("sni-only", trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + { + name: "CloudFront distribution using TLS v1.2", + input: state.State{AWS: aws.AWS{Cloudfront: cloudfront.Cloudfront{ + Distributions: []cloudfront.Distribution{ + { + Metadata: trivyTypes.NewTestMetadata(), + ViewerCertificate: cloudfront.ViewerCertificate{ + Metadata: trivyTypes.NewTestMetadata(), + MinimumProtocolVersion: trivyTypes.String(cloudfront.ProtocolVersionTLS1_2, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: false, + }, + { + name: "CloudFrontDefaultCertificate is true", + input: state.State{AWS: aws.AWS{Cloudfront: cloudfront.Cloudfront{ + Distributions: []cloudfront.Distribution{ + { + Metadata: trivyTypes.NewTestMetadata(), + ViewerCertificate: cloudfront.ViewerCertificate{ + Metadata: trivyTypes.NewTestMetadata(), + MinimumProtocolVersion: trivyTypes.String("TLSv1.0", trivyTypes.NewTestMetadata()), + CloudfrontDefaultCertificate: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: false, + }, + { + name: "SSLSupportMethod is not `sny-only`", + input: state.State{AWS: aws.AWS{Cloudfront: cloudfront.Cloudfront{ + Distributions: []cloudfront.Distribution{ + { + Metadata: trivyTypes.NewTestMetadata(), + ViewerCertificate: cloudfront.ViewerCertificate{ + Metadata: trivyTypes.NewTestMetadata(), + MinimumProtocolVersion: trivyTypes.String("TLSv1.0", trivyTypes.NewTestMetadata()), + SSLSupportMethod: trivyTypes.String("vip", trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + }, +} diff --git a/test/rego/aws_cloudwatch_test.go b/test/rego/aws_cloudwatch_test.go new file mode 100644 index 00000000..74217acd --- /dev/null +++ b/test/rego/aws_cloudwatch_test.go @@ -0,0 +1,1210 @@ +package test + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" + "github.com/aquasecurity/trivy/pkg/iac/state" + trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func init() { + addTests(awsCloudWatchTestCases) +} + +var awsCloudWatchTestCases = testCases{ + "AVD-AWS-0017": { + { + name: "AWS CloudWatch with unencrypted log group", + input: state.State{AWS: aws.AWS{CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + KMSKeyID: trivyTypes.String("", trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "AWS CloudWatch with encrypted log group", + input: state.State{AWS: aws.AWS{CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + KMSKeyID: trivyTypes.String("some-kms-key", trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0151": { + { + name: "Multi-region CloudTrail alarms on CloudTrail configuration change", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{ + { + Metadata: trivyTypes.NewTestMetadata(), + FilterName: trivyTypes.String("CloudTrailConfigurationChange", trivyTypes.NewTestMetadata()), + FilterPattern: trivyTypes.String(` {($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}`, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("CloudTrailConfigurationChange", trivyTypes.NewTestMetadata()), + MetricName: trivyTypes.String("CloudTrailConfigurationChange", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + { + Metadata: trivyTypes.NewTestMetadata(), + ID: trivyTypes.String("CloudTrailConfigurationChange", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}, + expected: false, + }, + { + name: "Multi-region CloudTrail has no filter for CloudTrail configuration change", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{}, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("CloudTrailConfigurationChange", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + {}, + }, + }, + }, + }, + }}, + expected: true, + }, + }, + "AVD-AWS-0153": { + { + name: "Multi-region CloudTrail alarms on CMK disabled or scheduled deletion", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{ + { + Metadata: trivyTypes.NewTestMetadata(), + FilterName: trivyTypes.String("CMKDisbledOrScheduledDelete", trivyTypes.NewTestMetadata()), + FilterPattern: trivyTypes.String(`{($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion))}`, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("CMKDisbledOrScheduledDelete", trivyTypes.NewTestMetadata()), + MetricName: trivyTypes.String("CMKDisbledOrScheduledDelete", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + { + Metadata: trivyTypes.NewTestMetadata(), + ID: trivyTypes.String("CMKDisbledOrScheduledDelete", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}, + expected: false, + }, + { + name: "Multi-region CloudTrail has no filter for CMK Disabled or scheduled deletion", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{}, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("CMKDisbledOrScheduledDelete", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + {}, + }, + }, + }, + }, + }}, + expected: true, + }, + }, + "AVD-AWS-0155": { + { + name: "Multi-region CloudTrail alarms on Config configuration change", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{ + { + Metadata: trivyTypes.NewTestMetadata(), + FilterName: trivyTypes.String("ConfigConfigurationChange", trivyTypes.NewTestMetadata()), + FilterPattern: trivyTypes.String(`{($.eventSource=config.amazonaws.com) && (($.eventName=StopConfigurationRecorder) || ($.eventName=DeleteDeliveryChannel) || ($.eventName=PutDeliveryChannel) || ($.eventName=PutConfigurationRecorder))}`, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("ConfigConfigurationChange", trivyTypes.NewTestMetadata()), + MetricName: trivyTypes.String("ConfigConfigurationChange", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + { + Metadata: trivyTypes.NewTestMetadata(), + ID: trivyTypes.String("ConfigConfigurationChange", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}, + expected: false, + }, + { + name: "Multi-region CloudTrail has no filter for Config configuration change", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{}, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("ConfigConfigurationChange", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + {}, + }, + }, + }, + }, + }}, + expected: true, + }, + }, + "AVD-AWS-0152": { + { + name: "Multi-region CloudTrail alarms on Console login failure", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{ + { + Metadata: trivyTypes.NewTestMetadata(), + FilterName: trivyTypes.String("ConsoleLoginFailure", trivyTypes.NewTestMetadata()), + FilterPattern: trivyTypes.String(`{($.eventName=ConsoleLogin) && ($.errorMessage="Failed authentication")}`, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("ConsoleLoginFailure", trivyTypes.NewTestMetadata()), + MetricName: trivyTypes.String("ConsoleLoginFailure", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + { + Metadata: trivyTypes.NewTestMetadata(), + ID: trivyTypes.String("ConsoleLoginFailure", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}, + expected: false, + }, + { + name: "Multi-region CloudTrail has no filter for console login failure", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{}, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("ConsoleLoginFailure", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + {}, + }, + }, + }, + }, + }}, + expected: true, + }, + }, + "AVD-AWS-0150": { + { + name: "Multi-region CloudTrail alarms on IAM Policy change", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{ + { + Metadata: trivyTypes.NewTestMetadata(), + FilterName: trivyTypes.String("IAMPolicyChanged", trivyTypes.NewTestMetadata()), + FilterPattern: trivyTypes.String(`{($.eventName=DeleteGroupPolicy) || + ($.eventName=DeleteRolePolicy) || + ($.eventName=DeleteUserPolicy) || + ($.eventName=PutGroupPolicy) || + ($.eventName=PutRolePolicy) || + ($.eventName=PutUserPolicy) || + ($.eventName=CreatePolicy) || + ($.eventName=DeletePolicy) || + ($.eventName=CreatePolicyVersion) || + ($.eventName=DeletePolicyVersion) || + ($.eventName=AttachRolePolicy) || + ($.eventName=DetachRolePolicy) || + ($.eventName=AttachUserPolicy) || + ($.eventName=DetachUserPolicy) || + ($.eventName=AttachGroupPolicy) || + ($.eventName=DetachGroupPolicy)}`, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("IAMPolicyChanged", trivyTypes.NewTestMetadata()), + MetricName: trivyTypes.String("IAMPolicyChanged", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + { + Metadata: trivyTypes.NewTestMetadata(), + ID: trivyTypes.String("IAMPolicyChanged", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}, + expected: false, + }, + { + name: "Multi-region CloudTrail has no filter for IAM Policy change", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{}, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("CloudTrail_Unauthorized_API_Call", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + {}, + }, + }, + }, + }, + }}, + expected: true, + }, + }, + "AVD-AWS-0157": { + { + name: "Multi-region CloudTrail alarms on network acl changes", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{ + { + Metadata: trivyTypes.NewTestMetadata(), + FilterName: trivyTypes.String("NACLChange", trivyTypes.NewTestMetadata()), + FilterPattern: trivyTypes.String(`{($.eventName=CreateNetworkAcl) || + ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || + ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || + ($.eventName=ReplaceNetworkAclAssociation)}`, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("NACLChange", trivyTypes.NewTestMetadata()), + MetricName: trivyTypes.String("NACLChange", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + { + Metadata: trivyTypes.NewTestMetadata(), + ID: trivyTypes.String("NACLChange", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}, + expected: false, + }, + { + name: "Multi-region CloudTrail has no filter for network acl changes", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{}, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("NACLChange", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + {}, + }, + }, + }, + }, + }}, + expected: true, + }, + }, + "AVD-AWS-0158": { + { + name: "Multi-region CloudTrail alarms on network gateway changes", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{ + { + Metadata: trivyTypes.NewTestMetadata(), + FilterName: trivyTypes.String("NetworkGatewayChange", trivyTypes.NewTestMetadata()), + FilterPattern: trivyTypes.String(`{($.eventName=CreateCustomerGateway) || + ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || + ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || + ($.eventName=DetachInternetGateway)}`, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("NetworkGatewayChange", trivyTypes.NewTestMetadata()), + MetricName: trivyTypes.String("NetworkGatewayChange", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + { + Metadata: trivyTypes.NewTestMetadata(), + ID: trivyTypes.String("NetworkGatewayChange", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}, + expected: false, + }, + { + name: "Multi-region CloudTrail has no filter for network gateway changes", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{}, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("NetworkGatewayChange", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + {}, + }, + }, + }, + }, + }}, + expected: true, + }, + }, + "AVD-AWS-0148": { + { + name: "Multi-region CloudTrail alarms on Non-MFA login", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{ + { + Metadata: trivyTypes.NewTestMetadata(), + FilterName: trivyTypes.String("NonMFALogin", trivyTypes.NewTestMetadata()), + FilterPattern: trivyTypes.String(`($.eventName = "ConsoleLogin") && + ($.additionalEventData.MFAUsed != "Yes") && + ($.userIdentity.type=="IAMUser") && + ($.responseElements.ConsoleLogin == "Success")`, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("NonMFALogin", trivyTypes.NewTestMetadata()), + MetricName: trivyTypes.String("NonMFALogin", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + { + Metadata: trivyTypes.NewTestMetadata(), + ID: trivyTypes.String("NonMFALogin", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}, + expected: false, + }, + { + name: "Multi-region CloudTrail has no filter for Unauthorized API calls", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{}, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("CloudTrail_Unauthorized_API_Call", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + {}, + }, + }, + }, + }, + }}, + expected: true, + }, + }, + "AVD-AWS-0174": { + { + name: "alarm exists", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{ + { + FilterName: trivyTypes.String("OrganizationEvents", trivyTypes.NewTestMetadata()), + FilterPattern: trivyTypes.String("{ $.eventSource = \"organizations.amazonaws.com\" }", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + MetricName: trivyTypes.String("OrganizationEvents", trivyTypes.NewTestMetadata()), + }, + }, + }, + }}, + expected: false, + }, + { + name: "metric filter does not exist", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + }, + }, + }, + }}, + expected: true, + }, + { + name: "alarm does not exist", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{ + { + FilterName: trivyTypes.String("OrganizationEvents", trivyTypes.NewTestMetadata()), + FilterPattern: trivyTypes.String("{ $.eventSource = \"organizations.amazonaws.com\" }", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}, + expected: true, + }, + }, + "AVD-AWS-0149": { + { + name: "Multi-region CloudTrail alarms on Non-MFA login", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{ + { + Metadata: trivyTypes.NewTestMetadata(), + FilterName: trivyTypes.String("RootUserUsage", trivyTypes.NewTestMetadata()), + FilterPattern: trivyTypes.String(`$.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && &.eventType != "AwsServiceEvent"`, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("RootUserUsage", trivyTypes.NewTestMetadata()), + MetricName: trivyTypes.String("RootUserUsage", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + { + Metadata: trivyTypes.NewTestMetadata(), + ID: trivyTypes.String("RootUserUsage", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}, + expected: false, + }, + { + name: "Multi-region CloudTrail alarms on Non-MFA login", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{}, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("RootUserUsage", trivyTypes.NewTestMetadata()), + MetricName: trivyTypes.String("RootUserUsage", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + { + Metadata: trivyTypes.NewTestMetadata(), + ID: trivyTypes.String("RootUserUsage", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}, + expected: true, + }, + }, + "AVD-AWS-0154": { + { + name: "Multi-region CloudTrail alarms on S3 bucket policy change", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{ + { + Metadata: trivyTypes.NewTestMetadata(), + FilterName: trivyTypes.String("BucketPolicyChange", trivyTypes.NewTestMetadata()), + FilterPattern: trivyTypes.String(`{($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl) || + ($.eventName=PutBucketPolicy) || ($.eventName=PutBucketCors) || ($.eventName=PutBucketLifecycle) || + ($.eventName=PutBucketReplication) || ($.eventName=DeleteBucketPolicy) || ($.eventName=DeleteBucketCors) || + ($.eventName=DeleteBucketLifecycle) || ($.eventName=DeleteBucketReplication))}`, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("BucketPolicyChange", trivyTypes.NewTestMetadata()), + MetricName: trivyTypes.String("BucketPolicyChange", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + { + Metadata: trivyTypes.NewTestMetadata(), + ID: trivyTypes.String("BucketPolicyChange", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}, + expected: false, + }, + { + name: "Multi-region CloudTrail has no filter for S3 bucket policy change", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{}, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("BucketPolicyChange", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + {}, + }, + }, + }, + }, + }}, + expected: true, + }, + }, + "AVD-AWS-0156": { + { + name: "Multi-region CloudTrail alarms on security group changes", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{ + { + Metadata: trivyTypes.NewTestMetadata(), + FilterName: trivyTypes.String("SecurityGroupChange", trivyTypes.NewTestMetadata()), + FilterPattern: trivyTypes.String(`{($.eventName=AuthorizeSecurityGroupIngress) || + ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || + ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || + ($.eventName=DeleteSecurityGroup)}`, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("SecurityGroupChange", trivyTypes.NewTestMetadata()), + MetricName: trivyTypes.String("SecurityGroupChange", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + { + Metadata: trivyTypes.NewTestMetadata(), + ID: trivyTypes.String("SecurityGroupChange", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}, + expected: false, + }, + { + name: "Multi-region CloudTrail has no filter for security group changes", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{}, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("SecurityGroupChange", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + {}, + }, + }, + }, + }, + }}, + expected: true, + }, + }, + "AVD-AWS-0147": { + { + name: "Multi-region CloudTrail alarms on Unauthorized API calls", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{ + { + Metadata: trivyTypes.NewTestMetadata(), + FilterName: trivyTypes.String("UnauthorizedAPIUsage", trivyTypes.NewTestMetadata()), + FilterPattern: trivyTypes.String(`($.errorCode = "*UnauthorizedOperation") || ($.errorCode = "AccessDenied*")`, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("CloudTrail_Unauthorized_API_Call", trivyTypes.NewTestMetadata()), + MetricName: trivyTypes.String("UnauthorizedAPIUsage", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + { + Metadata: trivyTypes.NewTestMetadata(), + ID: trivyTypes.String("UnauthorizedAPIUsage", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}, + expected: false, + }, + { + name: "Multi-region CloudTrail has no filter for Unauthorized API calls", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{}, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("CloudTrail_Unauthorized_API_Call", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + {}, + }, + }, + }, + }, + }}, + expected: true, + }, + }, + "AVD-AWS-0160": { + { + name: "Multi-region CloudTrail alarms on VPC changes", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{ + { + Metadata: trivyTypes.NewTestMetadata(), + FilterName: trivyTypes.String("VPCChange", trivyTypes.NewTestMetadata()), + FilterPattern: trivyTypes.String(`{($.eventName=CreateVpc) || + ($.eventName=DeleteVpc) || ($.eventName=ModifyVpcAttribute) || + ($.eventName=AcceptVpcPeeringConnection) || ($.eventName=CreateVpcPeeringConnection) || + ($.eventName=DeleteVpcPeeringConnection) || ($.eventName=RejectVpcPeeringConnection) || + ($.eventName=AttachClassicLinkVpc) || ($.eventName=DetachClassicLinkVpc) || + ($.eventName=DisableVpcClassicLink) || ($.eventName=EnableVpcClassicLink)}`, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("VPCChange", trivyTypes.NewTestMetadata()), + MetricName: trivyTypes.String("VPCChange", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + { + Metadata: trivyTypes.NewTestMetadata(), + ID: trivyTypes.String("VPCChange", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}, + expected: false, + }, + { + name: "Multi-region CloudTrail has no filter for VPC changes", + input: state.State{AWS: aws.AWS{ + CloudTrail: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Metadata: trivyTypes.NewTestMetadata(), + CloudWatchLogsLogGroupArn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + IsLogging: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + IsMultiRegion: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + CloudWatch: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Arn: trivyTypes.String("arn:aws:cloudwatch:us-east-1:123456789012:log-group:cloudtrail-logging", trivyTypes.NewTestMetadata()), + MetricFilters: []cloudwatch.MetricFilter{}, + }, + }, + Alarms: []cloudwatch.Alarm{ + { + Metadata: trivyTypes.NewTestMetadata(), + AlarmName: trivyTypes.String("VPCChange", trivyTypes.NewTestMetadata()), + Metrics: []cloudwatch.MetricDataQuery{ + {}, + }, + }, + }, + }, + }}, + expected: true, + }, + }, +}