diff --git a/rules/aws/aws_batch/batch_job_container_properties_privileged_rule.guard b/rules/aws/aws_batch/batch_job_container_properties_privileged_rule.guard new file mode 100644 index 0000000..fec8a3b --- /dev/null +++ b/rules/aws/aws_batch/batch_job_container_properties_privileged_rule.guard @@ -0,0 +1,50 @@ +# +##################################### +## AWS Solutions ## +##################################### +# Rule Identifier: +# BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE +# +# Description: +# Batch Job Definition Container Properties should not have Privileged set to true +# +# Reports on: +# AWS::Batch::JobDefinition +# +# Evaluates: +# AWS CloudFormation +# +# Rule Parameters: +# NA +# +# CFN_NAG Rule Id: +# W34 +# +# Scenarios: +# a) SKIP: when there is no Batch Job resource present. +# b) PASS: when Batch Job resources does not have container properties or privileged is set to false. +# c) FAIL: when Batch Job resources does have container properties and privileged is set to true. +# d) SKIP: when metadata has rule suppression for BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE + +# +# Select all Batch Job Definition resources from incoming template (payload) +# +let batch_job_container_properties_privileged_rule = Resources.*[ Type == 'AWS::Batch::JobDefinition' + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "W34" + Metadata.guard.SuppressedRules not exists or + Metadata.guard.SuppressedRules.* != "BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE" +] + +rule BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE when %batch_job_container_properties_privileged_rule !empty { + let violations = %batch_job_container_properties_privileged_rule[ + Type == 'AWS::Batch::JobDefinition' + Properties.ContainerProperties exists + Properties.ContainerProperties.Privileged == true + ] + %violations empty + << + Violation: Batch job definition resource has container properties set to true + Fix: set privileged to false or remove privileged field to make it false by default. + >> +} diff --git a/rules/aws/aws_batch/tests/batch_job_container_properties_privileged_rule_tests.yml b/rules/aws/aws_batch/tests/batch_job_container_properties_privileged_rule_tests.yml new file mode 100644 index 0000000..5366206 --- /dev/null +++ b/rules/aws/aws_batch/tests/batch_job_container_properties_privileged_rule_tests.yml @@ -0,0 +1,207 @@ +### +# BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE tests +### +--- +- name: Empty + input: {} + expectations: + rules: + BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE: SKIP + +- name: No resources + input: + Resources: {} + expectations: + rules: + BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE: SKIP + +- name: Batch Job resource with no container properties + input: + Resources: + JobDefinition: + Type: AWS::Batch::JobDefinition + Properties: + Type: container + JobDefinitionName: nvidia-smi + Parameters: Json + Timeout: 6000 + expectations: + rules: + BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE: PASS + +- name: Batch Job resource with container properties and privileged set to false + input: + Resources: + JobDefinition: + Type: AWS::Batch::JobDefinition + Properties: + Type: container + JobDefinitionName: nvidia-smi + ContainerProperties: + MountPoints: + - ReadOnly: false + SourceVolume: nvidia + ContainerPath: /usr/local/nvidia + Volumes: + - Host: + SourcePath: /var/lib/nvidia-docker/volumes/nvidia_driver/latest + Name: nvidia + Command: + - nvidia-smi + Privileged: false + JobRoleArn: String + ReadonlyRootFilesystem: true + ResourceRequirements: + - Type: MEMORY + Value: '2000' + - Type: VCPU + Value: '2' + Image: nvidia/cuda + expectations: + rules: + BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE: PASS + +- name: Batch Job resource with container properties and privileged set to true + input: + Resources: + JobDefinition: + Type: AWS::Batch::JobDefinition + Properties: + Type: container + JobDefinitionName: nvidia-smi + ContainerProperties: + MountPoints: + - ReadOnly: false + SourceVolume: nvidia + ContainerPath: /usr/local/nvidia + Volumes: + - Host: + SourcePath: /var/lib/nvidia-docker/volumes/nvidia_driver/latest + Name: nvidia + Command: + - nvidia-smi + Privileged: true + JobRoleArn: String + ReadonlyRootFilesystem: true + ResourceRequirements: + - Type: MEMORY + Value: '2000' + - Type: VCPU + Value: '2' + Image: nvidia/cuda + expectations: + rules: + BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE: FAIL + +- name: CFN_NAG suppression for W34 + input: + Resources: + JobDefinition: + Type: AWS::Batch::JobDefinition + Properties: + Type: container + JobDefinitionName: nvidia-smi + ContainerProperties: + MountPoints: + - ReadOnly: false + SourceVolume: nvidia + ContainerPath: /usr/local/nvidia + Volumes: + - Host: + SourcePath: /var/lib/nvidia-docker/volumes/nvidia_driver/latest + Name: nvidia + Command: + - nvidia-smi + Privileged: true + JobRoleArn: String + ReadonlyRootFilesystem: true + ResourceRequirements: + - Type: MEMORY + Value: '2000' + - Type: VCPU + Value: '2' + Image: nvidia/cuda + Metadata: + cfn_nag: + rules_to_suppress: + - id: W34 + reason: Suppressed to test suppression works and skips this test + expectations: + rules: + BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE: SKIP + +- name: Guard suppression for BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE + input: + Resources: + JobDefinition: + Type: AWS::Batch::JobDefinition + Properties: + Type: container + JobDefinitionName: nvidia-smi + ContainerProperties: + MountPoints: + - ReadOnly: false + SourceVolume: nvidia + ContainerPath: /usr/local/nvidia + Volumes: + - Host: + SourcePath: /var/lib/nvidia-docker/volumes/nvidia_driver/latest + Name: nvidia + Command: + - nvidia-smi + Privileged: true + JobRoleArn: String + ReadonlyRootFilesystem: true + ResourceRequirements: + - Type: MEMORY + Value: '2000' + - Type: VCPU + Value: '2' + Image: nvidia/cuda + Metadata: + guard: + SuppressedRules: + - BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE + expectations: + rules: + BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE: SKIP + +- name: Guard and CFN_NAG suppression for W34 & BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE + input: + Resources: + JobDefinition: + Type: AWS::Batch::JobDefinition + Properties: + Type: container + JobDefinitionName: nvidia-smi + ContainerProperties: + MountPoints: + - ReadOnly: false + SourceVolume: nvidia + ContainerPath: /usr/local/nvidia + Volumes: + - Host: + SourcePath: /var/lib/nvidia-docker/volumes/nvidia_driver/latest + Name: nvidia + Command: + - nvidia-smi + Privileged: true + JobRoleArn: String + ReadonlyRootFilesystem: true + ResourceRequirements: + - Type: MEMORY + Value: '2000' + - Type: VCPU + Value: '2' + Image: nvidia/cuda + Metadata: + cfn_nag: + rules_to_suppress: + - id: W34 + reason: Suppressed to test suppression works and skips this test + guard: + SuppressedRules: + - BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE + expectations: + rules: + BATCH_JOB_CONTAINER_PROPERTIES_PRIVILEGED_RULE: SKIP diff --git a/rules/aws/aws_cloudformation/cfn_no_explicit_resource_names.guard b/rules/aws/aws_cloudformation/cfn_no_explicit_resource_names.guard new file mode 100644 index 0000000..54dfa16 --- /dev/null +++ b/rules/aws/aws_cloudformation/cfn_no_explicit_resource_names.guard @@ -0,0 +1,131 @@ +# +##################################### +## AWS Solutions ## +##################################### +# Rule Identifier: +# CFN_NO_EXPLICIT_RESOURCE_NAMES +# +# Description: +# Checks that the template does not explicitely name resources. +# +# Reports on: +# Various +# +# Evaluates: +# AWS CloudFormation +# +# Rule Parameters: +# NA +# +# CFN_NAG Rule Id: +# W28 +# +# Scenarios: +# a) SKIP: when none of the covered resources types are present +# b) PASS: when none of the applicable types have static resource names +# c) FAIL: when any applicable types has a static resource name +# d) SKIP: when metada has rule suppression for CFN_NO_EXPLICIT_RESOURCE_NAMES or CFN_NAG W28 + +let applicable_types = [ + "AWS::ApiGateway::ApiKey", + "AWS::CloudWatch::Alarm", + "AWS::CodeDeploy::DeploymentConfig", + "AWS::CodeDeploy::DeploymentGroup", + "AWS::DynamoDB::Table", + "AWS::EC2::SecurityGroup", + "AWS::ECR::Repository", + "AWS::ElasticLoadBalancingV2::LoadBalancer", + "AWS::Elasticsearch::Domain", + "AWS::IAM::Group", + "AWS::IAM::ManagedPolicy", + "AWS::IAM::Role", + "AWS::Kinesis::Stream", + "AWS::RDS::DBInstance" +] + +# Select applicable resources less suppressed resources +let cloudformation_no_static_name_resources = Resources.*[Type in %applicable_types + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "W28" + Metadata.guard.SuppressedRules not exists or + Metadata.guard.SuppressedRules.* != "CFN_NO_EXPLICIT_RESOURCE_NAMES" +] + +rule CFN_NO_EXPLICIT_RESOURCE_NAMES + when %cloudformation_no_static_name_resources !empty { + AWS::ApiGateway::ApiKey { + Properties{ + Name empty + } + } + AWS::CloudWatch::Alarm { + Properties{ + AlarmName empty + } + } + AWS::CodeDeploy::DeploymentConfig { + Properties{ + DeploymentConfigName empty + } + } + AWS::CodeDeploy::DeploymentGroup { + Properties{ + DeploymentGroupName empty + } + } + AWS::DynamoDB::Table { + Properties{ + TableName empty + } + } + AWS::EC2::SecurityGroup { + Properties{ + GroupName empty + } + } + AWS::ECR::Repository { + Properties{ + RepositoryName empty + } + } + AWS::ElasticLoadBalancingV2::LoadBalancer { + Properties{ + Name empty + } + } + AWS::Elasticsearch::Domain { + Properties{ + DomainName empty + } + } + AWS::IAM::Group { + Properties{ + GroupName empty + } + } + AWS::IAM::ManagedPolicy { + Properties{ + ManagedPolicyName empty + } + } + AWS::IAM::Role { + Properties{ + RoleName empty + } + } + AWS::Kinesis::Stream { + Properties{ + Name empty + } + } + AWS::RDS::DBInstance { + Properties{ + DBInstanceIdentifier empty + } + } + %cloudformation_no_static_name_resources not empty + << + Violation: Resource found with an explicit name, this disallows updates that require replacement of this resource. + Fix: Remove static name from the resource + >> +} diff --git a/rules/aws/aws_cloudformation/tests/cfn_no_explicit_resource_names_tests.yml b/rules/aws/aws_cloudformation/tests/cfn_no_explicit_resource_names_tests.yml new file mode 100644 index 0000000..2ed3742 --- /dev/null +++ b/rules/aws/aws_cloudformation/tests/cfn_no_explicit_resource_names_tests.yml @@ -0,0 +1,924 @@ +### +# CFN_NO_EXPLICIT_RESOURCE_NAMES +### +--- +- name: Empty, SKIP + input: {} + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: SKIP + +- name: No resources, SKIP + input: + Resources: {} + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: SKIP + +- name: Unlisted resource type, SKIP + input: + Resources: + ExampleResource: + Type: AWS::SomeResource::Value + Properties: + Name: loremipsum + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: SKIP + +- name: ApiGateway ApiKey without static name, PASS + input: + Resources: + ExampleResource: + Type: AWS::ApiGateway::ApiKey + Properties: + Description: CloudFormation API Key V1 + Enabled: true + StageKeys: + - RestApiId: !Ref RestApi + StageName: Test + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: PASS + static_name: PASS + no_static_names: PASS + +- name: ApiGateway ApiKey with static name, FAIL + input: + Resources: + ExampleResource: + Type: AWS::ApiGateway::ApiKey + Properties: + Name: TestApiKey + Description: CloudFormation API Key V1 + Enabled: true + StageKeys: + - RestApiId: !Ref RestApi + StageName: Test + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: FAIL + static_name: FAIL + no_static_names: FAIL + +- name: CloudWatch Alarm without static AlarmName, PASS + input: + Resources: + ExampleResource: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmDescription: Lambda invocations + ComparisonOperator: LessThanLowerOrGreaterThanUpperThreshold + EvaluationPeriods: 1 + Metrics: + - Expression: ANOMALY_DETECTION_BAND(m1, 2) + Id: ad1 + - Id: m1 + MetricStat: + Metric: + MetricName: Invocations + Namespace: AWS/Lambda + Period: !!int 86400 + Stat: Sum + ThresholdMetricId: ad1 + TreatMissingData: breaching + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: PASS + static_alarmname: PASS + +- name: CloudWatch Alarm with static AlarmName, FAIL + input: + Resources: + ExampleResource: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmDescription: Lambda invocations + AlarmName: LambdaInvocationsAlarm + ComparisonOperator: LessThanLowerOrGreaterThanUpperThreshold + EvaluationPeriods: 1 + Metrics: + - Expression: ANOMALY_DETECTION_BAND(m1, 2) + Id: ad1 + - Id: m1 + MetricStat: + Metric: + MetricName: Invocations + Namespace: AWS/Lambda + Period: !!int 86400 + Stat: Sum + ThresholdMetricId: ad1 + TreatMissingData: breaching + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: FAIL + static_alarmname: FAIL + +- name: CodeDeploy DeploymentConfig without static deploymentConfigName, PASS + input: + Resources: + ExampleResource: + Type: AWS::CodeDeploy::DeploymentConfig + Properties: + MinimumHealthyHosts: + Type: "FLEET_PERCENT" + Value: 75 + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: PASS + static_deploymentconfigname: PASS + +- name: CodeDeploy DeploymentConfig without static deploymentConfigName, FAIL + input: + Resources: + ExampleResource: + Type: AWS::CodeDeploy::DeploymentConfig + Properties: + DeploymentConfigName: foo-bar-baz + MinimumHealthyHosts: + Type: "FLEET_PERCENT" + Value: 75 + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: FAIL + static_deploymentconfigname: FAIL + +- name: CodeDeploy DeploymentGroup without static deploymentGroupName, PASS + input: + Resources: + ExampleResource: + Type: AWS::CodeDeploy::DeploymentGroup + Properties: + ApplicationName: + Ref: "ApplicationName" + AutoScalingGroups: + Ref: CodeDeployAutoScalingGroups + Deployment: + Description: "A sample deployment" + IgnoreApplicationStopFailures: true + Revision: + RevisionType: GitHub + GitHubLocation: + CommitId: + Ref: CommitId + Repository: + Ref: Repository + ServiceRoleArn: + Fn::GetAtt: [ RoleArn, Arn ] + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: PASS + static_deploymentgroupname: PASS + +- name: CodeDeploy DeploymentGroup with static deploymentGroupName, FAIL + input: + Resources: + ExampleResource: + Type: AWS::CodeDeploy::DeploymentGroup + Properties: + DeploymentGroupName: foo-bar-baz + ApplicationName: + Ref: "ApplicationName" + AutoScalingGroups: + Ref: CodeDeployAutoScalingGroups + Deployment: + Description: "A sample deployment" + IgnoreApplicationStopFailures: true + Revision: + RevisionType: GitHub + GitHubLocation: + CommitId: + Ref: CommitId + Repository: + Ref: Repository + ServiceRoleArn: + Fn::GetAtt: [ RoleArn, Arn ] + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: FAIL + static_deploymentgroupname: FAIL + +- name: DynamoDB table without static name, PASS + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: PASS + static_name: PASS + input: + Resources: + ExampleResource: + Type: AWS::DynamoDB::Table + Properties: + AttributeDefinitions: + - + AttributeName: "Album" + AttributeType: "S" + - + AttributeName: "Artist" + AttributeType: "S" + - + AttributeName: "Sales" + AttributeType: "N" + - + AttributeName: "NumberOfSongs" + AttributeType: "N" + KeySchema: + - + AttributeName: "Album" + KeyType: "HASH" + - + AttributeName: "Artist" + KeyType: "RANGE" + ProvisionedThroughput: + ReadCapacityUnits: "5" + WriteCapacityUnits: "5" + GlobalSecondaryIndexes: + - + IndexName: "myGSI" + KeySchema: + - + AttributeName: "Sales" + KeyType: "HASH" + - + AttributeName: "Artist" + KeyType: "RANGE" + Projection: + NonKeyAttributes: + - "Album" + - "NumberOfSongs" + ProjectionType: "INCLUDE" + ProvisionedThroughput: + ReadCapacityUnits: "5" + WriteCapacityUnits: "5" + - + IndexName: "myGSI2" + KeySchema: + - + AttributeName: "NumberOfSongs" + KeyType: "HASH" + - + AttributeName: "Sales" + KeyType: "RANGE" + Projection: + NonKeyAttributes: + - "Album" + - "Artist" + ProjectionType: "INCLUDE" + ProvisionedThroughput: + ReadCapacityUnits: "5" + WriteCapacityUnits: "5" + LocalSecondaryIndexes: + - + IndexName: "myLSI" + KeySchema: + - + AttributeName: "Album" + KeyType: "HASH" + - + AttributeName: "Sales" + KeyType: "RANGE" + Projection: + NonKeyAttributes: + - "Artist" + - "NumberOfSongs" + ProjectionType: "INCLUDE" + +- name: DynamoDB table with static name, FAIL + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: FAIL + static_name: FAIL + input: + Resources: + ExampleResource: + Type: AWS::DynamoDB::Table + Properties: + AttributeDefinitions: + - + AttributeName: "Album" + AttributeType: "S" + - + AttributeName: "Artist" + AttributeType: "S" + - + AttributeName: "Sales" + AttributeType: "N" + - + AttributeName: "NumberOfSongs" + AttributeType: "N" + KeySchema: + - + AttributeName: "Album" + KeyType: "HASH" + - + AttributeName: "Artist" + KeyType: "RANGE" + ProvisionedThroughput: + ReadCapacityUnits: "5" + WriteCapacityUnits: "5" + TableName: "myTableName" + GlobalSecondaryIndexes: + - + IndexName: "myGSI" + KeySchema: + - + AttributeName: "Sales" + KeyType: "HASH" + - + AttributeName: "Artist" + KeyType: "RANGE" + Projection: + NonKeyAttributes: + - "Album" + - "NumberOfSongs" + ProjectionType: "INCLUDE" + ProvisionedThroughput: + ReadCapacityUnits: "5" + WriteCapacityUnits: "5" + - + IndexName: "myGSI2" + KeySchema: + - + AttributeName: "NumberOfSongs" + KeyType: "HASH" + - + AttributeName: "Sales" + KeyType: "RANGE" + Projection: + NonKeyAttributes: + - "Album" + - "Artist" + ProjectionType: "INCLUDE" + ProvisionedThroughput: + ReadCapacityUnits: "5" + WriteCapacityUnits: "5" + LocalSecondaryIndexes: + - + IndexName: "myLSI" + KeySchema: + - + AttributeName: "Album" + KeyType: "HASH" + - + AttributeName: "Sales" + KeyType: "RANGE" + Projection: + NonKeyAttributes: + - "Artist" + - "NumberOfSongs" + ProjectionType: "INCLUDE" + +- name: EC2 SecurityGroup without static GroupName, PASS + input: + Resources: + ExampleResource: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Allow http to client host + VpcId: !Ref myVPC + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 80 + ToPort: 80 + CidrIp: 0.0.0.0/0 + SecurityGroupEgress: + - IpProtocol: tcp + FromPort: 80 + ToPort: 80 + CidrIp: 0.0.0.0/0 + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: PASS + static_groupname: PASS + +- name: EC2 SecurityGroup with static GroupName, FAIL + input: + Resources: + ExampleResource: + Type: AWS::EC2::SecurityGroup + Properties: + GroupName: MySecurityGroup + GroupDescription: Allow http to client host + VpcId: !Ref myVPC + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 80 + ToPort: 80 + CidrIp: 0.0.0.0/0 + SecurityGroupEgress: + - IpProtocol: tcp + FromPort: 80 + ToPort: 80 + CidrIp: 0.0.0.0/0 + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: FAIL + static_groupname: FAIL + +- name: ECR Repository without static repositoryName, PASS + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: PASS + static_repositoryname: PASS + input: + Resources: + ExampleResource: + Type: AWS::ECR::Repository + Properties: + RepositoryPolicyText: + Version: "2012-10-17" + Statement: + - + Sid: AllowPushPull + Effect: Allow + Principal: + AWS: + - "arn:aws:iam::123456789012:user/Bob" + - "arn:aws:iam::123456789012:user/Alice" + Action: + - "ecr:GetDownloadUrlForLayer" + - "ecr:BatchGetImage" + - "ecr:BatchCheckLayerAvailability" + - "ecr:PutImage" + - "ecr:InitiateLayerUpload" + - "ecr:UploadLayerPart" + - "ecr:CompleteLayerUpload" + +- name: ECR Repository with static repositoryName, FAIL + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: FAIL + static_repositoryname: FAIL + input: + Resources: + ExampleResource: + Type: AWS::ECR::Repository + Properties: + RepositoryName: "test-repository" + RepositoryPolicyText: + Version: "2012-10-17" + Statement: + - + Sid: AllowPushPull + Effect: Allow + Principal: + AWS: + - "arn:aws:iam::123456789012:user/Bob" + - "arn:aws:iam::123456789012:user/Alice" + Action: + - "ecr:GetDownloadUrlForLayer" + - "ecr:BatchGetImage" + - "ecr:BatchCheckLayerAvailability" + - "ecr:PutImage" + - "ecr:InitiateLayerUpload" + - "ecr:UploadLayerPart" + - "ecr:CompleteLayerUpload" + +- name: ELB without static name, PASS + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: PASS + static_name: PASS + input: + Resources: + ExampleResource: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Type: application + Scheme: internet-facing + Subnets: + - !Ref SubnetIdA + - !Ref SubnetIdB + SecurityGroups: + - !Ref ELB_SG_Id + +- name: ELB with static name, FAIL + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: FAIL + static_name: FAIL + input: + Resources: + ExampleResource: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Type: application + Name: "foo-bar-alb" + Scheme: internet-facing + Subnets: + - !Ref SubnetIdA + - !Ref SubnetIdB + SecurityGroups: + - !Ref ELB_SG_Id + +- name: ElasticSearch Domain without static domainName, PASS + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: PASS + static_domainname: PASS + input: + Resources: + ExampleResource: + Type: AWS::Elasticsearch::Domain + Properties: + ElasticsearchVersion: '7.10' + ElasticsearchClusterConfig: + DedicatedMasterEnabled: true + InstanceCount: '2' + ZoneAwarenessEnabled: true + InstanceType: 'm3.medium.elasticsearch' + DedicatedMasterType: 'm3.medium.elasticsearch' + DedicatedMasterCount: '3' + EBSOptions: + EBSEnabled: true + Iops: '0' + VolumeSize: '20' + VolumeType: 'gp2' + AccessPolicies: + Version: '2012-10-17' + Statement: + - + Effect: 'Allow' + Principal: + AWS: 'arn:aws:iam::123456789012:user/es-user' + Action: 'es:*' + Resource: 'arn:aws:es:us-east-1:846973539254:domain/test/*' + LogPublishingOptions: + ES_APPLICATION_LOGS: + CloudWatchLogsLogGroupArn: 'arn:aws:logs:us-east-1:123456789012:log-group:/aws/opensearchservice/domains/es-application-logs' + Enabled: true + SEARCH_SLOW_LOGS: + CloudWatchLogsLogGroupArn: 'arn:aws:logs:us-east-1:123456789012:log-group:/aws/opensearchservice/domains/es-slow-logs' + Enabled: true + INDEX_SLOW_LOGS: + CloudWatchLogsLogGroupArn: 'arn:aws:logs:us-east-1:123456789012:log-group:/aws/opensearchservice/domains/es-index-slow-logs' + Enabled: true + AdvancedOptions: + rest.action.multi.allow_explicit_index: true + +- name: ElasticSearch Domain with static domainName, FAIL + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: FAIL + static_domainname: FAIL + input: + Resources: + ExampleResource: + Type: AWS::Elasticsearch::Domain + Properties: + DomainName: 'foo-bar-domain' + ElasticsearchVersion: '7.10' + ElasticsearchClusterConfig: + DedicatedMasterEnabled: true + InstanceCount: '2' + ZoneAwarenessEnabled: true + InstanceType: 'm3.medium.elasticsearch' + DedicatedMasterType: 'm3.medium.elasticsearch' + DedicatedMasterCount: '3' + EBSOptions: + EBSEnabled: true + Iops: '0' + VolumeSize: '20' + VolumeType: 'gp2' + AccessPolicies: + Version: '2012-10-17' + Statement: + - + Effect: 'Allow' + Principal: + AWS: 'arn:aws:iam::123456789012:user/es-user' + Action: 'es:*' + Resource: 'arn:aws:es:us-east-1:846973539254:domain/test/*' + LogPublishingOptions: + ES_APPLICATION_LOGS: + CloudWatchLogsLogGroupArn: 'arn:aws:logs:us-east-1:123456789012:log-group:/aws/opensearchservice/domains/es-application-logs' + Enabled: true + SEARCH_SLOW_LOGS: + CloudWatchLogsLogGroupArn: 'arn:aws:logs:us-east-1:123456789012:log-group:/aws/opensearchservice/domains/es-slow-logs' + Enabled: true + INDEX_SLOW_LOGS: + CloudWatchLogsLogGroupArn: 'arn:aws:logs:us-east-1:123456789012:log-group:/aws/opensearchservice/domains/es-index-slow-logs' + Enabled: true + AdvancedOptions: + rest.action.multi.allow_explicit_index: true + +- name: IAM Group without static groupName, PASS + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: PASS + static_groupname: PASS + input: + Resources: + ExampleResource: + Type: AWS::IAM::Group + Properties: + ManagedPolicyArns: + - !Ref GroupMP + Path: / + +- name: IAM Group without static groupName, FAIL + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: FAIL + static_groupname: FAIL + input: + Resources: + ExampleResource: + Type: AWS::IAM::Group + Properties: + GroupName: foobarbaz + ManagedPolicyArns: + - !Ref GroupMP + Path: / + +- name: IAM ManagedPolicy without static managedPolicyName, PASS + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: PASS + static_managedpolicyname: PASS + input: + Resources: + ExampleResource: + Type: AWS::IAM::ManagedPolicy + Properties: + Description: Policy for creating a test database + Path: / + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: 'rds:CreateDBInstance' + Resource: !Join + - '' + - - 'arn:aws:rds:' + - !Ref 'AWS::Region' + - ':' + - !Ref 'AWS::AccountId' + - ':db:test*' + Condition: + StringEquals: + 'rds:DatabaseEngine': mysql + - Effect: Allow + Action: 'rds:CreateDBInstance' + Resource: !Join + - '' + - - 'arn:aws:rds:' + - !Ref 'AWS::Region' + - ':' + - !Ref 'AWS::AccountId' + - ':db:test*' + Condition: + StringEquals: + 'rds:DatabaseClass': db.t2.micro + Groups: + - TestDBGroup + +- name: IAM ManagedPolicy with static managedPolicyName, FAIL + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: FAIL + static_managedpolicyname: PASS + input: + Resources: + ExampleResource: + Type: AWS::IAM::ManagedPolicy + Properties: + Description: Policy for creating a test database + ManagedPolicyName: foo-bar-baz + Path: / + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: 'rds:CreateDBInstance' + Resource: !Join + - '' + - - 'arn:aws:rds:' + - !Ref 'AWS::Region' + - ':' + - !Ref 'AWS::AccountId' + - ':db:test*' + Condition: + StringEquals: + 'rds:DatabaseEngine': mysql + - Effect: Allow + Action: 'rds:CreateDBInstance' + Resource: !Join + - '' + - - 'arn:aws:rds:' + - !Ref 'AWS::Region' + - ':' + - !Ref 'AWS::AccountId' + - ':db:test*' + Condition: + StringEquals: + 'rds:DatabaseClass': db.t2.micro + Groups: + - TestDBGroup + +- name: IAM Role without static roleName, PASS + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: PASS + static_rolename: PASS + input: + Resources: + ExampleResource: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Action: + - 'sts:AssumeRole' + Path: / + Policies: + - PolicyName: root + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: '*' + Resource: '*' + +- name: IAM Role with static roleName, FAIL + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: FAIL + static_rolename: FAIL + input: + Resources: + ExampleResource: + Type: AWS::IAM::Role + Properties: + RoleName: foobarbaz + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Action: + - 'sts:AssumeRole' + Path: / + Policies: + - PolicyName: root + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: '*' + Resource: '*' + +- name: IAM Role with static roleName - suppressed CFN_NAG, SKIP + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: SKIP + input: + Resources: + ExampleResource: + Type: AWS::IAM::Role + Metadata: + cfn_nag: + rules_to_suppress: + - id: W28 + reason: Suppressed for a very good reason + Properties: + RoleName: foobarbaz + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Action: + - 'sts:AssumeRole' + Path: / + Policies: + - PolicyName: root + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: '*' + Resource: '*' + +- name: IAM Role with static roleName - suppressed GUARD, SKIP + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: SKIP + input: + Resources: + ExampleResource: + Type: AWS::IAM::Role + Metadata: + guard: + SuppressedRules: + - CFN_NO_EXPLICIT_RESOURCE_NAMES + Properties: + RoleName: foobarbaz + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Action: + - 'sts:AssumeRole' + Path: / + Policies: + - PolicyName: root + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: '*' + Resource: '*' + +- name: Kinesis Stream without static name, PASS + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: PASS + static_name: PASS + input: + Resources: + ExampleResource: + Type: AWS::Kinesis::Stream + Properties: + RetentionPeriodHours: 168 + ShardCount: 3 + StreamEncryption: + EncryptionType: KMS + KeyId: !Ref myKey + Tags: + - + Key: Environment + Value: Production + +- name: Kinesis Stream with static name, FAIL + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: FAIL + static_rolename: FAIL + input: + Resources: + ExampleResource: + Type: AWS::Kinesis::Stream + Properties: + Name: MyKinesisStream + RetentionPeriodHours: 168 + ShardCount: 3 + StreamEncryption: + EncryptionType: KMS + KeyId: !Ref myKey + Tags: + - + Key: Environment + Value: Production + +- name: RDS Instance without static dBInstanceIdentifier, PASS + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: PASS + static_dbinstanceidentifier: PASS + input: + Resources: + ExampleResource: + Type: AWS::RDS::DBInstance + Properties: + DBName: !Ref DBName + DBInstanceClass: !Ref DBInstanceClass + AllocatedStorage: !Ref DBAllocatedStorage + Engine: MySQL + EngineVersion: "8.0.33" + MasterUsername: !Ref DBUsername + MasterUserPassword: !Ref DBPassword + MonitoringInterval: 60 + MonitoringRoleArn: 'arn:aws:iam::123456789012:role/rds-monitoring-role' + +- name: RDS Instance with static dBInstanceIdentifier, FAIL + expectations: + rules: + CFN_NO_EXPLICIT_RESOURCE_NAMES: FAIL + static_dbinstanceidentifier: FAIL + input: + Resources: + ExampleResource: + Type: AWS::RDS::DBInstance + Properties: + DBInstanceIdentifier: !Ref DBInstanceID + DBName: !Ref DBName + DBInstanceClass: !Ref DBInstanceClass + AllocatedStorage: !Ref DBAllocatedStorage + Engine: MySQL + EngineVersion: "8.0.33" + MasterUsername: !Ref DBUsername + MasterUserPassword: !Ref DBPassword + MonitoringInterval: 60 + MonitoringRoleArn: 'arn:aws:iam::123456789012:role/rds-monitoring-role' diff --git a/rules/aws/aws_cognito/cognito_allow_unauthenticated_identities_rule.guard b/rules/aws/aws_cognito/cognito_allow_unauthenticated_identities_rule.guard new file mode 100644 index 0000000..55d4690 --- /dev/null +++ b/rules/aws/aws_cognito/cognito_allow_unauthenticated_identities_rule.guard @@ -0,0 +1,57 @@ +# +##################################### +## AWS Solutions ## +##################################### +# +# Rule Identifier: +# COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE +# +# Description: +# AWS::Cognito::IdentityPool AllowUnauthenticatedIdentities property should be false. But CAN be true if proper restrictive IAM roles and permissions are established for unauthenticated users. +# +# Reports on: +# AWS::Cognito::IdentityPool +# +# Evaluates: +# AWS CloudFormation +# +# Rule Parameters: +# None +# +# CFN_NAG Rule Id: +# W57 +# +# Scenarios: +# a) SKIP: when there are no Cognito Identity Pool Resources. +# b) SKIP: when metadata has rule suppression for COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE. +# c) FAIL: when AllowUnauthenticatedIdentities in Cognito Identity Pool Resources is set to true. +# d) PASS: when AllowUnauthenticatedIdentities in Cognito Identity Pool Resources is set to false. + +# +# Select all Cognito Identity Pool Resources from incoming template (payload) +# +let cognito_allow_unauthenticated_identities_rule = Resources.*[ Type == 'AWS::Cognito::IdentityPool' + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "W57" + Metadata.guard.SuppressedRules not exists or + Metadata.guard.SuppressedRules.* != "COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE" +] + +rule COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE when %cognito_allow_unauthenticated_identities_rule !empty { + let violations = %cognito_allow_unauthenticated_identities_rule[ + Type == 'AWS::Cognito::IdentityPool' + Properties.AllowUnauthenticatedIdentities == /(?i)true/ + OR + Properties.AllowUnauthenticatedIdentities == true + OR + Properties.AllowUnauthenticatedIdentities == True + OR + Properties.AllowUnauthenticatedIdentities == TRUE + ] + + %violations empty + << + Violation: AllowUnauthenticatedIdentities in Cognito Identity Pool Resources is set to true. + Fix: set AllowUnauthenticatedIdentities to false in Cognito Identity Pool Resources. + >> +} diff --git a/rules/aws/aws_cognito/cognito_user_pool_mfa_configuration_rule.guard b/rules/aws/aws_cognito/cognito_user_pool_mfa_configuration_rule.guard new file mode 100644 index 0000000..0222c43 --- /dev/null +++ b/rules/aws/aws_cognito/cognito_user_pool_mfa_configuration_rule.guard @@ -0,0 +1,51 @@ +# +##################################### +## AWS Solutions ## +##################################### +# Rule Identifier: +# COGNITO_USER_POOL_MFA_CONFIGURATION_RULE +# +# Description: +# AWS Cognito UserPool should have MfaConfiguration set to 'ON' (MUST be wrapped in quotes) or at least 'OPTIONAL. +# +# Reports on: +# AWS::Cognito::UserPool +# +# Evaluates: +# AWS CloudFormation +# +# Rule Parameters: +# NA +# +# CFN_NAG Rule Id: +# F78 +# +# Scenarios: +# a) SKIP: when there are no cognito userpool resources present. +# b) PASS: when all cognito userpool resources have mfa configuration as ON or OPTIONAL. +# c) FAIL: when any cognito userpool resources have mfa configuration as OFF. +# d) SKIP: when metadata has rule suppression for COGNITO_USER_POOL_MFA_CONFIGURATION_RULE or CFN_NAG F78 + +# +# Select all cognito UserPool resources from incoming template (payload) +# +let cognito_user_pool_mfa_configuration_rule = Resources.*[ Type == 'AWS::Cognito::UserPool' + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "F78" + Metadata.guard.SuppressedRules not exists or + Metadata.guard.SuppressedRules.* != "COGNITO_USER_POOL_MFA_CONFIGURATION_RULE" +] + +rule COGNITO_USER_POOL_MFA_CONFIGURATION_RULE when %cognito_user_pool_mfa_configuration_rule !empty { + let violation = %cognito_user_pool_mfa_configuration_rule[ + Type == 'AWS::Cognito::UserPool' + Properties.MfaConfiguration exists + Properties.MfaConfiguration == 'OFF' + ] + + %violation empty + << + Violation: cognito userpool resources have mfa configuration as OFF. + Fix: Change mfa configuration to ON or OPTIONAL. + >> +} diff --git a/rules/aws/aws_cognito/tests/cognito_allow_unauthenticated_identities_rule_tests.yml b/rules/aws/aws_cognito/tests/cognito_allow_unauthenticated_identities_rule_tests.yml new file mode 100644 index 0000000..d656a1c --- /dev/null +++ b/rules/aws/aws_cognito/tests/cognito_allow_unauthenticated_identities_rule_tests.yml @@ -0,0 +1,103 @@ +### +# COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE tests +### +--- +- name: Empty + input: {} + expectations: + rules: + COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE: SKIP + +- name: No resources + input: + Resources: {} + expectations: + rules: + COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE: SKIP + +- name: Cognito IdentityPool resource with AllowUnauthenticatedIdentities set to false + input: + Resources: + SampleCognitoResource: + Type: AWS::Cognito::IdentityPool + Properties: + AllowClassicFlow: true + AllowUnauthenticatedIdentities: false + DeveloperProviderName: 'testProvider' + IdentityPoolName: 'testIdentity' + expectations: + rules: + COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE: PASS + +- name: Cognito IdentityPool resource with AllowUnauthenticatedIdentities set to true + input: + Resources: + SampleCognitoResource: + Type: AWS::Cognito::IdentityPool + Properties: + AllowClassicFlow: true + AllowUnauthenticatedIdentities: true + DeveloperProviderName: 'testProvider' + IdentityPoolName: 'testIdentity' + expectations: + rules: + COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE: FAIL + +- name: CFN_NAG suppression for W57 + input: + Resources: + SampleCognitoResource: + Type: AWS::Cognito::IdentityPool + Properties: + AllowClassicFlow: true + AllowUnauthenticatedIdentities: true + DeveloperProviderName: 'testProvider' + IdentityPoolName: 'testIdentity' + Metadata: + cfn_nag: + rules_to_suppress: + - id: W57 + reason: Suppressed to test suppression works and skips this test + expectations: + rules: + COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE: SKIP + +- name: Guard suppression for COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE + input: + Resources: + SampleCognitoResource: + Type: AWS::Cognito::IdentityPool + Properties: + AllowClassicFlow: true + AllowUnauthenticatedIdentities: true + DeveloperProviderName: 'testProvider' + IdentityPoolName: 'testIdentity' + Metadata: + guard: + SuppressedRules: + - COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE + expectations: + rules: + COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE: SKIP + +- name: Guard and CFN_NAG suppression for W57 & COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE + input: + Resources: + SampleCognitoResource: + Type: AWS::Cognito::IdentityPool + Properties: + AllowClassicFlow: true + AllowUnauthenticatedIdentities: true + DeveloperProviderName: 'testProvider' + IdentityPoolName: 'testIdentity' + Metadata: + cfn_nag: + rules_to_suppress: + - id: W57 + reason: Suppressed to test suppression works and skips this test + guard: + SuppressedRules: + - COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE + expectations: + rules: + COGNITO_ALLOW_UNAUTHENTICATED_IDENTITIES_RULE: SKIP diff --git a/rules/aws/aws_cognito/tests/cognito_user_pool_mfa_configuration_rule_tests.yml b/rules/aws/aws_cognito/tests/cognito_user_pool_mfa_configuration_rule_tests.yml new file mode 100644 index 0000000..e47c611 --- /dev/null +++ b/rules/aws/aws_cognito/tests/cognito_user_pool_mfa_configuration_rule_tests.yml @@ -0,0 +1,165 @@ +### +# COGNITO_USER_POOL_MFA_CONFIGURATION_RULE tests +### +--- +- name: Empty + input: {} + expectations: + rules: + COGNITO_USER_POOL_MFA_CONFIGURATION_RULE: SKIP + +- name: No resources + input: + Resources: {} + expectations: + rules: + COGNITO_USER_POOL_MFA_CONFIGURATION_RULE: SKIP + +- name: MFA Conf is ON + input: + Resources: + SampleCognito: + Type: AWS::Cognito::UserPool + Properties: + AliasAttributes: + - test@amazon.com + - 1234567890 + - test-alias + AutoVerifiedAttributes: + - test1@amazon.com + DeletionProtection: ACTIVE + UserPoolName: 'TEST' + MfaConfiguration: 'ON' + expectations: + rules: + COGNITO_USER_POOL_MFA_CONFIGURATION_RULE: PASS + +- name: MFA Conf is OPTIONAL + input: + Resources: + SampleCognito: + Type: AWS::Cognito::UserPool + Properties: + AliasAttributes: + - test@amazon.com + - 1234567890 + - test-alias + AutoVerifiedAttributes: + - test1@amazon.com + DeletionProtection: ACTIVE + UserPoolName: 'TEST' + MfaConfiguration: 'OPTIONAL' + expectations: + rules: + COGNITO_USER_POOL_MFA_CONFIGURATION_RULE: PASS + +- name: MFA Conf does not exist + input: + Resources: + SampleCognito: + Type: AWS::Cognito::UserPool + Properties: + AliasAttributes: + - test@amazon.com + - 1234567890 + - test-alias + AutoVerifiedAttributes: + - test1@amazon.com + DeletionProtection: ACTIVE + UserPoolName: 'TEST' + expectations: + rules: + COGNITO_USER_POOL_MFA_CONFIGURATION_RULE: PASS + +- name: MFA Conf is OFF. + input: + Resources: + SampleCognito: + Type: AWS::Cognito::UserPool + Properties: + AliasAttributes: + - test@amazon.com + - 1234567890 + - test-alias + AutoVerifiedAttributes: + - test1@amazon.com + DeletionProtection: ACTIVE + UserPoolName: 'TEST' + MfaConfiguration: 'OFF' + expectations: + rules: + COGNITO_USER_POOL_MFA_CONFIGURATION_RULE: FAIL + +- name: CFN_NAG suppression for F78 + input: + Resources: + SampleCognito: + Type: AWS::Cognito::UserPool + Properties: + AliasAttributes: + - test@amazon.com + - 1234567890 + - test-alias + AutoVerifiedAttributes: + - test1@amazon.com + DeletionProtection: ACTIVE + UserPoolName: 'TEST' + MfaConfiguration: 'OFF' + Metadata: + cfn_nag: + rules_to_suppress: + - id: F78 + reason: Suppressed to test suppression works and skips this test + expectations: + rules: + COGNITO_USER_POOL_MFA_CONFIGURATION_RULE: SKIP + +- name: Guard suppression for COGNITO_USER_POOL_MFA_CONFIGURATION_RULE + input: + Resources: + SampleCognito: + Type: AWS::Cognito::UserPool + Properties: + AliasAttributes: + - test@amazon.com + - 1234567890 + - test-alias + AutoVerifiedAttributes: + - test1@amazon.com + DeletionProtection: ACTIVE + UserPoolName: 'TEST' + MfaConfiguration: 'OFF' + Metadata: + guard: + SuppressedRules: + - COGNITO_USER_POOL_MFA_CONFIGURATION_RULE + expectations: + rules: + COGNITO_USER_POOL_MFA_CONFIGURATION_RULE: SKIP + +- name: Guard and CFN_NAG suppression for F78 & COGNITO_USER_POOL_MFA_CONFIGURATION_RULE + input: + Resources: + SampleCognito: + Type: AWS::Cognito::UserPool + Properties: + AliasAttributes: + - test@amazon.com + - 1234567890 + - test-alias + AutoVerifiedAttributes: + - test1@amazon.com + DeletionProtection: ACTIVE + UserPoolName: 'TEST' + MfaConfiguration: 'OFF' + Metadata: + cfn_nag: + rules_to_suppress: + - id: F78 + reason: Suppressed to test suppression works and skips this test + guard: + SuppressedRules: + - COGNITO_USER_POOL_MFA_CONFIGURATION_RULE + expectations: + rules: + COGNITO_USER_POOL_MFA_CONFIGURATION_RULE: SKIP diff --git a/rules/aws/aws_dlm/dlm_lifecycle_policy_cross_region_encryption_rule.guard b/rules/aws/aws_dlm/dlm_lifecycle_policy_cross_region_encryption_rule.guard new file mode 100644 index 0000000..15b3aa5 --- /dev/null +++ b/rules/aws/aws_dlm/dlm_lifecycle_policy_cross_region_encryption_rule.guard @@ -0,0 +1,59 @@ +# +##################################### +## AWS Solutions ## +##################################### +# Rule Identifier: +# DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE +# +# Description: +# DLM LifecyclePolicy PolicyDetails Actions CrossRegionCopy EncryptionConfiguration should enable Encryption. +# +# Reports on: +# AWS::DLM::LifecyclePolicy +# +# Evaluates: +# AWS CloudFormation +# +# Rule Parameters: +# NA +# +# CFN_NAG Rule Id: +# W81 +# +# Scenarios: +# a) SKIP: when there are no DLM LifeCycle Policy resources present. +# b) PASS: When all DLM LifeCycle Policy resources have encryption enabled. +# c) FAIL: When any DLM LifeCycle Policy resources do not have encryption enabled. +# d) SKIP: when metadata has rule suppression for DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE or W81. + +# +# Select all DLM LifeCycle Policy resources from incoming template (payload) +# +let dlm_lifecycle_policy_cross_region_encryption_rule = Resources.*[ Type == 'AWS::DLM::LifecyclePolicy' + Metadata.cfn_nag.rules_to_suppress not exists or + Metadata.cfn_nag.rules_to_suppress.*.id != "W81" + Metadata.guard.SuppressedRules not exists or + Metadata.guard.SuppressedRules.* != "DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE" +] + +rule DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE when %dlm_lifecycle_policy_cross_region_encryption_rule !empty { + let violations = %dlm_lifecycle_policy_cross_region_encryption_rule[ + Type == 'AWS::DLM::LifecyclePolicy' + Properties.PolicyDetails exists + Properties.PolicyDetails.Actions exists + Properties.PolicyDetails.Actions[*].CrossRegionCopy exists + some Properties.PolicyDetails.Actions[*].CrossRegionCopy[*] { + EncryptionConfiguration.Encrypted !exists + OR + EncryptionConfiguration.Encrypted == false + OR + EncryptionConfiguration.Encrypted == 'false' + } + ] + + %violations empty + << + Violation: DLM LifeCycle Policy resources do not have CrossRegion Encryption enabled. + Fix: Enable CrossRegion Encryption for DLM LifeCycle Policy resources. + >> +} diff --git a/rules/aws/aws_dlm/tests/dlm_lifecycle_policy_cross_region_encryption_rule_tests.yml b/rules/aws/aws_dlm/tests/dlm_lifecycle_policy_cross_region_encryption_rule_tests.yml new file mode 100644 index 0000000..3c3f03f --- /dev/null +++ b/rules/aws/aws_dlm/tests/dlm_lifecycle_policy_cross_region_encryption_rule_tests.yml @@ -0,0 +1,403 @@ +### +# DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE tests +### +--- +- name: Empty + input: {} + expectations: + rules: + DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE: SKIP + +- name: No resources + input: + Resources: {} + expectations: + rules: + DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE: SKIP + +- name: ACTIONS do not exist + input: + Resources: + BasicLifecyclePolicy: + Type: AWS::DLM::LifecyclePolicy + Properties: + Description: Lifecycle Policy using CloudFormation + State: ENABLED + ExecutionRoleArn: arn:aws:iam::123456789012:role/service-role/AWSDataLifecycleManagerDefaultRole + PolicyDetails: + ResourceTypes: + - VOLUME + TargetTags: + - Key: costcenter + Value: '115' + Schedules: + - Name: Daily Snapshots + TagsToAdd: + - Key: type + Value: DailySnapshot + CreateRule: + Interval: 12 + IntervalUnit: HOURS + Times: + - '13:00' + RetainRule: + Count: 1 + CopyTags: true + CrossRegionCopyRules: + - Encrypted: false + Target: us-east-1 + expectations: + rules: + DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE: PASS + +- name: CrossRegionCopy do not exist + input: + Resources: + BasicLifecyclePolicy: + Type: AWS::DLM::LifecyclePolicy + Properties: + Description: Lifecycle Policy using CloudFormation + State: ENABLED + ExecutionRoleArn: arn:aws:iam::123456789012:role/service-role/AWSDataLifecycleManagerDefaultRole + PolicyDetails: + Actions: + ResourceTypes: + - VOLUME + TargetTags: + - Key: costcenter + Value: '115' + Schedules: + - Name: Daily Snapshots + TagsToAdd: + - Key: type + Value: DailySnapshot + CreateRule: + Interval: 12 + IntervalUnit: HOURS + Times: + - '13:00' + RetainRule: + Count: 1 + CopyTags: true + CrossRegionCopyRules: + - Encrypted: false + Target: us-east-1 + expectations: + rules: + DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE: PASS + +- name: Encrypted set to True + input: + Resources: + BasicLifecyclePolicy: + Type: AWS::DLM::LifecyclePolicy + Properties: + Description: Lifecycle Policy using CloudFormation + State: ENABLED + ExecutionRoleArn: arn:aws:iam::123456789012:role/service-role/AWSDataLifecycleManagerDefaultRole + PolicyDetails: + Actions: + - CrossRegionCopy: + - EncryptionConfiguration: + CmkArn: 'TestArn' + Encrypted: true + Target: 'TestTarget' + Name: 'TestName' + ResourceTypes: + - VOLUME + TargetTags: + - Key: costcenter + Value: '115' + Schedules: + - Name: Daily Snapshots + TagsToAdd: + - Key: type + Value: DailySnapshot + CreateRule: + Interval: 12 + IntervalUnit: HOURS + Times: + - '13:00' + RetainRule: + Count: 1 + CopyTags: true + CrossRegionCopyRules: + - Encrypted: false + Target: us-east-1 + expectations: + rules: + DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE: PASS + +- name: Encrypted set to False + input: + Resources: + BasicLifecyclePolicy: + Type: AWS::DLM::LifecyclePolicy + Properties: + Description: Lifecycle Policy using CloudFormation + State: ENABLED + ExecutionRoleArn: arn:aws:iam::123456789012:role/service-role/AWSDataLifecycleManagerDefaultRole + PolicyDetails: + Actions: + - CrossRegionCopy: + - EncryptionConfiguration: + CmkArn: 'TestArn' + Encrypted: false + Target: 'TestTarget' + Name: 'TestName' + ResourceTypes: + - VOLUME + TargetTags: + - Key: costcenter + Value: '115' + Schedules: + - Name: Daily Snapshots + TagsToAdd: + - Key: type + Value: DailySnapshot + CreateRule: + Interval: 12 + IntervalUnit: HOURS + Times: + - '13:00' + RetainRule: + Count: 1 + CopyTags: true + CrossRegionCopyRules: + - Encrypted: false + Target: us-east-1 + expectations: + rules: + DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE: FAIL + +- name: Encrypted do not exist + input: + Resources: + BasicLifecyclePolicy: + Type: AWS::DLM::LifecyclePolicy + Properties: + Description: Lifecycle Policy using CloudFormation + State: ENABLED + ExecutionRoleArn: arn:aws:iam::123456789012:role/service-role/AWSDataLifecycleManagerDefaultRole + PolicyDetails: + Actions: + - CrossRegionCopy: + - EncryptionConfiguration: + CmkArn: 'TestArn' + Target: 'TestTarget' + Name: 'TestName' + ResourceTypes: + - VOLUME + TargetTags: + - Key: costcenter + Value: '115' + Schedules: + - Name: Daily Snapshots + TagsToAdd: + - Key: type + Value: DailySnapshot + CreateRule: + Interval: 12 + IntervalUnit: HOURS + Times: + - '13:00' + RetainRule: + Count: 1 + CopyTags: true + CrossRegionCopyRules: + - Encrypted: false + Target: us-east-1 + expectations: + rules: + DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE: FAIL + +- name: 2 CrossRegionCopy, one with encryption enabled, and one with encryption disabled. + input: + Resources: + BasicLifecyclePolicy: + Type: AWS::DLM::LifecyclePolicy + Properties: + Description: Lifecycle Policy using CloudFormation + State: ENABLED + ExecutionRoleArn: arn:aws:iam::123456789012:role/service-role/AWSDataLifecycleManagerDefaultRole + PolicyDetails: + Actions: + - CrossRegionCopy: + - EncryptionConfiguration: + CmkArn: 'TestArn' + Encrypted: true + Target: 'TestTarget' + Name: 'TestName' + - CrossRegionCopy: + - EncryptionConfiguration: + CmkArn: 'TestArn2' + Encrypted: false + Target: 'TestTarget2' + Name: 'TestName2' + ResourceTypes: + - VOLUME + TargetTags: + - Key: costcenter + Value: '115' + Schedules: + - Name: Daily Snapshots + TagsToAdd: + - Key: type + Value: DailySnapshot + CreateRule: + Interval: 12 + IntervalUnit: HOURS + Times: + - '13:00' + RetainRule: + Count: 1 + CopyTags: true + CrossRegionCopyRules: + - Encrypted: false + Target: us-east-1 + expectations: + rules: + DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE: FAIL + +- name: CFN_NAG suppression for W81 + input: + Resources: + BasicLifecyclePolicy: + Type: AWS::DLM::LifecyclePolicy + Properties: + Description: Lifecycle Policy using CloudFormation + State: ENABLED + ExecutionRoleArn: arn:aws:iam::123456789012:role/service-role/AWSDataLifecycleManagerDefaultRole + PolicyDetails: + Actions: + - CrossRegionCopy: + - EncryptionConfiguration: + CmkArn: 'TestArn' + Encrypted: false + Target: 'TestTarget' + Name: 'TestName' + ResourceTypes: + - VOLUME + TargetTags: + - Key: costcenter + Value: '115' + Schedules: + - Name: Daily Snapshots + TagsToAdd: + - Key: type + Value: DailySnapshot + CreateRule: + Interval: 12 + IntervalUnit: HOURS + Times: + - '13:00' + RetainRule: + Count: 1 + CopyTags: true + CrossRegionCopyRules: + - Encrypted: false + Target: us-east-1 + Metadata: + cfn_nag: + rules_to_suppress: + - id: W81 + reason: Suppressed to test suppression works and skips this test + expectations: + rules: + DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE: SKIP + +- name: Guard suppression for DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE + input: + Resources: + BasicLifecyclePolicy: + Type: AWS::DLM::LifecyclePolicy + Properties: + Description: Lifecycle Policy using CloudFormation + State: ENABLED + ExecutionRoleArn: arn:aws:iam::123456789012:role/service-role/AWSDataLifecycleManagerDefaultRole + PolicyDetails: + Actions: + - CrossRegionCopy: + - EncryptionConfiguration: + CmkArn: 'TestArn' + Encrypted: false + Target: 'TestTarget' + Name: 'TestName' + ResourceTypes: + - VOLUME + TargetTags: + - Key: costcenter + Value: '115' + Schedules: + - Name: Daily Snapshots + TagsToAdd: + - Key: type + Value: DailySnapshot + CreateRule: + Interval: 12 + IntervalUnit: HOURS + Times: + - '13:00' + RetainRule: + Count: 1 + CopyTags: true + CrossRegionCopyRules: + - Encrypted: false + Target: us-east-1 + Metadata: + guard: + SuppressedRules: + - DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE + expectations: + rules: + DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE: SKIP + +- name: Guard and CFN_NAG suppression for W81 & DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE + input: + Resources: + BasicLifecyclePolicy: + Type: AWS::DLM::LifecyclePolicy + Properties: + Description: Lifecycle Policy using CloudFormation + State: ENABLED + ExecutionRoleArn: arn:aws:iam::123456789012:role/service-role/AWSDataLifecycleManagerDefaultRole + PolicyDetails: + Actions: + - CrossRegionCopy: + - EncryptionConfiguration: + CmkArn: 'TestArn' + Encrypted: false + Target: 'TestTarget' + Name: 'TestName' + ResourceTypes: + - VOLUME + TargetTags: + - Key: costcenter + Value: '115' + Schedules: + - Name: Daily Snapshots + TagsToAdd: + - Key: type + Value: DailySnapshot + CreateRule: + Interval: 12 + IntervalUnit: HOURS + Times: + - '13:00' + RetainRule: + Count: 1 + CopyTags: true + CrossRegionCopyRules: + - Encrypted: false + Target: us-east-1 + Metadata: + cfn_nag: + rules_to_suppress: + - id: W81 + reason: Suppressed to test suppression works and skips this test + guard: + SuppressedRules: + - DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE + expectations: + rules: + DLM_LIFECYCLE_POLICY_CROSS_REGION_ENCRYPTION_RULE: SKIP