diff --git a/rules/aws/codebuild/codebuild_encryption_key_rule.guard b/rules/aws/codebuild/codebuild_encryption_key_rule.guard new file mode 100644 index 0000000..8596daf --- /dev/null +++ b/rules/aws/codebuild/codebuild_encryption_key_rule.guard @@ -0,0 +1,46 @@ +# +##################################### +## AWS Solutions ## +##################################### +# Rule Identifier: +# CODEBUILD_ENCRYPTION_KEY_RULE +# +# Description: +# CodeBuild project should specify an EncryptionKey value +# +# Reports on: +# AWS::CodeBuild::Project +# +# Evaluates: +# AWS CloudFormation +# +# Rule Parameters: +# NA +# +# CFN_NAG Rule Id: +# W32 +# +# Scenarios: +# a) SKIP: when there is no CodeBuild Project resource present. +# b) PASS: when CodeBuild Project resources have Encryption Key. +# c) FAIL: when CodeBuild Project resources does have Encryption Key. +# d) SKIP: when metadata has rule suppression for CODEBUILD_ENCRYPTION_KEY_RULE + +# +# Select all CodeBuild Project resources from incoming template (payload) +# +let codebuild_encryption_key_rule = Resources.*[ Type == 'AWS::CodeBuild::Project' + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "W32" + Metadata.guard.SuppressedRules not exists or + Metadata.guard.SuppressedRules.* != "CODEBUILD_ENCRYPTION_KEY_RULE" +] + +rule CODEBUILD_ENCRYPTION_KEY_RULE when %codebuild_encryption_key_rule !empty { + %codebuild_encryption_key_rule.Type == 'AWS::CodeBuild::Project' + %codebuild_encryption_key_rule.Properties.EncryptionKey exists + << + Violation: CodeBuild project encryption key does not exist + Fix: Specify encryption key value + >> +} diff --git a/rules/aws/codebuild/tests/codebuild_encryption_key_rule_tests.yml b/rules/aws/codebuild/tests/codebuild_encryption_key_rule_tests.yml new file mode 100644 index 0000000..cc42dc2 --- /dev/null +++ b/rules/aws/codebuild/tests/codebuild_encryption_key_rule_tests.yml @@ -0,0 +1,194 @@ +### +# CODEBUILD_ENCRYPTION_KEY_RULE tests +### +--- +- name: Empty + input: {} + expectations: + rules: + CODEBUILD_ENCRYPTION_KEY_RULE: SKIP + +- name: No resources + input: + Resources: {} + expectations: + rules: + CODEBUILD_ENCRYPTION_KEY_RULE: SKIP + +- name: CodeBuild project with Encryption Key + input: + Resources: + Project: + Type: AWS::CodeBuild::Project + Properties: + Name: myProjectName + Description: A description about my project + ServiceRole: !GetAtt ServiceRole.Arn + EncryptionKey: 'TestEncryptionKey' + Artifacts: + Type: no_artifacts + Environment: + Type: LINUX_CONTAINER + ComputeType: BUILD_GENERAL1_SMALL + Image: aws/codebuild/java:openjdk-8 + EnvironmentVariables: + - Name: varName + Type: varType + Value: varValue + Source: + Location: codebuild-demo-test/0123ab9a371ebf0187b0fe5614fbb72c + Type: S3 + TimeoutInMinutes: 10 + Tags: + - Key: Key1 + Value: Value1 + - Key: Key2 + Value: Value2 + expectations: + rules: + CODEBUILD_ENCRYPTION_KEY_RULE: PASS + +- name: CodeBuild project where Encryption Key does not exist + input: + Resources: + Project: + Type: AWS::CodeBuild::Project + Properties: + Name: myProjectName + Description: A description about my project + ServiceRole: !GetAtt ServiceRole.Arn + Artifacts: + Type: no_artifacts + Environment: + Type: LINUX_CONTAINER + ComputeType: BUILD_GENERAL1_SMALL + Image: aws/codebuild/java:openjdk-8 + EnvironmentVariables: + - Name: varName + Type: varType + Value: varValue + Source: + Location: codebuild-demo-test/0123ab9a371ebf0187b0fe5614fbb72c + Type: S3 + TimeoutInMinutes: 10 + Tags: + - Key: Key1 + Value: Value1 + - Key: Key2 + Value: Value2 + expectations: + rules: + CODEBUILD_ENCRYPTION_KEY_RULE: FAIL + +- name: CFN_NAG suppression for W32 + input: + Resources: + Project: + Type: AWS::CodeBuild::Project + Properties: + Name: myProjectName + Description: A description about my project + ServiceRole: !GetAtt ServiceRole.Arn + Artifacts: + Type: no_artifacts + Environment: + Type: LINUX_CONTAINER + ComputeType: BUILD_GENERAL1_SMALL + Image: aws/codebuild/java:openjdk-8 + EnvironmentVariables: + - Name: varName + Type: varType + Value: varValue + Source: + Location: codebuild-demo-test/0123ab9a371ebf0187b0fe5614fbb72c + Type: S3 + TimeoutInMinutes: 10 + Tags: + - Key: Key1 + Value: Value1 + - Key: Key2 + Value: Value2 + Metadata: + cfn_nag: + rules_to_suppress: + - id: W32 + reason: Suppressed to test suppression works and skips this test + expectations: + rules: + CODEBUILD_ENCRYPTION_KEY_RULE: SKIP + +- name: Guard suppression for CODEBUILD_ENCRYPTION_KEY_RULE + input: + Resources: + Project: + Type: AWS::CodeBuild::Project + Properties: + Name: myProjectName + Description: A description about my project + ServiceRole: !GetAtt ServiceRole.Arn + Artifacts: + Type: no_artifacts + Environment: + Type: LINUX_CONTAINER + ComputeType: BUILD_GENERAL1_SMALL + Image: aws/codebuild/java:openjdk-8 + EnvironmentVariables: + - Name: varName + Type: varType + Value: varValue + Source: + Location: codebuild-demo-test/0123ab9a371ebf0187b0fe5614fbb72c + Type: S3 + TimeoutInMinutes: 10 + Tags: + - Key: Key1 + Value: Value1 + - Key: Key2 + Value: Value2 + Metadata: + guard: + SuppressedRules: + - CODEBUILD_ENCRYPTION_KEY_RULE + expectations: + rules: + CODEBUILD_ENCRYPTION_KEY_RULE: SKIP + +- name: Guard and CFN_NAG suppression for W32 & CODEBUILD_ENCRYPTION_KEY_RULE + input: + Resources: + Project: + Type: AWS::CodeBuild::Project + Properties: + Name: myProjectName + Description: A description about my project + ServiceRole: !GetAtt ServiceRole.Arn + Artifacts: + Type: no_artifacts + Environment: + Type: LINUX_CONTAINER + ComputeType: BUILD_GENERAL1_SMALL + Image: aws/codebuild/java:openjdk-8 + EnvironmentVariables: + - Name: varName + Type: varType + Value: varValue + Source: + Location: codebuild-demo-test/0123ab9a371ebf0187b0fe5614fbb72c + Type: S3 + TimeoutInMinutes: 10 + Tags: + - Key: Key1 + Value: Value1 + - Key: Key2 + Value: Value2 + Metadata: + cfn_nag: + rules_to_suppress: + - id: W32 + reason: Suppressed to test suppression works and skips this test + guard: + SuppressedRules: + - CODEBUILD_ENCRYPTION_KEY_RULE + expectations: + rules: + CODEBUILD_ENCRYPTION_KEY_RULE: SKIP diff --git a/rules/aws/dax/dax_encryption_enabled.guard b/rules/aws/dax/dax_encryption_enabled.guard index 1d6ac09..ba9660c 100644 --- a/rules/aws/dax/dax_encryption_enabled.guard +++ b/rules/aws/dax/dax_encryption_enabled.guard @@ -29,7 +29,9 @@ # let dax_clusters_encryption = Resources.*[ Type == "AWS::DAX::Cluster" - Metadata.guard.SuppressedRules not exists or + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "W83" + Metadata.guard.SuppressedRules not exists or Metadata.guard.SuppressedRules.* != "DAX_ENCRYPTION_ENABLED" ] diff --git a/rules/aws/dax/tests/dax_encryption_enabled_tests.yml b/rules/aws/dax/tests/dax_encryption_enabled_tests.yml index 3af8f33..df18ae1 100644 --- a/rules/aws/dax/tests/dax_encryption_enabled_tests.yml +++ b/rules/aws/dax/tests/dax_encryption_enabled_tests.yml @@ -36,6 +36,28 @@ rules: DAX_ENCRYPTION_ENABLED: SKIP +- name: Scenario b) SSESpecification not provided but rule suppressed, SKIP + input: + Resources: + daxCluster: + Type: AWS::DAX::Cluster + Metadata: + cfn_nag: + rules_to_suppress: + - id: W83 + reason: Suppressed for a very good reason + Properties: + ClusterName: "MyDAXCluster" + NodeType: "dax.r3.large" + ReplicationFactor: 1 + IAMRoleARN: "arn:aws:iam::111122223333:role/DaxAccess" + Description: "DAX cluster created with CloudFormation" + SubnetGroupName: !Ref subnetGroup + ClusterEndpointEncryptionType: TLS + expectations: + rules: + DAX_ENCRYPTION_ENABLED: SKIP + - name: Scenario c) SSESpecification configuration missing, FAIL input: Resources: diff --git a/rules/aws/dynamodb/dynamodb_billing_mode_rule.guard b/rules/aws/dynamodb/dynamodb_billing_mode_rule.guard new file mode 100644 index 0000000..528bdf4 --- /dev/null +++ b/rules/aws/dynamodb/dynamodb_billing_mode_rule.guard @@ -0,0 +1,54 @@ +# +##################################### +## AWS Solutions ## +##################################### +# Rule Identifier: +# DYNAMODB_BILLING_MODE_RULE +# +# Description: +# DynamoDB table should have billing mode set to either PAY_PER_REQUEST or PROVISIONED. +# +# Reports on: +# AWS::DynamoDB::Table +# +# Evaluates: +# AWS CloudFormation +# +# Rule Parameters: +# NA +# +# CFN_NAG Rule Id: +# W73 +# +# Scenarios: +# a) SKIP: when there are no DynamoDb Table resources present +# b) PASS: When all DynamoDb Table resources uses billingMode as PAY_PER_REQUEST or PROVISIONED +# c) FAIL: When any DynamoDb Table resources uses billingMode not as PAY_PER_REQUEST or PROVISIONED or billingMode is not specified. +# d) SKIP: when metadata has rule suppression for DYNAMODB_BILLING_MODE_RULE + +# +# Select all DynamoDb Table resources from incoming template (payload) +# +let dynamodb_billing_mode_rule = Resources.*[ Type == 'AWS::DynamoDB::Table' + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "W73" + Metadata.guard.SuppressedRules not exists or + Metadata.guard.SuppressedRules.* != "DYNAMODB_BILLING_MODE_RULE" +] + +rule DYNAMODB_BILLING_MODE_RULE when %dynamodb_billing_mode_rule !empty { + let violations = %dynamodb_billing_mode_rule[ + Properties.BillingMode !exists + OR + Properties { + BillingMode != 'PAY_PER_REQUEST' + BillingMode != 'PROVISIONED' + } + ] + + %violations empty + << + Violation: DynamoDb Table resources uses billingMode not as PAY_PER_REQUEST or PROVISIONED or billingMode is not specified. + Fix: Specify billingMode as PAY_PER_REQUEST or PROVISIONED. + >> +} diff --git a/rules/aws/dynamodb/dynamodb_pitr_enabled.guard b/rules/aws/dynamodb/dynamodb_pitr_enabled.guard index d7b9eca..569ce7e 100644 --- a/rules/aws/dynamodb/dynamodb_pitr_enabled.guard +++ b/rules/aws/dynamodb/dynamodb_pitr_enabled.guard @@ -29,6 +29,8 @@ # Select all DynamoDB Table resources from incoming template (payload) # let dynamodb_pitr_enabled = Resources.*[ Type == "AWS::DynamoDB::Table" + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "W78" Metadata.guard.SuppressedRules not exists or Metadata.guard.SuppressedRules.* != "DYNAMODB_PITR_ENABLED" ] diff --git a/rules/aws/dynamodb/dynamodb_table_encrypted_kms.guard b/rules/aws/dynamodb/dynamodb_table_encrypted_kms.guard index 0a4bb41..ff07c8c 100644 --- a/rules/aws/dynamodb/dynamodb_table_encrypted_kms.guard +++ b/rules/aws/dynamodb/dynamodb_table_encrypted_kms.guard @@ -25,6 +25,8 @@ # d) PASS: when all DynamoDB Tables are encrypted with KMS let dynamodb_table_encrypted_kms = Resources.*[ Type == "AWS::DynamoDB::Table" + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "W74" Metadata.guard.SuppressedRules not exists or Metadata.guard.SuppressedRules.* != "DYNAMODB_TABLE_ENCRYPTED_KMS" ] diff --git a/rules/aws/dynamodb/tests/dynamodb_billing_mode_rule_tests.yml b/rules/aws/dynamodb/tests/dynamodb_billing_mode_rule_tests.yml new file mode 100644 index 0000000..d591f4b --- /dev/null +++ b/rules/aws/dynamodb/tests/dynamodb_billing_mode_rule_tests.yml @@ -0,0 +1,275 @@ +### +# DYNAMODB_BILLING_MODE_RULE tests +### +--- +- name: Empty + input: {} + expectations: + rules: + DYNAMODB_BILLING_MODE_RULE: SKIP + +- name: No resources + input: + Resources: {} + expectations: + rules: + DYNAMODB_BILLING_MODE_RULE: SKIP + +- name: DynamoDb BillingMode as PROVISIONED + input: + Resources: + mySecondDDBTable: + Type: AWS::DynamoDB::Table + DependsOn: "myFirstDDBTable" + Properties: + AttributeDefinitions: + - AttributeName: "ArtistId" + AttributeType: "S" + - AttributeName: "Concert" + AttributeType: "S" + - AttributeName: "TicketSales" + AttributeType: "S" + KeySchema: + - AttributeName: "ArtistId" + KeyType: "HASH" + - AttributeName: "Concert" + KeyType: "RANGE" + ProvisionedThroughput: + ReadCapacityUnits: + Ref: "ReadCapacityUnits" + WriteCapacityUnits: + Ref: "WriteCapacityUnits" + GlobalSecondaryIndexes: + - IndexName: "myGSI" + KeySchema: + - AttributeName: "TicketSales" + KeyType: "HASH" + Projection: + ProjectionType: "KEYS_ONLY" + ProvisionedThroughput: + ReadCapacityUnits: + Ref: "ReadCapacityUnits" + WriteCapacityUnits: + Ref: "WriteCapacityUnits" + BillingMode: 'PROVISIONED' + expectations: + rules: + DYNAMODB_BILLING_MODE_RULE: PASS + +- name: DynamoDb BillingMode does not exist + input: + Resources: + mySecondDDBTable: + Type: AWS::DynamoDB::Table + DependsOn: "myFirstDDBTable" + Properties: + AttributeDefinitions: + - AttributeName: "ArtistId" + AttributeType: "S" + - AttributeName: "Concert" + AttributeType: "S" + - AttributeName: "TicketSales" + AttributeType: "S" + KeySchema: + - AttributeName: "ArtistId" + KeyType: "HASH" + - AttributeName: "Concert" + KeyType: "RANGE" + ProvisionedThroughput: + ReadCapacityUnits: + Ref: "ReadCapacityUnits" + WriteCapacityUnits: + Ref: "WriteCapacityUnits" + GlobalSecondaryIndexes: + - IndexName: "myGSI" + KeySchema: + - AttributeName: "TicketSales" + KeyType: "HASH" + Projection: + ProjectionType: "KEYS_ONLY" + ProvisionedThroughput: + ReadCapacityUnits: + Ref: "ReadCapacityUnits" + WriteCapacityUnits: + Ref: "WriteCapacityUnits" + expectations: + rules: + DYNAMODB_BILLING_MODE_RULE: FAIL + +- name: DynamoDb BillingMode as 'TEST' + input: + Resources: + mySecondDDBTable: + Type: AWS::DynamoDB::Table + DependsOn: "myFirstDDBTable" + Properties: + AttributeDefinitions: + - AttributeName: "ArtistId" + AttributeType: "S" + - AttributeName: "Concert" + AttributeType: "S" + - AttributeName: "TicketSales" + AttributeType: "S" + KeySchema: + - AttributeName: "ArtistId" + KeyType: "HASH" + - AttributeName: "Concert" + KeyType: "RANGE" + ProvisionedThroughput: + ReadCapacityUnits: + Ref: "ReadCapacityUnits" + WriteCapacityUnits: + Ref: "WriteCapacityUnits" + GlobalSecondaryIndexes: + - IndexName: "myGSI" + KeySchema: + - AttributeName: "TicketSales" + KeyType: "HASH" + Projection: + ProjectionType: "KEYS_ONLY" + ProvisionedThroughput: + ReadCapacityUnits: + Ref: "ReadCapacityUnits" + WriteCapacityUnits: + Ref: "WriteCapacityUnits" + BillingMode: 'TEST' + expectations: + rules: + DYNAMODB_BILLING_MODE_RULE: FAIL + +- name: CFN_NAG suppression for W73 + input: + Resources: + mySecondDDBTable: + Type: AWS::DynamoDB::Table + DependsOn: "myFirstDDBTable" + Properties: + AttributeDefinitions: + - AttributeName: "ArtistId" + AttributeType: "S" + - AttributeName: "Concert" + AttributeType: "S" + - AttributeName: "TicketSales" + AttributeType: "S" + KeySchema: + - AttributeName: "ArtistId" + KeyType: "HASH" + - AttributeName: "Concert" + KeyType: "RANGE" + ProvisionedThroughput: + ReadCapacityUnits: + Ref: "ReadCapacityUnits" + WriteCapacityUnits: + Ref: "WriteCapacityUnits" + GlobalSecondaryIndexes: + - IndexName: "myGSI" + KeySchema: + - AttributeName: "TicketSales" + KeyType: "HASH" + Projection: + ProjectionType: "KEYS_ONLY" + ProvisionedThroughput: + ReadCapacityUnits: + Ref: "ReadCapacityUnits" + WriteCapacityUnits: + Ref: "WriteCapacityUnits" + Metadata: + cfn_nag: + rules_to_suppress: + - id: W73 + reason: Suppressed to test suppression works and skips this test + expectations: + rules: + DYNAMODB_BILLING_MODE_RULE: SKIP + +- name: Guard suppression for DYNAMODB_BILLING_MODE_RULE + input: + Resources: + mySecondDDBTable: + Type: AWS::DynamoDB::Table + DependsOn: "myFirstDDBTable" + Properties: + AttributeDefinitions: + - AttributeName: "ArtistId" + AttributeType: "S" + - AttributeName: "Concert" + AttributeType: "S" + - AttributeName: "TicketSales" + AttributeType: "S" + KeySchema: + - AttributeName: "ArtistId" + KeyType: "HASH" + - AttributeName: "Concert" + KeyType: "RANGE" + ProvisionedThroughput: + ReadCapacityUnits: + Ref: "ReadCapacityUnits" + WriteCapacityUnits: + Ref: "WriteCapacityUnits" + GlobalSecondaryIndexes: + - IndexName: "myGSI" + KeySchema: + - AttributeName: "TicketSales" + KeyType: "HASH" + Projection: + ProjectionType: "KEYS_ONLY" + ProvisionedThroughput: + ReadCapacityUnits: + Ref: "ReadCapacityUnits" + WriteCapacityUnits: + Ref: "WriteCapacityUnits" + Metadata: + guard: + SuppressedRules: + - DYNAMODB_BILLING_MODE_RULE + expectations: + rules: + DYNAMODB_BILLING_MODE_RULE: SKIP + +- name: Guard and CFN_NAG suppression for W73 & DYNAMODB_BILLING_MODE_RULE + input: + Resources: + mySecondDDBTable: + Type: AWS::DynamoDB::Table + DependsOn: "myFirstDDBTable" + Properties: + AttributeDefinitions: + - AttributeName: "ArtistId" + AttributeType: "S" + - AttributeName: "Concert" + AttributeType: "S" + - AttributeName: "TicketSales" + AttributeType: "S" + KeySchema: + - AttributeName: "ArtistId" + KeyType: "HASH" + - AttributeName: "Concert" + KeyType: "RANGE" + ProvisionedThroughput: + ReadCapacityUnits: + Ref: "ReadCapacityUnits" + WriteCapacityUnits: + Ref: "WriteCapacityUnits" + GlobalSecondaryIndexes: + - IndexName: "myGSI" + KeySchema: + - AttributeName: "TicketSales" + KeyType: "HASH" + Projection: + ProjectionType: "KEYS_ONLY" + ProvisionedThroughput: + ReadCapacityUnits: + Ref: "ReadCapacityUnits" + WriteCapacityUnits: + Ref: "WriteCapacityUnits" + Metadata: + cfn_nag: + rules_to_suppress: + - id: W73 + reason: Suppressed to test suppression works and skips this test + guard: + SuppressedRules: + - DYNAMODB_BILLING_MODE_RULE + expectations: + rules: + DYNAMODB_BILLING_MODE_RULE: SKIP diff --git a/rules/aws/dynamodb/tests/dynamodb_pitr_enabled_tests.yml b/rules/aws/dynamodb/tests/dynamodb_pitr_enabled_tests.yml index 514fbde..1dc6ffc 100644 --- a/rules/aws/dynamodb/tests/dynamodb_pitr_enabled_tests.yml +++ b/rules/aws/dynamodb/tests/dynamodb_pitr_enabled_tests.yml @@ -45,6 +45,37 @@ rules: DYNAMODB_PITR_ENABLED: SKIP +- name: Scenario b) DynamoDB Table with PITR disabled but rule is suppressed, SKIP + input: + Resources: + Exampletable: + Type: AWS::DynamoDB::Table + Metadata: + cfn_nag: + rules_to_suppress: + - id: W78 + reason: Suppressed for a very good reason + Properties: + KeySchema: + - AttributeName: Id + KeyType: HASH + AttributeDefinitions: + - AttributeName: Id + AttributeType: S + - AttributeName: dummy + AttributeType: S + - AttributeName: name + AttributeType: S + - AttributeName: owner + AttributeType: S + - AttributeName: createdAt + AttributeType: S + PointInTimeRecoverySpecification: + PointInTimeRecoveryEnabled: true + expectations: + rules: + DYNAMODB_PITR_ENABLED: SKIP + - name: Scenario c) DynamoDB Table with a missing PITR configuration, FAIL input: Resources: diff --git a/rules/aws/dynamodb/tests/dynamodb_table_encrypted_kms_tests.yml b/rules/aws/dynamodb/tests/dynamodb_table_encrypted_kms_tests.yml index cd8312d..35d7116 100644 --- a/rules/aws/dynamodb/tests/dynamodb_table_encrypted_kms_tests.yml +++ b/rules/aws/dynamodb/tests/dynamodb_table_encrypted_kms_tests.yml @@ -43,6 +43,35 @@ rules: DYNAMODB_TABLE_ENCRYPTED_KMS : SKIP +- name: Scenario b) DynamoDB Table with missing SSESpecification property but rule suppressed, SKIP + input: + Resources: + Exampletable: + Type: AWS::DynamoDB::Table + Metadata: + cfn_nag: + rules_to_suppress: + - id: W74 + reason: Suppressed for a very good reason + Properties: + KeySchema: + - AttributeName: Id + KeyType: HASH + AttributeDefinitions: + - AttributeName: Id + AttributeType: S + - AttributeName: dummy + AttributeType: S + - AttributeName: name + AttributeType: S + - AttributeName: owner + AttributeType: S + - AttributeName: createdAt + AttributeType: S + expectations: + rules: + DYNAMODB_TABLE_ENCRYPTED_KMS : SKIP + - name: Scenario c) DynamoDB Table with missing SSESpecification property, FAIL input: Resources: diff --git a/rules/aws/elastic_load_balancing/elb_logging_enabled.guard b/rules/aws/elastic_load_balancing/elb_logging_enabled.guard index 78826aa..adbb923 100644 --- a/rules/aws/elastic_load_balancing/elb_logging_enabled.guard +++ b/rules/aws/elastic_load_balancing/elb_logging_enabled.guard @@ -29,6 +29,8 @@ # Select all Elastic Load Balancing Resources from incoming template (payload) # let elb_logging_enabled_resources = Resources.*[ Type == 'AWS::ElasticLoadBalancing::LoadBalancer' + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "W26" Metadata.guard.SuppressedRules not exists or Metadata.guard.SuppressedRules.* != "ELB_LOGGING_ENABLED" ] diff --git a/rules/aws/elastic_load_balancing/tests/elb_logging_enabled_tests.yml b/rules/aws/elastic_load_balancing/tests/elb_logging_enabled_tests.yml index 6030b63..a91a86c 100644 --- a/rules/aws/elastic_load_balancing/tests/elb_logging_enabled_tests.yml +++ b/rules/aws/elastic_load_balancing/tests/elb_logging_enabled_tests.yml @@ -28,6 +28,20 @@ rules: ELB_LOGGING_ENABLED: SKIP +- name: Scenario b) Rule suppressed - CFN_NAG, SKIP + input: + Resources: + Elb: + Type: AWS::ElasticLoadBalancing::LoadBalancer + Metadata: + cfn_nag: + rules_to_suppress: + - id: W26 + reason: Suppressed for a very good reason + expectations: + rules: + ELB_LOGGING_ENABLED: SKIP + - name: Scenario c) 'AccessLoggingPolicy' has not been specified, FAIL input: Resources: diff --git a/rules/aws/elastic_load_balancing_v2/elbv2_access_logging_rule.guard b/rules/aws/elastic_load_balancing_v2/elbv2_access_logging_rule.guard new file mode 100644 index 0000000..539b967 --- /dev/null +++ b/rules/aws/elastic_load_balancing_v2/elbv2_access_logging_rule.guard @@ -0,0 +1,64 @@ +# +##################################### +## AWS Solutions ## +##################################### +# +# Rule Identifier: +# ELBV2_ACCESS_LOGGING_RULE +# +# Description: +# Elastic Load Balancer V2 should have access logging enabled +# +# Reports on: +# AWS::ElasticLoadBalancingV2::LoadBalancer +# +# Evaluates: +# AWS CloudFormation +# +# Rule Parameters: +# None +# +# CFN_NAG Rule Id: +# W52 +# +# Scenarios: +# a) SKIP: when there are no Elastic Load Balancing V2 LoadBalancer Resources +# b) SKIP: when metadata has rule suppression for ELBV2_ACCESS_LOGGING_RULE +# c) FAIL: when loadBalancerAttributes does not exists or access logging is disabled. +# d) PASS: when loadBalancerAttributes exists or access logging is enabled. + +# +# Select all Elastic Load Balancing Resources from incoming template (payload) +# +let elbv2_access_logging_rule = Resources.*[ Type == 'AWS::ElasticLoadBalancingV2::LoadBalancer' + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "W52" + Metadata.guard.SuppressedRules not exists or + Metadata.guard.SuppressedRules.* != "ELBV2_ACCESS_LOGGING_RULE" +] + +rule ELBV2_ACCESS_LOGGING_RULE when %elbv2_access_logging_rule !empty { + let violations = %elbv2_access_logging_rule[ + Type == 'AWS::ElasticLoadBalancingV2::LoadBalancer' + Properties.LoadBalancerAttributes !exists + OR + Properties.LoadBalancerAttributes[*].key != 'access_logs.s3.enabled' + OR + some Properties.LoadBalancerAttributes[*] { + key == 'access_logs.s3.enabled' + value !exists + OR + value == 'false' + OR + value == 'False' + OR + value == 'FALSE' + } + ] + + %violations empty + << + Violation: Elastic Load Balancer V2 does not have loadBalancerAttributes or access logging is disabled. + Fix: Specify loadBalancerAttributes and make sure access logging is enabled. + >> +} diff --git a/rules/aws/elastic_load_balancing_v2/elbv2_listener_protocol_rule.guard b/rules/aws/elastic_load_balancing_v2/elbv2_listener_protocol_rule.guard new file mode 100644 index 0000000..26bbae4 --- /dev/null +++ b/rules/aws/elastic_load_balancing_v2/elbv2_listener_protocol_rule.guard @@ -0,0 +1,52 @@ +# +##################################### +## AWS Solutions ## +##################################### +# +# Rule Identifier: +# ELBV2_LISTENER_PROTOCOL_RULE +# +# Description: +# Elastic Load Balancer V2 Listener Protocol should use HTTPS for ALBs +# +# Reports on: +# AWS::ElasticLoadBalancingV2::Listener +# +# Evaluates: +# AWS CloudFormation +# +# Rule Parameters: +# None +# +# CFN_NAG Rule Id: +# W56 +# +# Scenarios: +# a) SKIP: when there are no Elastic Load Balancing V2 Listener Resources +# b) SKIP: when metadata has rule suppression for ELBV2_LISTENER_PROTOCOL_RULE +# c) FAIL: when Protocol in Elastic Load Balancing V2 Listener uses HTTP. +# d) PASS: when Protocol in Elastic Load Balancing V2 Listener uses HTTPS. + +# +# Select all Elastic Load Balancing Resources from incoming template (payload) +# +let elbv2_listener_protocol_rule = Resources.*[ Type == 'AWS::ElasticLoadBalancingV2::Listener' + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "W56" + Metadata.guard.SuppressedRules not exists or + Metadata.guard.SuppressedRules.* != "ELBV2_LISTENER_PROTOCOL_RULE" +] + +rule ELBV2_LISTENER_PROTOCOL_RULE when %elbv2_listener_protocol_rule !empty { + let violations = %elbv2_listener_protocol_rule[ + Type == 'AWS::ElasticLoadBalancingV2::Listener' + Properties.Protocol exists + Properties.Protocol == 'HTTP' + ] + + %violations empty + << + Violation: Elastic Load Balancer V2 do not use HTTPS protocol. + Fix: use HTTPS protocol for Elastic Load Balancer V2 Listener. + >> +} diff --git a/rules/aws/elastic_load_balancing_v2/elbv2_listener_ssl_policy_rule.guard b/rules/aws/elastic_load_balancing_v2/elbv2_listener_ssl_policy_rule.guard new file mode 100644 index 0000000..c64f918 --- /dev/null +++ b/rules/aws/elastic_load_balancing_v2/elbv2_listener_ssl_policy_rule.guard @@ -0,0 +1,63 @@ +# +##################################### +## AWS Solutions ## +##################################### +# +# Rule Identifier: +# ELBV2_LISTENER_SSL_POLICY_RULE +# +# Description: +# Elastic Load Balancer V2 Listener SslPolicy should use TLS 1.2 +# +# Reports on: +# AWS::ElasticLoadBalancingV2::Listener +# +# Evaluates: +# AWS CloudFormation +# +# Rule Parameters: +# None +# +# CFN_NAG Rule Id: +# W55 +# +# Scenarios: +# a) SKIP: when there are no Elastic Load Balancing V2 Listener Resources +# b) SKIP: when metadata has rule suppression for ELBV2_LISTENER_SSL_POLICY_RULE +# c) FAIL: when sslPolicy in Elastic Load Balancing V2 Listener does not exist or do not use TLS 1.2. +# d) PASS: when sslPolicy in Elastic Load Balancing V2 Listener does exist and use TLS 1.2. + +# +# Select all Elastic Load Balancing Resources from incoming template (payload) +# +let elbv2_listener_ssl_policy_rule = Resources.*[ Type == 'AWS::ElasticLoadBalancingV2::Listener' + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "W55" + Metadata.guard.SuppressedRules not exists or + Metadata.guard.SuppressedRules.* != "ELBV2_LISTENER_SSL_POLICY_RULE" +] + +rule ELBV2_LISTENER_SSL_POLICY_RULE when %elbv2_listener_ssl_policy_rule !empty { + let violations = %elbv2_listener_ssl_policy_rule[ + Type == 'AWS::ElasticLoadBalancingV2::Listener' + Properties.SslPolicy !exists + OR + Properties.SslPolicy == /(?i)ELBSecurityPolicy-2016-08/ + OR + Properties.SslPolicy == /(?i)ELBSecurityPolicy-TLS-1-0-2015-04/ + OR + Properties.SslPolicy == /(?i)ELBSecurityPolicy-TLS-1-1-2017-01/ + OR + Properties.SslPolicy == /(?i)ELBSecurityPolicy-FS-2018-06/ + OR + Properties.SslPolicy == /(?i)ELBSecurityPolicy-FS-1-1-2019-08/ + OR + Properties.SslPolicy == /(?i)ELBSecurityPolicy-2015/ + ] + + %violations empty + << + Violation: Elastic Load Balancer V2 does not have SslPolicy or do not use TLS 1.2. + Fix: Enable SslPolicy and use TLS 1.2. + >> +} diff --git a/rules/aws/elastic_load_balancing_v2/tests/elbv2_access_logging_rule_tests.yml b/rules/aws/elastic_load_balancing_v2/tests/elbv2_access_logging_rule_tests.yml new file mode 100644 index 0000000..e8ec02f --- /dev/null +++ b/rules/aws/elastic_load_balancing_v2/tests/elbv2_access_logging_rule_tests.yml @@ -0,0 +1,157 @@ +### +# ELBV2_ACCESS_LOGGING_RULE tests +### +--- +- name: Empty + input: {} + expectations: + rules: + ELBV2_ACCESS_LOGGING_RULE: SKIP + +- name: No resources + input: + Resources: {} + expectations: + rules: + ELBV2_ACCESS_LOGGING_RULE: SKIP + +- name: ELBV2 with loadBalancerAttributes with access logging enabled. + input: + Resources: + ElbV2Resource: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic: TestRule + IpAddressType: ipv4 + LoadBalancerAttributes: + - Key: access_logs.s3.enabled + Value: true + Name: testElb + Type: ElbResource + expectations: + rules: + ELBV2_ACCESS_LOGGING_RULE: PASS + +- name: ELBV2 with no loadBalancerAttributes + input: + Resources: + ElbV2Resource: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic: TestRule + IpAddressType: ipv4 + Name: testElb + Type: ElbResource + expectations: + rules: + ELBV2_ACCESS_LOGGING_RULE: FAIL + +- name: ELBV2 with loadBalancerAttributes with no access logging key. + input: + Resources: + ElbV2Resource: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic: TestRule + IpAddressType: ipv4 + LoadBalancerAttributes: + - Key: access_logs.s3.bucket + Value: 'testBucket' + Name: testElb + Type: ElbResource + expectations: + rules: + ELBV2_ACCESS_LOGGING_RULE: FAIL + +- name: ELBV2 with loadBalancerAttributes with access logging set to false. + input: + Resources: + ElbV2Resource: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic: TestRule + IpAddressType: ipv4 + LoadBalancerAttributes: + - Key: access_logs.s3.bucket + Value: 'testBucket' + - Key: access_logs.s3.enabled + Value: 'false' + Name: testElb + Type: ElbResource + expectations: + rules: + ELBV2_ACCESS_LOGGING_RULE: FAIL + +- name: CFN_NAG suppression for W52 + input: + Resources: + ElbV2Resource: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic: TestRule + IpAddressType: ipv4 + LoadBalancerAttributes: + - Key: access_logs.s3.bucket + Value: 'testBucket' + - Key: access_logs.s3.enabled + Value: 'false' + Name: testElb + Type: ElbResource + Metadata: + cfn_nag: + rules_to_suppress: + - id: W52 + reason: Suppressed to test suppression works and skips this test + expectations: + rules: + ELBV2_ACCESS_LOGGING_RULE: SKIP + +- name: Guard suppression for ELBV2_ACCESS_LOGGING_RULE + input: + Resources: + ElbV2Resource: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic: TestRule + IpAddressType: ipv4 + LoadBalancerAttributes: + - Key: access_logs.s3.bucket + Value: 'testBucket' + - Key: access_logs.s3.enabled + Value: 'false' + Name: testElb + Type: ElbResource + Metadata: + guard: + SuppressedRules: + - ELBV2_ACCESS_LOGGING_RULE + expectations: + rules: + ELBV2_ACCESS_LOGGING_RULE: SKIP + +- name: Guard and CFN_NAG suppression for W52 & ELBV2_ACCESS_LOGGING_RULE + input: + Resources: + ElbV2Resource: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic: TestRule + IpAddressType: ipv4 + LoadBalancerAttributes: + - Key: access_logs.s3.bucket + Value: 'testBucket' + - Key: access_logs.s3.enabled + Value: 'false' + Name: testElb + Type: ElbResource + Metadata: + cfn_nag: + rules_to_suppress: + - id: W52 + reason: Suppressed to test suppression works and skips this test + guard: + SuppressedRules: + - ELBV2_ACCESS_LOGGING_RULE + expectations: + rules: + ELBV2_ACCESS_LOGGING_RULE: SKIP diff --git a/rules/aws/elastic_load_balancing_v2/tests/elbv2_listener_protocol_rule_tests.yml b/rules/aws/elastic_load_balancing_v2/tests/elbv2_listener_protocol_rule_tests.yml new file mode 100644 index 0000000..66f8987 --- /dev/null +++ b/rules/aws/elastic_load_balancing_v2/tests/elbv2_listener_protocol_rule_tests.yml @@ -0,0 +1,168 @@ +### +# ELBV2_LISTENER_PROTOCOL_RULE tests +### +--- +- name: Empty + input: {} + expectations: + rules: + ELBV2_LISTENER_PROTOCOL_RULE: SKIP + +- name: No resources + input: + Resources: {} + expectations: + rules: + ELBV2_LISTENER_PROTOCOL_RULE: SKIP + +- name: ELBV2 with protocol as HTTPS + input: + Resources: + HTTPlistener: + Type: "AWS::ElasticLoadBalancingV2::Listener" + Properties: + DefaultActions: + - Type: "redirect" + RedirectConfig: + Protocol: "HTTPS" + Port: 443 + Host: "#{host}" + Path: "/#{path}" + Query: "#{query}" + StatusCode: "HTTP_301" + LoadBalancerArn: !Ref myLoadBalancer + Port: 80 + Protocol: "HTTPS" + SslPolicy: TLS13-1-2-2021-06 + expectations: + rules: + ELBV2_LISTENER_PROTOCOL_RULE: PASS + +- name: ELBV2 listener with no protocol specified + input: + Resources: + HTTPlistener: + Type: "AWS::ElasticLoadBalancingV2::Listener" + Properties: + DefaultActions: + - Type: "redirect" + RedirectConfig: + Protocol: "HTTPS" + Port: 443 + Host: "#{host}" + Path: "/#{path}" + Query: "#{query}" + StatusCode: "HTTP_301" + LoadBalancerArn: !Ref myLoadBalancer + Port: 80 + expectations: + rules: + ELBV2_LISTENER_PROTOCOL_RULE: PASS + +- name: ELBV2 listener with Protocol as HTTP + input: + Resources: + HTTPlistener: + Type: "AWS::ElasticLoadBalancingV2::Listener" + Properties: + DefaultActions: + - Type: "redirect" + RedirectConfig: + Protocol: "HTTPS" + Port: 443 + Host: "#{host}" + Path: "/#{path}" + Query: "#{query}" + StatusCode: "HTTP_301" + LoadBalancerArn: !Ref myLoadBalancer + Port: 80 + Protocol: "HTTP" + expectations: + rules: + ELBV2_LISTENER_PROTOCOL_RULE: FAIL + +- name: CFN_NAG suppression for W56 + input: + Resources: + HTTPlistener: + Type: "AWS::ElasticLoadBalancingV2::Listener" + Properties: + DefaultActions: + - Type: "redirect" + RedirectConfig: + Protocol: "HTTPS" + Port: 443 + Host: "#{host}" + Path: "/#{path}" + Query: "#{query}" + StatusCode: "HTTP_301" + LoadBalancerArn: !Ref myLoadBalancer + Port: 80 + Protocol: "HTTP" + SslPolicy: ELBSecurityPolicy-TLS-1-1-2017-01 + Metadata: + cfn_nag: + rules_to_suppress: + - id: W56 + reason: Suppressed to test suppression works and skips this test + expectations: + rules: + ELBV2_LISTENER_PROTOCOL_RULE: SKIP + +- name: Guard suppression for ELBV2_LISTENER_PROTOCOL_RULE + input: + Resources: + HTTPlistener: + Type: "AWS::ElasticLoadBalancingV2::Listener" + Properties: + DefaultActions: + - Type: "redirect" + RedirectConfig: + Protocol: "HTTPS" + Port: 443 + Host: "#{host}" + Path: "/#{path}" + Query: "#{query}" + StatusCode: "HTTP_301" + LoadBalancerArn: !Ref myLoadBalancer + Port: 80 + Protocol: "HTTP" + SslPolicy: ELBSecurityPolicy-TLS-1-1-2017-01 + Metadata: + guard: + SuppressedRules: + - ELBV2_LISTENER_PROTOCOL_RULE + expectations: + rules: + ELBV2_LISTENER_PROTOCOL_RULE: SKIP + +- name: Guard and CFN_NAG suppression for W56 & ELBV2_LISTENER_PROTOCOL_RULE + input: + Resources: + HTTPlistener: + Type: "AWS::ElasticLoadBalancingV2::Listener" + Properties: + DefaultActions: + - Type: "redirect" + RedirectConfig: + Protocol: "HTTPS" + Port: 443 + Host: "#{host}" + Path: "/#{path}" + Query: "#{query}" + StatusCode: "HTTP_301" + LoadBalancerArn: !Ref myLoadBalancer + Port: 80 + Protocol: "HTTP" + SslPolicy: ELBSecurityPolicy-TLS-1-1-2017-01 + Metadata: + cfn_nag: + rules_to_suppress: + - id: W56 + reason: Suppressed to test suppression works and skips this test + guard: + SuppressedRules: + - ELBV2_LISTENER_PROTOCOL_RULE + expectations: + rules: + ELBV2_LISTENER_PROTOCOL_RULE: SKIP diff --git a/rules/aws/elastic_load_balancing_v2/tests/elbv2_listener_ssl_policy_rule_tests.yml b/rules/aws/elastic_load_balancing_v2/tests/elbv2_listener_ssl_policy_rule_tests.yml new file mode 100644 index 0000000..5b67304 --- /dev/null +++ b/rules/aws/elastic_load_balancing_v2/tests/elbv2_listener_ssl_policy_rule_tests.yml @@ -0,0 +1,170 @@ +### +# ELBV2_LISTENER_SSL_POLICY_RULE tests +### +--- +- name: Empty + input: {} + expectations: + rules: + ELBV2_LISTENER_SSL_POLICY_RULE: SKIP + +- name: No resources + input: + Resources: {} + expectations: + rules: + ELBV2_LISTENER_SSL_POLICY_RULE: SKIP + +- name: ELBV2 with loadBalancerAttributes SSLPolicy using TLS 1.2 + input: + Resources: + HTTPlistener: + Type: "AWS::ElasticLoadBalancingV2::Listener" + Properties: + DefaultActions: + - Type: "redirect" + RedirectConfig: + Protocol: "HTTPS" + Port: 443 + Host: "#{host}" + Path: "/#{path}" + Query: "#{query}" + StatusCode: "HTTP_301" + LoadBalancerArn: !Ref myLoadBalancer + Port: 80 + Protocol: "HTTP" + SslPolicy: TLS13-1-2-2021-06 + expectations: + rules: + ELBV2_LISTENER_SSL_POLICY_RULE: PASS + +- name: ELBV2 listener with no sslPolicy + input: + Resources: + HTTPlistener: + Type: "AWS::ElasticLoadBalancingV2::Listener" + Properties: + DefaultActions: + - Type: "redirect" + RedirectConfig: + Protocol: "HTTPS" + Port: 443 + Host: "#{host}" + Path: "/#{path}" + Query: "#{query}" + StatusCode: "HTTP_301" + LoadBalancerArn: !Ref myLoadBalancer + Port: 80 + Protocol: "HTTP" + expectations: + rules: + ELBV2_LISTENER_SSL_POLICY_RULE: FAIL + +- name: ELBV2 listener with sslPolicy not using TLS 1.2 + input: + Resources: + HTTPlistener: + Type: "AWS::ElasticLoadBalancingV2::Listener" + Properties: + DefaultActions: + - Type: "redirect" + RedirectConfig: + Protocol: "HTTPS" + Port: 443 + Host: "#{host}" + Path: "/#{path}" + Query: "#{query}" + StatusCode: "HTTP_301" + LoadBalancerArn: !Ref myLoadBalancer + Port: 80 + Protocol: "HTTP" + SslPolicy: ELBSecurityPolicy-TLS-1-1-2017-01 + expectations: + rules: + ELBV2_LISTENER_SSL_POLICY_RULE: FAIL + +- name: CFN_NAG suppression for W55 + input: + Resources: + HTTPlistener: + Type: "AWS::ElasticLoadBalancingV2::Listener" + Properties: + DefaultActions: + - Type: "redirect" + RedirectConfig: + Protocol: "HTTPS" + Port: 443 + Host: "#{host}" + Path: "/#{path}" + Query: "#{query}" + StatusCode: "HTTP_301" + LoadBalancerArn: !Ref myLoadBalancer + Port: 80 + Protocol: "HTTP" + SslPolicy: ELBSecurityPolicy-TLS-1-1-2017-01 + Metadata: + cfn_nag: + rules_to_suppress: + - id: W55 + reason: Suppressed to test suppression works and skips this test + expectations: + rules: + ELBV2_LISTENER_SSL_POLICY_RULE: SKIP + +- name: Guard suppression for ELBV2_LISTENER_SSL_POLICY_RULE + input: + Resources: + HTTPlistener: + Type: "AWS::ElasticLoadBalancingV2::Listener" + Properties: + DefaultActions: + - Type: "redirect" + RedirectConfig: + Protocol: "HTTPS" + Port: 443 + Host: "#{host}" + Path: "/#{path}" + Query: "#{query}" + StatusCode: "HTTP_301" + LoadBalancerArn: !Ref myLoadBalancer + Port: 80 + Protocol: "HTTP" + SslPolicy: ELBSecurityPolicy-TLS-1-1-2017-01 + Metadata: + guard: + SuppressedRules: + - ELBV2_LISTENER_SSL_POLICY_RULE + expectations: + rules: + ELBV2_LISTENER_SSL_POLICY_RULE: SKIP + +- name: Guard and CFN_NAG suppression for W55 & ELBV2_LISTENER_SSL_POLICY_RULE + input: + Resources: + HTTPlistener: + Type: "AWS::ElasticLoadBalancingV2::Listener" + Properties: + DefaultActions: + - Type: "redirect" + RedirectConfig: + Protocol: "HTTPS" + Port: 443 + Host: "#{host}" + Path: "/#{path}" + Query: "#{query}" + StatusCode: "HTTP_301" + LoadBalancerArn: !Ref myLoadBalancer + Port: 80 + Protocol: "HTTP" + SslPolicy: ELBSecurityPolicy-TLS-1-1-2017-01 + Metadata: + cfn_nag: + rules_to_suppress: + - id: W55 + reason: Suppressed to test suppression works and skips this test + guard: + SuppressedRules: + - ELBV2_LISTENER_SSL_POLICY_RULE + expectations: + rules: + ELBV2_LISTENER_SSL_POLICY_RULE: SKIP diff --git a/rules/aws/elasticache/elasticache_redis_cluster_automatic_backup_check.guard b/rules/aws/elasticache/elasticache_redis_cluster_automatic_backup_check.guard deleted file mode 100644 index 127e693..0000000 --- a/rules/aws/elasticache/elasticache_redis_cluster_automatic_backup_check.guard +++ /dev/null @@ -1,39 +0,0 @@ -# -##################################### -## Gherkin ## -##################################### -# -# Rule Identifier: -# ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK -# -# Description: -# Checks if Amazon DynamoDB table is encrypted with AWS Key Management Service (KMS). -# -# Reports on: -# AWS::ElastiCache::ReplicationGroup -# -# Evaluates: -# AWS CloudFormation -# -# Rule Parameters: -# NA -# -# Scenarios: -# a) SKIP: when there are no AWS::ElastiCache::ReplicationGroup resources present -# b) PASS: when all DynamoDB Tables are encrypted with KMS -# c) FAIL: when all DynamoDB Tables are not are encrypted with KMS -# d) SKIP: when metada has rule suppression for ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK - -let replication_groups_automatedbackup = Resources.*[Type == "AWS::ElastiCache::ReplicationGroup" - Metadata.guard.SuppressedRules not exists or - Metadata.guard.SuppressedRules.* != "ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK" -] - -rule ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK when %replication_groups_automatedbackup !empty { - %replication_groups_automatedbackup.Properties.Engine != "redis" or - %replication_groups_automatedbackup.Properties.SnapshotRetentionLimit > 0 - << - Violation: Amazon ElastiCache Replication Groups are configured with automatic backups - Fix: Set SnapshotRetentionLimit to value greater than zero - >> -} diff --git a/rules/aws/elasticache/elasticache_replication_group_encryption_at_rest.guard b/rules/aws/elasticache/elasticache_replication_group_encryption_at_rest.guard new file mode 100644 index 0000000..57f1b7d --- /dev/null +++ b/rules/aws/elasticache/elasticache_replication_group_encryption_at_rest.guard @@ -0,0 +1,45 @@ +# +##################################### +## AWS Solutions ## +##################################### +# Rule Identifier: +# ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST +# +# Description: +# ElastiCache ReplicationGroup should have encryption enabled for at rest +# +# Reports on: +# AWS::Elasticache::ReplicationGroup +# +# Evaluates: +# AWS CloudFormation +# +# Rule Parameters: +# NA +# +# CFN_NAG Rule Id: +# F25 +# +# Scenarios: +# a) SKIP: when no AWS::Elasticache::ReplicationGroup resources are present +# b) PASS: when all AWS::Elasticache::ReplicationGroup resources have the AtRestEncryptionEnabled property set to true +# c) FAIL: when any AWS::Elasticache::ReplicationGroup resources have the AtRestEncryptionEnabled set to false or absent (default false) +# e) SKIP: when metadata includes the suppression for rule ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST + +# +# Select all AWS::Elasticache::ReplicationGroup resources from incoming template (payload) +# +let elasticache_replication_group_encryption_at_rest = Resources.*[ Type == 'AWS::ElastiCache::ReplicationGroup' + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "F25" + Metadata.guard.SuppressedRules not exists or + Metadata.guard.SuppressedRules.* != "ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST" +] + +rule ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST when %elasticache_replication_group_encryption_at_rest !empty { + %elasticache_replication_group_encryption_at_rest.Properties.AtRestEncryptionEnabled == true + << + Violation: ElastiCache ReplicationGroup should have encryption enabled for at rest + Fix: Set AtRestEncryptionEnabled property to true + >> +} diff --git a/rules/aws/elasticache/elasticache_replication_group_transit_encryption.guard b/rules/aws/elasticache/elasticache_replication_group_transit_encryption.guard new file mode 100644 index 0000000..158e2d4 --- /dev/null +++ b/rules/aws/elasticache/elasticache_replication_group_transit_encryption.guard @@ -0,0 +1,47 @@ +# +##################################### +## AWS Solutions ## +##################################### +# Rule Identifier: +# ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT +# +# Description: +# ElastiCache ReplicationGroup should have encryption enabled for in transit +# +# Reports on: +# AWS::Elasticache::ReplicationGroup +# +# Evaluates: +# AWS CloudFormation +# +# Rule Parameters: +# NA +# +# CFN_NAG Rule Id: +# F33 +# +# Scenarios: +# a) SKIP: when no AWS::Elasticache::ReplicationGroup resources are present +# b) PASS: when all AWS::Elasticache::ReplicationGroup resources have the TransitEncryptionEnabled property set to true +# c) FAIL: when any AWS::Elasticache::ReplicationGroup resources have the TransitEncryptionEnabled set to false or absent (default false) +# e) SKIP: when metadata includes the suppression for rule ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT + +# +# Select all AWS::Elasticache::ReplicationGroup resources from incoming template (payload) +# +let elasticache_replication_group_transit_encryption = Resources.*[ Type == 'AWS::ElastiCache::ReplicationGroup' + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "F33" + Metadata.guard.SuppressedRules not exists or + Metadata.guard.SuppressedRules.* != "ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST" + Properties.Engine == "redis" + Properties.EngineVersion not in [ /^2\..*/, /^3\.1\..*/, /^3\.2\.5$/ ] # v3.2.6 or 4.x+ +] + +rule ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT when %elasticache_replication_group_transit_encryption !empty { + %elasticache_replication_group_transit_encryption.Properties.TransitEncryptionEnabled == true + << + Violation: ElastiCache ReplicationGroup should have encryption enabled for in transit + Fix: Set TransitEncryptionEnabled property to true + >> +} diff --git a/rules/aws/elasticache/tests/elasticache_redis_cluster_automatic_backup_check_tests.yml b/rules/aws/elasticache/tests/elasticache_redis_cluster_automatic_backup_check_tests.yml deleted file mode 100644 index a642ce1..0000000 --- a/rules/aws/elasticache/tests/elasticache_redis_cluster_automatic_backup_check_tests.yml +++ /dev/null @@ -1,96 +0,0 @@ -### -# ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK test -### ---- -- name: Empty, SKIP - input: {} - expectations: - rules: - ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK: SKIP - -- name: No resources, SKIP - input: - Resources: {} - expectations: - rules: - ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK: SKIP - -- name: redis engine type and snapshotretentionlimit greater than 0, PASS - input: - Resources: - myReplicationGroup: - Type: 'AWS::ElastiCache::ReplicationGroup' - Properties: - ReplicationGroupDescription: my description - NumCacheClusters: 2 - Engine: redis - CacheNodeType: cache.m3.medium - AutomaticFailoverEnabled: 'true' - CacheSubnetGroupName: subnetgroup - EngineVersion: 2.8.6 - PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' - SnapshotRetentionLimit: 15 - SnapshotWindow: '03:30-05:30' - expectations: - rules: - ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK: PASS - -- name: engine type redis but snapshotretentionlimit set to 0 - input: - Resources: - myReplicationGroup: - Type: 'AWS::ElastiCache::ReplicationGroup' - Properties: - ReplicationGroupDescription: my description - NumCacheClusters: 2 - Engine: redis - CacheNodeType: cache.m3.medium - AutomaticFailoverEnabled: 'true' - CacheSubnetGroupName: subnetgroup - EngineVersion: 2.8.6 - PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' - SnapshotRetentionLimit: 0 - SnapshotWindow: '03:30-05:30' - expectations: - rules: - ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK: FAIL - -- name: engine type redis but snapshotretentionlimit not set - input: - Resources: - myReplicationGroup: - Type: 'AWS::ElastiCache::ReplicationGroup' - Properties: - ReplicationGroupDescription: my description - NumCacheClusters: 2 - Engine: redis - CacheNodeType: cache.m3.medium - AutomaticFailoverEnabled: 'true' - CacheSubnetGroupName: subnetgroup - EngineVersion: 2.8.6 - PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' - expectations: - rules: - ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK: FAIL - -- name: rule suppressed, SKIP - input: - Resources: - myReplicationGroup: - Type: 'AWS::ElastiCache::ReplicationGroup' - Metadata: - guard: - SuppressedRules: - - ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK - Properties: - ReplicationGroupDescription: my description - NumCacheClusters: 2 - Engine: redis - CacheNodeType: cache.m3.medium - AutomaticFailoverEnabled: 'true' - CacheSubnetGroupName: subnetgroup - EngineVersion: 2.8.6 - PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' - expectations: - rules: - ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK: SKIP \ No newline at end of file diff --git a/rules/aws/elasticache/tests/elasticache_replication_group_encryption_at_rest_tests.yml b/rules/aws/elasticache/tests/elasticache_replication_group_encryption_at_rest_tests.yml new file mode 100644 index 0000000..a1239b2 --- /dev/null +++ b/rules/aws/elasticache/tests/elasticache_replication_group_encryption_at_rest_tests.yml @@ -0,0 +1,163 @@ +### +# ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST tests +### +--- +- name: Empty, SKIP + input: {} + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST: SKIP + +- name: No resources, SKIP + input: + Resources: {} + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST: SKIP + +- name: Elasticache with default AtRestEncryptionEnabled (false), FAIL + input: + Resources: + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + ReplicationGroupDescription: my description + NumCacheClusters: '2' + Engine: redis + CacheNodeType: cache.m3.medium + AutomaticFailoverEnabled: 'true' + CacheSubnetGroupName: subnetgroup + EngineVersion: 2.8.6 + PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' + SnapshotRetentionLimit: '4' + SnapshotWindow: '03:30-05:30' + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST: FAIL + +- name: Elasticache with AtRestEncryptionEnabled = false, FAIL + input: + Resources: + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + AtRestEncryptionEnabled: false + ReplicationGroupDescription: my description + NumCacheClusters: '2' + Engine: redis + CacheNodeType: cache.m3.medium + AutomaticFailoverEnabled: 'true' + CacheSubnetGroupName: subnetgroup + EngineVersion: 2.8.6 + PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' + SnapshotRetentionLimit: '4' + SnapshotWindow: '03:30-05:30' + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST: FAIL + +- name: Elasticache with AtRestEncryptionEnabled = true, PASS + input: + Resources: + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + AtRestEncryptionEnabled: true + ReplicationGroupDescription: my description + NumCacheClusters: '2' + Engine: redis + CacheNodeType: cache.m3.medium + AutomaticFailoverEnabled: 'true' + CacheSubnetGroupName: subnetgroup + EngineVersion: 2.8.6 + PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' + SnapshotRetentionLimit: '4' + SnapshotWindow: '03:30-05:30' + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST: PASS + +## +## Suppression +## +- name: F25 CFN_NAG Suppression, SKIP + input: + Resources: + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + AtRestEncryptionEnabled: false + ReplicationGroupDescription: my description + NumCacheClusters: '2' + Engine: redis + CacheNodeType: cache.m3.medium + AutomaticFailoverEnabled: 'true' + CacheSubnetGroupName: subnetgroup + EngineVersion: 2.8.6 + PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' + SnapshotRetentionLimit: '4' + SnapshotWindow: '03:30-05:30' + Metadata: + cfn_nag: + rules_to_suppress: + - id: F25 + reason: Suppressed for a very good reason + guard: + SuppressedRules: + - ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST: Suppressed for a very good reason + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST: SKIP + +- name: ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST Guard Suppression, SKIP + input: + Resources: + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + AtRestEncryptionEnabled: false + ReplicationGroupDescription: my description + NumCacheClusters: '2' + Engine: redis + CacheNodeType: cache.m3.medium + AutomaticFailoverEnabled: 'true' + CacheSubnetGroupName: subnetgroup + EngineVersion: 2.8.6 + PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' + SnapshotRetentionLimit: '4' + SnapshotWindow: '03:30-05:30' + Metadata: + guard: + SuppressedRules: + - ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST: Suppressed for a very good reason + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST: SKIP + +- name: CFN_NAG & Guard Suppression, SKIP + input: + Resources: + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + ReplicationGroupDescription: my description + NumCacheClusters: '2' + Engine: redis + CacheNodeType: cache.m3.medium + AutomaticFailoverEnabled: 'true' + CacheSubnetGroupName: subnetgroup + EngineVersion: 2.8.6 + PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' + SnapshotRetentionLimit: '4' + SnapshotWindow: '03:30-05:30' + Metadata: + cfn_nag: + rules_to_suppress: + - id: F25 + reason: Suppressed for a very good reason + guard: + SuppressedRules: + - ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST: Suppressed for a very good reason + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_AT_REST: SKIP \ No newline at end of file diff --git a/rules/aws/elasticache/tests/elasticache_replication_group_transit_encryption_tests.yml b/rules/aws/elasticache/tests/elasticache_replication_group_transit_encryption_tests.yml new file mode 100644 index 0000000..dbf9f31 --- /dev/null +++ b/rules/aws/elasticache/tests/elasticache_replication_group_transit_encryption_tests.yml @@ -0,0 +1,205 @@ +### +# ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT tests +### +--- +- name: Empty, SKIP + input: {} + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT: SKIP + +- name: No resources, SKIP + input: + Resources: {} + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT: SKIP + +- name: Elasticache old (ignored) version 2.x, SKIP + input: + Resources: + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + ReplicationGroupDescription: my description + NumCacheClusters: '2' + Engine: redis + CacheNodeType: cache.m3.medium + AutomaticFailoverEnabled: 'true' + CacheSubnetGroupName: subnetgroup + EngineVersion: 2.8.6 + PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' + SnapshotRetentionLimit: '4' + SnapshotWindow: '03:30-05:30' + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT: SKIP + +- name: Elasticache old (ignored) version 3.x, SKIP + input: + Resources: + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + ReplicationGroupDescription: my description + NumCacheClusters: '2' + Engine: redis + CacheNodeType: cache.m3.medium + AutomaticFailoverEnabled: 'true' + CacheSubnetGroupName: subnetgroup + EngineVersion: 3.2.5 + PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' + SnapshotRetentionLimit: '4' + SnapshotWindow: '03:30-05:30' + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT: SKIP + +- name: Elasticache default to false, FAIL + input: + Resources: + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + ReplicationGroupDescription: my description + NumCacheClusters: '2' + Engine: redis + CacheNodeType: cache.m3.medium + AutomaticFailoverEnabled: 'true' + CacheSubnetGroupName: subnetgroup + EngineVersion: 4.0.10 + PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' + SnapshotRetentionLimit: '4' + SnapshotWindow: '03:30-05:30' + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT: FAIL + +- name: Elasticache with TransitEncryptionEnabled = false, FAIL + input: + Resources: + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + AtRestEncryptionEnabled: false + ReplicationGroupDescription: my description + NumCacheClusters: '2' + Engine: redis + CacheNodeType: cache.m3.medium + AutomaticFailoverEnabled: 'true' + CacheSubnetGroupName: subnetgroup + EngineVersion: 4.0.10 + PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' + SnapshotRetentionLimit: '4' + SnapshotWindow: '03:30-05:30' + TransitEncryptionEnabled: false + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT: FAIL + +- name: Elasticache with TransitEncryptionEnabled = true, PASS + input: + Resources: + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + AtRestEncryptionEnabled: true + ReplicationGroupDescription: my description + NumCacheClusters: '2' + Engine: redis + CacheNodeType: cache.m3.medium + AutomaticFailoverEnabled: 'true' + CacheSubnetGroupName: subnetgroup + EngineVersion: 4.0.10 + PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' + SnapshotRetentionLimit: '4' + SnapshotWindow: '03:30-05:30' + TransitEncryptionEnabled: true + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT: PASS + +## +## Suppression +## +- name: F25 CFN_NAG Suppression, SKIP + input: + Resources: + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + AtRestEncryptionEnabled: false + ReplicationGroupDescription: my description + NumCacheClusters: '2' + Engine: redis + CacheNodeType: cache.m3.medium + AutomaticFailoverEnabled: 'true' + CacheSubnetGroupName: subnetgroup + EngineVersion: 4.0.10 + PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' + SnapshotRetentionLimit: '4' + SnapshotWindow: '03:30-05:30' + Metadata: + cfn_nag: + rules_to_suppress: + - id: F33 + reason: Suppressed for a very good reason + guard: + SuppressedRules: + - ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT: Suppressed for a very good reason + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT: SKIP + +- name: ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT Guard Suppression, SKIP + input: + Resources: + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + AtRestEncryptionEnabled: false + ReplicationGroupDescription: my description + NumCacheClusters: '2' + Engine: redis + CacheNodeType: cache.m3.medium + AutomaticFailoverEnabled: 'true' + CacheSubnetGroupName: subnetgroup + EngineVersion: 4.0.10 + PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' + SnapshotRetentionLimit: '4' + SnapshotWindow: '03:30-05:30' + Metadata: + guard: + SuppressedRules: + - ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT: Suppressed for a very good reason + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT: SKIP + +- name: CFN_NAG & Guard Suppression, SKIP + input: + Resources: + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + ReplicationGroupDescription: my description + NumCacheClusters: '2' + Engine: redis + CacheNodeType: cache.m3.medium + AutomaticFailoverEnabled: 'true' + CacheSubnetGroupName: subnetgroup + EngineVersion: 4.0.10 + PreferredMaintenanceWindow: 'wed:09:25-wed:22:30' + SnapshotRetentionLimit: '4' + SnapshotWindow: '03:30-05:30' + Metadata: + cfn_nag: + rules_to_suppress: + - id: F33 + reason: Suppressed for a very good reason + guard: + SuppressedRules: + - ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT: Suppressed for a very good reason + expectations: + rules: + ELASTICACHE_REPLICATION_GROUP_ENCRYPTION_IN_TRANSIT: SKIP \ No newline at end of file