Amazon OpenSearch controls - AWS Control Tower

Amazon OpenSearch controls

Topics

[CT.OPENSEARCH.PR.1] Require an Elasticsearch domain to encrypt data at rest

This control checks whether Elasticsearch domains have encryption-at-rest enabled.

  • Control objective: Encrypt data at rest

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::Elasticsearch::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.1 rule specification

Details and examples

Explanation

For an added layer of security for your sensitive data in OpenSearch, you should configure your OpenSearch to be encrypted at rest. Elasticsearch domains offer encryption of data at rest. The feature uses AWS KMS to store and manage your encryption keys. To perform the encryption, it uses the Advanced Encryption Standard algorithm with 256-bit keys (AES-256).

Remediation for rule failure

Within EncryptionAtRestOptions, set Enabled to true.

The examples that follow show how to implement this remediation.

Elasticsearch Domain - Example

An Elasticsearch domain configured with encryption-at-rest enabled. The example is shown in JSON and in YAML.

JSON example

{ "ElasticsearchDomain": { "Type": "AWS::Elasticsearch::Domain", "Properties": { "ElasticsearchVersion": 7.1, "ElasticsearchClusterConfig": { "InstanceCount": "1", "InstanceType": "t3.small.elasticsearch" }, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "EncryptionAtRestOptions": { "Enabled": true } } } }

YAML example

ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' EncryptionAtRestOptions: Enabled: true

CT.OPENSEARCH.PR.1 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # elasticsearch_encrypted_at_rest_check # # Description: # This control checks whether Elasticsearch domains have encryption-at-rest enabled. # # Reports on: # AWS::Elasticsearch::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any Elasticsearch domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'EncryptionAtRestOptions' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'EncryptionAtRestOptions' has been provided # And: 'Enabled' in 'EncryptionAtRestOptions' has not been provided or provided # and set to a value other than bool(true) # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'EncryptionAtRestOptions' has been provided # And: 'Enabled' in 'EncryptionAtRestOptions' has been provided and set to bool(true) # Then: PASS # # Constants # let ELASTICSEARCH_DOMAIN_TYPE = "AWS::Elasticsearch::Domain" let INPUT_DOCUMENT = this # # Assignments # let elasticsearch_domains = Resources.*[ Type == %ELASTICSEARCH_DOMAIN_TYPE ] # # Primary Rules # rule elasticsearch_encrypted_at_rest_check when is_cfn_template(%INPUT_DOCUMENT) %elasticsearch_domains not empty { check(%elasticsearch_domains.Properties) << [CT.OPENSEARCH.PR.1]: Require an Elasticsearch domain to encrypt data at rest [FIX]: Within 'EncryptionAtRestOptions', set 'Enabled' to 'true'. >> } rule elasticsearch_encrypted_at_rest_check when is_cfn_hook(%INPUT_DOCUMENT, %ELASTICSEARCH_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%ELASTICSEARCH_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.1]: Require an Elasticsearch domain to encrypt data at rest [FIX]: Within 'EncryptionAtRestOptions', set 'Enabled' to 'true'. >> } # # Parameterized Rules # rule check(elasticsearch_domain) { %elasticsearch_domain { # Scenario 2 EncryptionAtRestOptions exists EncryptionAtRestOptions is_struct EncryptionAtRestOptions { # Scenarios 3 and 4 Enabled exists Enabled == true } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists }

CT.OPENSEARCH.PR.1 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' EncryptionAtRestOptions: Enabled: true

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' EncryptionAtRestOptions: Enabled: false

[CT.OPENSEARCH.PR.2] Require an Elasticsearch domain to be created in a user-specified Amazon VPC

This control checks whether Elasticsearch domains are configured with VPC option settings that specify a target Amazon VPC.

  • Control objective: Limit network access

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::Elasticsearch::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.2 rule specification

Details and examples

Explanation

Elasticsearch domains deployed within a VPC can communicate with VPC resources over the private AWS network, without the need to traverse the public internet. This configuration increases the security posture by limiting access to the data in transit. VPCs provide a number of network controls that help create secure access to Elasticsearch domains, including network ACLs and security groups. Security Hub recommends that you migrate public Elasticsearch domains to VPCs to take advantage of these controls.

Remediation for rule failure

Within VPCOptions, set SubnetIds to a list with one or more Amazon EC2 subnet IDs.

The examples that follow show how to implement this remediation.

Elasticsearch Domain - Example

An Elasticsearch domain configured to deploy within an Amazon VPC by means of VPC option settings. The example is shown in JSON and in YAML.

JSON example

{ "ElasticsearchDomain": { "Type": "AWS::Elasticsearch::Domain", "Properties": { "ElasticsearchVersion": 7.1, "ElasticsearchClusterConfig": { "InstanceCount": "1", "InstanceType": "t3.small.elasticsearch" }, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "VPCOptions": { "SubnetIds": [ { "Ref": "Subnet" } ] } } } }

YAML example

ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' VPCOptions: SubnetIds: - !Ref 'Subnet'

CT.OPENSEARCH.PR.2 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # elasticsearch_in_vpc_only_check # # Description: # This control checks whether Elasticsearch domains are configured with VPC option settings that specify a target Amazon VPC. # # Reports on: # AWS::Elasticsearch::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any Elasticsearch domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'VPCOptions' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'VPCOptions' has been provided # And: 'SubnetIds' in 'VPCOptions' has not been provided or has been provided # as an empty list # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'VPCOptions' has been provided # And: 'SubnetIds' in 'VPCOptions' has been provided as a list with one or more values # Then: PASS # # Constants # let ELASTICSEARCH_DOMAIN_TYPE = "AWS::Elasticsearch::Domain" let INPUT_DOCUMENT = this # # Assignments # let elasticsearch_domains = Resources.*[ Type == %ELASTICSEARCH_DOMAIN_TYPE ] # # Primary Rules # rule elasticsearch_in_vpc_only_check when is_cfn_template(%INPUT_DOCUMENT) %elasticsearch_domains not empty { check(%elasticsearch_domains.Properties) << [CT.OPENSEARCH.PR.2]: Require an Elasticsearch domain to be created in a user-specified Amazon VPC [FIX]: Within 'VPCOptions', set 'SubnetIds' to a list with one or more Amazon EC2 subnet IDs. >> } rule elasticsearch_in_vpc_only_check when is_cfn_hook(%INPUT_DOCUMENT, %ELASTICSEARCH_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%ELASTICSEARCH_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.2]: Require an Elasticsearch domain to be created in a user-specified Amazon VPC [FIX]: Within 'VPCOptions', set 'SubnetIds' to a list with one or more Amazon EC2 subnet IDs. >> } # # Parameterized Rules # rule check(elasticsearch_domain) { %elasticsearch_domain { # Scenario 2 VPCOptions exists VPCOptions is_struct VPCOptions { # Scenarios 3 and 4 SubnetIds exists SubnetIds is_list SubnetIds not empty } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists }

CT.OPENSEARCH.PR.2 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 Subnet: Type: AWS::EC2::Subnet Properties: VpcId: Ref: VPC CidrBlock: 10.0.0.0/16 AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: '' ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' VPCOptions: SubnetIds: - Ref: Subnet

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*'

[CT.OPENSEARCH.PR.3] Require an Elasticsearch domain to encrypt data sent between nodes

This control checks whether Elasticsearch domains have node-to-node encryption enabled.

  • Control objective: Encrypt data in transit

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::Elasticsearch::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.3 rule specification

Details and examples

Explanation

HTTPS (TLS) can help prevent potential attackers from eavesdropping on or manipulating network traffic using person-in-the-middle, or similar, attacks. Only encrypted connections over HTTPS (TLS) should be allowed. Enabling node-to-node encryption for Elasticsearch domains ensures that intra-cluster communications are encrypted in transit.

Usage considerations
  • A performance penalty may be associated with this configuration. You should be aware of and test the performance trade-offs before enabling this option.

Remediation for rule failure

Within NodeToNodeEncryptionOptions, set Enabled to true.

The examples that follow show how to implement this remediation.

Elasticsearch Domain - Example

An Elasticsearch domain configured with node-to-node encryption enabled. The example is shown in JSON and in YAML.

JSON example

{ "ElasticsearchDomain": { "Type": "AWS::Elasticsearch::Domain", "Properties": { "ElasticsearchVersion": 7.1, "ElasticsearchClusterConfig": { "InstanceCount": "1", "InstanceType": "t3.small.elasticsearch" }, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "NodeToNodeEncryptionOptions": { "Enabled": true } } } }

YAML example

ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: Enabled: true

CT.OPENSEARCH.PR.3 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # elasticsearch_node_to_node_encryption_check # # Description: # This control checks whether Elasticsearch domains have node-to-node encryption enabled. # # Reports on: # AWS::Elasticsearch::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any Elasticsearch domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'NodeToNodeEncryptionOptions' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'NodeToNodeEncryptionOptions' has been provided # And: 'Enabled' in 'NodeToNodeEncryptionOptions' has not been provided or has been provided # and set to a value other than bool(true) # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'NodeToNodeEncryptionOptions' has been provided # And: 'Enabled' in 'NodeToNodeEncryptionOptions' has been provided and set to a # value of bool(true) # Then: PASS # # Constants # let ELASTICSEARCH_DOMAIN_TYPE = "AWS::Elasticsearch::Domain" let INPUT_DOCUMENT = this # # Assignments # let elasticsearch_domains = Resources.*[ Type == %ELASTICSEARCH_DOMAIN_TYPE ] # # Primary Rules # rule elasticsearch_node_to_node_encryption_check when is_cfn_template(%INPUT_DOCUMENT) %elasticsearch_domains not empty { check(%elasticsearch_domains.Properties) << [CT.OPENSEARCH.PR.3]: Require an Elasticsearch domain to encrypt data sent between nodes [FIX]: Within 'NodeToNodeEncryptionOptions', set 'Enabled' to 'true'. >> } rule elasticsearch_node_to_node_encryption_check when is_cfn_hook(%INPUT_DOCUMENT, %ELASTICSEARCH_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%ELASTICSEARCH_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.3]: Require an Elasticsearch domain to encrypt data sent between nodes [FIX]: Within 'NodeToNodeEncryptionOptions', set 'Enabled' to 'true'. >> } # # Parameterized Rules # rule check(elasticsearch_domain) { %elasticsearch_domain { # Scenario 2 NodeToNodeEncryptionOptions exists NodeToNodeEncryptionOptions is_struct NodeToNodeEncryptionOptions { # Scenarios 3 and 4 Enabled exists Enabled == true } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists }

CT.OPENSEARCH.PR.3 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: Enabled: true

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: {}

[CT.OPENSEARCH.PR.4] Require an Elasticsearch domain to send error logs to Amazon CloudWatch Logs

This control checks whether Elasticsearch domains are configured to send error logs to an Amazon CloudWatch Logs log group.

  • Control objective: Establish logging and monitoring

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::Elasticsearch::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.4 rule specification

Details and examples

Explanation

Enable error logs (ES_APPLICATION_LOGS) for Elasticsearch domains and send those logs to CloudWatch Logs for retention and response. Domain error logs can assist with security and access audits, and can help to diagnose availability issues.

Remediation for rule failure

Within LogPublishingOptions, provide an ES_APPLICATION_LOGS configuration, set Enabled to true, and set CloudWatchLogsLogGroupArn to the ARN of a valid Amazon CloudWatch Logs log group.

The examples that follow show how to implement this remediation.

Elasticsearch Domain - Example

An Elasticsearch domain configured to send error logs to Amazon CloudWatch Logs. The example is shown in JSON and in YAML.

JSON example

{ "ElasticsearchDomain": { "Type": "AWS::Elasticsearch::Domain", "Properties": { "ElasticsearchVersion": 7.1, "ElasticsearchClusterConfig": { "InstanceCount": "1", "InstanceType": "t3.small.elasticsearch" }, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "LogPublishingOptions": { "ES_APPLICATION_LOGS": { "CloudWatchLogsLogGroupArn": { "Fn::GetAtt": [ "LogGroup", "Arn" ] }, "Enabled": true } } } } }

YAML example

ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' LogPublishingOptions: ES_APPLICATION_LOGS: CloudWatchLogsLogGroupArn: !GetAtt 'LogGroup.Arn' Enabled: true

CT.OPENSEARCH.PR.4 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # elasticsearch_application_logging_enabled_check # # Description: # This control checks whether Elasticsearch domains are configured to send error logs to an Amazon CloudWatch Logs log group. # # Reports on: # AWS::Elasticsearch::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any Elasticsearch domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'LogPublishingOptions' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'LogPublishingOptions' has been provided # And: 'ES_APPLICATION_LOGS' in 'LogPublishingOptions' has not been provided # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'LogPublishingOptions' has been specified # And: 'ES_APPLICATION_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'ES_APPLICATION_LOGS' has not been provided or provided and set to # a value other than bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'ES_APPLICATION_LOGS' has not been provided or provided # as an empty string or invalid local reference # Then: FAIL # Scenario: 5 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'LogPublishingOptions' has been specified # And: 'ES_APPLICATION_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'ES_APPLICATION_LOGS' has been provided and set to bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'ES_APPLICATION_LOGS' has not been provided or provided # as an empty string or invalid local reference # Then: FAIL # Scenario: 6 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'LogPublishingOptions' has been specified # And: 'ES_APPLICATION_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'ES_APPLICATION_LOGS' has not been provided or provided and set to # a value other than bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'ES_APPLICATION_LOGS' has been provided as a non-empty string # or valid local reference # Then: FAIL # Scenario: 7 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'LogPublishingOptions' has been specified # And: 'ES_APPLICATION_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'ES_APPLICATION_LOGS' has been provided and set to bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'ES_APPLICATION_LOGS' has been provided as a non-empty string # or valid local reference # Then: PASS # # Constants # let ELASTICSEARCH_DOMAIN_TYPE = "AWS::Elasticsearch::Domain" let INPUT_DOCUMENT = this # # Assignments # let elasticsearch_domains = Resources.*[ Type == %ELASTICSEARCH_DOMAIN_TYPE ] # # Primary Rules # rule elasticsearch_application_logging_enabled_check when is_cfn_template(%INPUT_DOCUMENT) %elasticsearch_domains not empty { check(%elasticsearch_domains.Properties) << [CT.OPENSEARCH.PR.4]: Require an Elasticsearch domain to send error logs to Amazon CloudWatch Logs [FIX]: Within 'LogPublishingOptions', provide an 'ES_APPLICATION_LOGS' configuration, set 'Enabled' to 'true', and set 'CloudWatchLogsLogGroupArn' to the ARN of a valid Amazon CloudWatch Logs log group. >> } rule elasticsearch_application_logging_enabled_check when is_cfn_hook(%INPUT_DOCUMENT, %ELASTICSEARCH_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%ELASTICSEARCH_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.4]: Require an Elasticsearch domain to send error logs to Amazon CloudWatch Logs [FIX]: Within 'LogPublishingOptions', provide an 'ES_APPLICATION_LOGS' configuration, set 'Enabled' to 'true', and set 'CloudWatchLogsLogGroupArn' to the ARN of a valid Amazon CloudWatch Logs log group. >> } # # Parameterized Rules # rule check(elasticsearch_domain) { %elasticsearch_domain { # Scenario 2 LogPublishingOptions exists LogPublishingOptions is_struct LogPublishingOptions { # Scenario 3 ES_APPLICATION_LOGS exists ES_APPLICATION_LOGS is_struct ES_APPLICATION_LOGS { # Scenarios 4, 5, 6 and 7 Enabled exists Enabled == true CloudWatchLogsLogGroupArn exists check_is_string_and_not_empty(CloudWatchLogsLogGroupArn) or check_local_references(%INPUT_DOCUMENT, CloudWatchLogsLogGroupArn, "AWS::Logs::LogGroup") } } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists } rule check_is_string_and_not_empty(value) { %value { this is_string this != /\A\s*\z/ } } rule check_local_references(doc, reference_properties, referenced_resource_type) { %reference_properties { 'Fn::GetAtt' { query_for_resource(%doc, this[0], %referenced_resource_type) <<Local Stack reference was invalid>> } or Ref { query_for_resource(%doc, this, %referenced_resource_type) <<Local Stack reference was invalid>> } } } rule query_for_resource(doc, resource_key, referenced_resource_type) { let referenced_resource = %doc.Resources[ keys == %resource_key ] %referenced_resource not empty %referenced_resource { Type == %referenced_resource_type } }

CT.OPENSEARCH.PR.4 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: ElasticsearchDomain: Type: AWS::Elasticsearch::Domain DependsOn: LogGroupPolicy Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' LogPublishingOptions: ES_APPLICATION_LOGS: CloudWatchLogsLogGroupArn: Fn::GetAtt: - LogGroup - Arn Enabled: true LogGroup: Type: AWS::Logs::LogGroup LogGroupPolicy: Type: AWS::Logs::ResourcePolicy Properties: PolicyName: Fn::Sub: ${AWS::StackName}-AllowES PolicyDocument: Fn::Sub: - '{"Version": "2012-10-17","Statement":[{"Effect":"Allow","Principal": {"Service": ["es.amazonaws.com"]},"Action":["logs:PutLogEvents","logs:CreateLogStream"],"Resource":"${LogGroupArn}","Condition":{"StringEquals":{ "aws:SourceAccount": "${AWS::AccountId}"}}}]}' - LogGroupArn: Fn::GetAtt: [ LogGroup, Arn ]

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*'

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' LogPublishingOptions: ES_APPLICATION_LOGS: Enabled: false

[CT.OPENSEARCH.PR.5] Require an Elasticsearch domain to send audit logs to Amazon CloudWatch Logs

This control checks whether Elasticsearch domains are configured to send audit logs to an Amazon CloudWatch Logs log group.

  • Control objective: Establish logging and monitoring

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::Elasticsearch::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.5 rule specification

Details and examples

Explanation

Audit logs are highly customizable. They allow you to track user activity on your Elasticsearch clusters, including authentication successes and failures, requests to OpenSearch, index changes, and incoming search queries.

Usage considerations
  • This control requires that Elasticsearch domains must have advanced security options configured.

  • To enable advanced security options on an Elasticsearch domain through the AdvancedSecurityOptions property, you must enable encryption of data at rest (by means of EncryptionAtRestOptions), node-to-node encryption (by means of NodeToNodeEncryptionOptions), and enforce HTTPS connections (by means of DomainEndpointOptions).

Remediation for rule failure

Within LogPublishingOptions, provide an AUDIT_LOGS configuration, set Enabled to true, and set CloudWatchLogsLogGroupArn to the ARN of a valid Amazon CloudWatch Logs log group.

The examples that follow show how to implement this remediation.

Elasticsearch Domain - Example

An Elasticsearch domain configured to send audit logs to Amazon CloudWatch Logs. The example is shown in JSON and in YAML.

JSON example

{ "ElasticsearchDomain": { "Type": "AWS::Elasticsearch::Domain", "Properties": { "ElasticsearchVersion": 7.1, "ElasticsearchClusterConfig": { "InstanceCount": "1", "InstanceType": "t3.small.elasticsearch" }, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "NodeToNodeEncryptionOptions": { "Enabled": true }, "EncryptionAtRestOptions": { "Enabled": true }, "DomainEndpointOptions": { "EnforceHTTPS": true }, "AdvancedSecurityOptions": { "Enabled": true, "InternalUserDatabaseEnabled": false, "MasterUserOptions": { "MasterUserARN": { "Fn::GetAtt": [ "IAMRole", "Arn" ] } } }, "LogPublishingOptions": { "AUDIT_LOGS": { "CloudWatchLogsLogGroupArn": { "Fn::GetAtt": [ "LogGroup", "Arn" ] }, "Enabled": true } } } } }

YAML example

ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: Enabled: true EncryptionAtRestOptions: Enabled: true DomainEndpointOptions: EnforceHTTPS: true AdvancedSecurityOptions: Enabled: true InternalUserDatabaseEnabled: false MasterUserOptions: MasterUserARN: !GetAtt 'IAMRole.Arn' LogPublishingOptions: AUDIT_LOGS: CloudWatchLogsLogGroupArn: !GetAtt 'LogGroup.Arn' Enabled: true

CT.OPENSEARCH.PR.5 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # elasticsearch_audit_logging_enabled_check # # Description: # This control checks whether Elasticsearch domains are configured to send audit logs to an Amazon CloudWatch Logs log group. # # Reports on: # AWS::Elasticsearch::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any Elasticsearch domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'LogPublishingOptions' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'LogPublishingOptions' has been provided # And: 'AUDIT_LOGS' in 'LogPublishingOptions' has not been provided # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'LogPublishingOptions' has been specified # And: 'AUDIT_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'AUDIT_LOGS' has not been provided or provided and set to # a value other than bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'AUDIT_LOGS' has not been provided or provided # as an empty string or invalid local reference # Then: FAIL # Scenario: 5 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'LogPublishingOptions' has been specified # And: 'AUDIT_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'AUDIT_LOGS' has been provided and set to bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'AUDIT_LOGS' has not been provided or provided # as an empty string or invalid local reference # Then: FAIL # Scenario: 6 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'LogPublishingOptions' has been specified # And: 'AUDIT_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'AUDIT_LOGS' has not been provided or provided and set to # a value other than bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'AUDIT_LOGS' has been provided as a non-empty string # or valid local reference # Then: FAIL # Scenario: 7 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'LogPublishingOptions' has been specified # And: 'AUDIT_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'AUDIT_LOGS' has been provided and set to bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'AUDIT_LOGS' has been provided as a non-empty string # or valid local reference # Then: PASS # # Constants # let ELASTICSEARCH_DOMAIN_TYPE = "AWS::Elasticsearch::Domain" let INPUT_DOCUMENT = this # # Assignments # let elasticsearch_domains = Resources.*[ Type == %ELASTICSEARCH_DOMAIN_TYPE ] # # Primary Rules # rule elasticsearch_audit_logging_enabled_check when is_cfn_template(%INPUT_DOCUMENT) %elasticsearch_domains not empty { check(%elasticsearch_domains.Properties) << [CT.OPENSEARCH.PR.5]: Require an Elasticsearch domain to send audit logs to Amazon CloudWatch Logs [FIX]: Within 'LogPublishingOptions', provide an 'AUDIT_LOGS' configuration, set 'Enabled' to 'true', and set 'CloudWatchLogsLogGroupArn' to the ARN of a valid Amazon CloudWatch Logs log group. >> } rule elasticsearch_audit_logging_enabled_check when is_cfn_hook(%INPUT_DOCUMENT, %ELASTICSEARCH_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%ELASTICSEARCH_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.5]: Require an Elasticsearch domain to send audit logs to Amazon CloudWatch Logs [FIX]: Within 'LogPublishingOptions', provide an 'AUDIT_LOGS' configuration, set 'Enabled' to 'true', and set 'CloudWatchLogsLogGroupArn' to the ARN of a valid Amazon CloudWatch Logs log group. >> } # # Parameterized Rules # rule check(elasticsearch_domain) { %elasticsearch_domain { # Scenario 2 LogPublishingOptions exists LogPublishingOptions is_struct LogPublishingOptions { # Scenario 3 AUDIT_LOGS exists AUDIT_LOGS is_struct AUDIT_LOGS { # Scenarios 4, 5, 6 and 7 Enabled exists Enabled == true CloudWatchLogsLogGroupArn exists check_is_string_and_not_empty(CloudWatchLogsLogGroupArn) or check_local_references(%INPUT_DOCUMENT, CloudWatchLogsLogGroupArn, "AWS::Logs::LogGroup") } } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists } rule check_is_string_and_not_empty(value) { %value { this is_string this != /\A\s*\z/ } } rule check_local_references(doc, reference_properties, referenced_resource_type) { %reference_properties { 'Fn::GetAtt' { query_for_resource(%doc, this[0], %referenced_resource_type) <<Local Stack reference was invalid>> } or Ref { query_for_resource(%doc, this, %referenced_resource_type) <<Local Stack reference was invalid>> } } } rule query_for_resource(doc, resource_key, referenced_resource_type) { let referenced_resource = %doc.Resources[ keys == %resource_key ] %referenced_resource not empty %referenced_resource { Type == %referenced_resource_type } }

CT.OPENSEARCH.PR.5 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: IAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: AWS: Ref: AWS::AccountId Action: sts:AssumeRole ElasticsearchDomain: Type: AWS::Elasticsearch::Domain DependsOn: LogGroupPolicy Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: Enabled: true EncryptionAtRestOptions: Enabled: true DomainEndpointOptions: EnforceHTTPS: true AdvancedSecurityOptions: Enabled: true InternalUserDatabaseEnabled: false MasterUserOptions: MasterUserARN: Fn::GetAtt: - IAMRole - Arn LogPublishingOptions: AUDIT_LOGS: CloudWatchLogsLogGroupArn: Fn::GetAtt: - LogGroup - Arn Enabled: true LogGroup: Type: AWS::Logs::LogGroup LogGroupPolicy: Type: AWS::Logs::ResourcePolicy Properties: PolicyName: AllowES PolicyDocument: Fn::Sub: - '{"Version": "2012-10-17","Statement":[{"Effect":"Allow","Principal": {"Service": ["es.amazonaws.com"]},"Action":["logs:PutLogEvents","logs:CreateLogStream"],"Resource":"${LogGroupArn}","Condition":{"StringEquals":{ "aws:SourceAccount": "${AWS::AccountId}"}}}]}' - LogGroupArn: Fn::GetAtt: [ LogGroup, Arn ]

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: IAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: AWS: Ref: AWS::AccountId Action: sts:AssumeRole ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: Enabled: true EncryptionAtRestOptions: Enabled: true DomainEndpointOptions: EnforceHTTPS: true AdvancedSecurityOptions: Enabled: true InternalUserDatabaseEnabled: false MasterUserOptions: MasterUserARN: Fn::GetAtt: - IAMRole - Arn

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: IAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: AWS: Ref: AWS::AccountId Action: sts:AssumeRole ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: Enabled: true EncryptionAtRestOptions: Enabled: true DomainEndpointOptions: EnforceHTTPS: true AdvancedSecurityOptions: Enabled: true InternalUserDatabaseEnabled: false MasterUserOptions: MasterUserARN: Fn::GetAtt: - IAMRole - Arn LogPublishingOptions: AUDIT_LOGS: Enabled: false

[CT.OPENSEARCH.PR.6] Require an Elasticsearch domain to have zone awareness and at least three data nodes

This control checks whether ElasticSearch domains are configured with at least three data nodes and zone awareness enabled.

  • Control objective: Improve availability

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::Elasticsearch::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.6 rule specification

Details and examples

Explanation

An Elasticsearch domain requires at least three data nodes for high availability and fault-tolerance. Deploying an Elasticsearch domain with at least three data nodes ensures that cluster operations can continue if a node fails.

Remediation for rule failure

Within ElasticsearchClusterConfig, set ZoneAwarenessEnabled to true, and set InstanceCount to an integer value greater than or equal to three.

The examples that follow show how to implement this remediation.

Elasticsearch Domain - Example

An Elasticsearch domain configured with three data nodes and zone awareness enabled. The example is shown in JSON and in YAML.

JSON example

{ "ElasticsearchDomain": { "Type": "AWS::Elasticsearch::Domain", "Properties": { "ElasticsearchVersion": 7.1, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "ElasticsearchClusterConfig": { "InstanceType": "t3.small.elasticsearch", "InstanceCount": 3, "ZoneAwarenessEnabled": true, "ZoneAwarenessConfig": { "AvailabilityZoneCount": 3 } } } } }

YAML example

ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' ElasticsearchClusterConfig: InstanceType: t3.small.elasticsearch InstanceCount: 3 ZoneAwarenessEnabled: true ZoneAwarenessConfig: AvailabilityZoneCount: 3

CT.OPENSEARCH.PR.6 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # elasticsearch_data_node_fault_tolerance_check # # Description: # This control checks whether Elasticsearch domains are configured with at least three data nodes and zone awareness enabled. # # Reports on: # AWS::Elasticsearch::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any Elasticsearch domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'ElasticsearchClusterConfig' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'ElasticsearchClusterConfig' has been provided # And: 'ZoneAwarenessEnabled' in 'ElasticsearchClusterConfig' has not been provided # or provided and set to a value other than bool(true) # And: 'InstanceCount' in 'ElasticsearchClusterConfig' has not been provided or # provided and set to an integer value less than three (< 3) # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'ElasticsearchClusterConfig' has been provided # And: 'ZoneAwarenessEnabled' in 'ElasticsearchClusterConfig' has been provided # and set to bool(true) # And: 'InstanceCount' in 'ElasticsearchClusterConfig' has not been provided or # provided and set to an integer value less than three (< 3) # Then: FAIL # Scenario: 5 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'ElasticsearchClusterConfig' has been provided # And: 'ZoneAwarenessEnabled' in 'ElasticsearchClusterConfig' has not been provided # or provided and set to a value other than bool(true) # And: 'InstanceCount' in 'ElasticsearchClusterConfig' has been provided and set to # an integer value greater than or equal to three (>= 3) # Then: FAIL # Scenario: 6 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'ElasticsearchClusterConfig' has been provided # And: 'ZoneAwarenessEnabled' in 'ElasticsearchClusterConfig' has been provided # and set to bool(true) # And: 'InstanceCount' in 'ElasticsearchClusterConfig' has been provided and set to # an integer value greater than or equal to three (>= 3) # Then: PASS # # Constants # let ELASTICSEARCH_DOMAIN_TYPE = "AWS::Elasticsearch::Domain" let INPUT_DOCUMENT = this # # Assignments # let elasticsearch_domains = Resources.*[ Type == %ELASTICSEARCH_DOMAIN_TYPE ] # # Primary Rules # rule elasticsearch_data_node_fault_tolerance_check when is_cfn_template(%INPUT_DOCUMENT) %elasticsearch_domains not empty { check(%elasticsearch_domains.Properties) << [CT.OPENSEARCH.PR.6]: Require an Elasticsearch domain to have zone awareness and at least three data nodes [FIX]: Within 'ElasticsearchClusterConfig', set 'ZoneAwarenessEnabled' to 'true', and set 'InstanceCount' to an integer value greater than or equal to three. >> } rule elasticsearch_data_node_fault_tolerance_check when is_cfn_hook(%INPUT_DOCUMENT, %ELASTICSEARCH_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%ELASTICSEARCH_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.6]: Require an Elasticsearch domain to have zone awareness and at least three data nodes [FIX]: Within 'ElasticsearchClusterConfig', set 'ZoneAwarenessEnabled' to 'true', and set 'InstanceCount' to an integer value greater than or equal to three. >> } # # Parameterized Rules # rule check(elasticsearch_domain) { %elasticsearch_domain { # Scenario 2 ElasticsearchClusterConfig exists ElasticsearchClusterConfig is_struct ElasticsearchClusterConfig { # Scenario 3, 4, 5 and 6 ZoneAwarenessEnabled exists ZoneAwarenessEnabled == true InstanceCount exists InstanceCount >= 3 } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists }

CT.OPENSEARCH.PR.6 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' ElasticsearchClusterConfig: InstanceType: t3.small.elasticsearch InstanceCount: 3 ZoneAwarenessEnabled: true ZoneAwarenessConfig: AvailabilityZoneCount: 3

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' ElasticsearchClusterConfig: InstanceType: t3.small.elasticsearch ZoneAwarenessEnabled: false InstanceCount: 2

[CT.OPENSEARCH.PR.7] Require an Elasticsearch domain to have at least three dedicated master nodes

This control checks whether Elasticsearch domains are configured with at least three dedicated master nodes.

  • Control objective: Improve availability

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::Elasticsearch::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.7 rule specification

Details and examples

Explanation

An Elasticsearch domain requires at least three dedicated master nodes for high availability and fault-tolerance. Dedicated master node resources can be strained during data node blue/green deployments, because additional nodes must be managed. Deploying an Elasticsearch domain with at least three dedicated master nodes ensures that sufficient master node resource capacity exists, and that cluster operations can continue if a node fails.

Remediation for rule failure

Within ElasticsearchClusterConfig, set DedicatedMasterEnabled to true, and set DedicatedMasterCount to an integer value greater than or equal to three, or omit the DedicatedMasterCount property to adopt the default value of three.

The examples that follow show how to implement this remediation.

Elasticsearch Domain - Example One

An Elasticsearch domain configured with three dedicated master nodes by means of the DedicatedMasterCount property. The example is shown in JSON and in YAML.

JSON example

{ "ElasticsearchDomain": { "Type": "AWS::Elasticsearch::Domain", "Properties": { "ElasticsearchVersion": 7.1, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "ElasticsearchClusterConfig": { "InstanceType": "t3.small.elasticsearch", "DedicatedMasterEnabled": true, "DedicatedMasterCount": 3 } } } }

YAML example

ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' ElasticsearchClusterConfig: InstanceType: t3.small.elasticsearch DedicatedMasterEnabled: true DedicatedMasterCount: 3

The examples that follow show how to implement this remediation.

Elasticsearch Domain - Example Two

An Elasticsearch domain configured with three dedicated master nodes by means of the AWS CloudFormation defaults. The example is shown in JSON and in YAML.

JSON example

{ "ElasticsearchDomain": { "Type": "AWS::Elasticsearch::Domain", "Properties": { "ElasticsearchVersion": 7.1, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "ElasticsearchClusterConfig": { "InstanceType": "t3.small.elasticsearch", "DedicatedMasterEnabled": true } } } }

YAML example

ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' ElasticsearchClusterConfig: InstanceType: t3.small.elasticsearch DedicatedMasterEnabled: true

CT.OPENSEARCH.PR.7 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # elasticsearch_primary_node_fault_tolerance_check # # Description: # This control checks whether Elasticsearch domains are configured with at least three dedicated master nodes. # # Reports on: # AWS::Elasticsearch::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any Elasticsearch domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'ElasticsearchClusterConfig' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'ElasticsearchClusterConfig' has been provided # And: 'DedicatedMasterEnabled' in 'ElasticsearchClusterConfig' has not been provided # or provided and set to a value other than bool(true) # And: 'DedicatedMasterCount' in 'ElasticsearchClusterConfig' has not been provided or # provided and set to an integer value less than three (< 3) # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'ElasticsearchClusterConfig' has been provided # And: 'DedicatedMasterEnabled' in 'ElasticsearchClusterConfig' has not been provided # or provided and set to a value other than bool(true) # And: 'DedicatedMasterCount' in 'ElasticsearchClusterConfig' has been provided and set to # an integer value greater than or equal to three (>= 3) # Then: FAIL # Scenario: 5 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'ElasticsearchClusterConfig' has been provided # And: 'DedicatedMasterEnabled' in 'ElasticsearchClusterConfig' has been provided # and set to bool(true) # And: 'DedicatedMasterCount' in 'ElasticsearchClusterConfig' has been provided and set # to an integer value less than three (< 3) # Then: FAIL # Scenario: 6 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'ElasticsearchClusterConfig' has been provided # And: 'DedicatedMasterEnabled' in 'ElasticsearchClusterConfig' has been provided # and set to bool(true) # And: 'DedicatedMasterCount' in 'ElasticsearchClusterConfig' has not been provided # Then: PASS # Scenario: 7 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'ElasticsearchClusterConfig' has been provided # And: 'DedicatedMasterEnabled' in 'ElasticsearchClusterConfig' has been provided # and set to bool(true) # And: 'DedicatedMasterCount' in 'ElasticsearchClusterConfig' has been provided and set to # an integer value greater than or equal to three (>= 3) # Then: PASS # # Constants # let ELASTICSEARCH_DOMAIN_TYPE = "AWS::Elasticsearch::Domain" let INPUT_DOCUMENT = this # # Assignments # let elasticsearch_domains = Resources.*[ Type == %ELASTICSEARCH_DOMAIN_TYPE ] # # Primary Rules # rule elasticsearch_primary_node_fault_tolerance_check when is_cfn_template(%INPUT_DOCUMENT) %elasticsearch_domains not empty { check(%elasticsearch_domains.Properties) << [CT.OPENSEARCH.PR.7]: Require an Elasticsearch domain to have at least three dedicated master nodes [FIX]: Within 'ElasticsearchClusterConfig', set 'DedicatedMasterEnabled' to 'true', and set 'DedicatedMasterCount' to an integer value greater than or equal to three, or omit the 'DedicatedMasterCount' property to adopt the default value of three. >> } rule elasticsearch_primary_node_fault_tolerance_check when is_cfn_hook(%INPUT_DOCUMENT, %ELASTICSEARCH_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%ELASTICSEARCH_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.7]: Require an Elasticsearch domain to have at least three dedicated master nodes [FIX]: Within 'ElasticsearchClusterConfig', set 'DedicatedMasterEnabled' to 'true', and set 'DedicatedMasterCount' to an integer value greater than or equal to three, or omit the 'DedicatedMasterCount' property to adopt the default value of three. >> } # # Parameterized Rules # rule check(elasticsearch_domain) { %elasticsearch_domain { # Scenario 2 ElasticsearchClusterConfig exists ElasticsearchClusterConfig is_struct ElasticsearchClusterConfig { # Scenario 3, 4, 5 and 6 DedicatedMasterEnabled exists DedicatedMasterEnabled == true DedicatedMasterCount not exists or DedicatedMasterCount >= 3 } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists }

CT.OPENSEARCH.PR.7 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceType: t3.small.elasticsearch DedicatedMasterEnabled: true EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*'

PASS Example - Use this template to verify a compliant resource creation.

Resources: ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceType: t3.small.elasticsearch DedicatedMasterEnabled: true DedicatedMasterCount: 3 EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*'

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceType: t3.small.elasticsearch DedicatedMasterEnabled: false EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*'

[CT.OPENSEARCH.PR.8] Require an Elasticsearch Service domain to use TLSv1.2

This control checks whether Elasticsearch Service domains are configured to require HTTPS with a minimum TLS version of TLSv1.2.

  • Control objective: Encrypt data in transit

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::Elasticsearch::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.8 rule specification

Details and examples

Explanation

HTTPS (TLS) can help prevent potential attackers from using person-in-the-middle, or similar attacks, to eavesdrop on or manipulate network traffic. Only encrypted connections over HTTPS (TLS) should be allowed. Encrypting data in transit can affect performance. You should test your application with this feature to understand the performance profile and the effects of TLS. TLS 1.2 provides several security enhancements over previous versions of TLS.

Remediation for rule failure

Within DomainEndpointOptions, set EnforceHTTPS to true and set TLSSecurityPolicy to Policy-Min-TLS-1-2-2019-07.

The examples that follow show how to implement this remediation.

Elasticsearch Domain - Example

An Elasticsearch domain configured to require that all traffic to the domain arrives over HTTPS with a minimum TLS version of TLSv1.2. The example is shown in JSON and in YAML.

JSON example

{ "ElasticsearchDomain": { "Type": "AWS::Elasticsearch::Domain", "Properties": { "ElasticsearchVersion": 7.1, "ElasticsearchClusterConfig": { "InstanceCount": "1", "InstanceType": "t3.small.elasticsearch" }, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "DomainEndpointOptions": { "EnforceHTTPS": true, "TLSSecurityPolicy": "Policy-Min-TLS-1-2-2019-07" } } } }

YAML example

ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' DomainEndpointOptions: EnforceHTTPS: true TLSSecurityPolicy: Policy-Min-TLS-1-2-2019-07

CT.OPENSEARCH.PR.8 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # elasticsearch_https_required_check # # Description: # This control checks whether Elasticsearch domains are configured to require HTTPS with a minimum TLS version of TLSv1.2. # # Reports on: # AWS::Elasticsearch::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any Elasticsearch domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'DomainEndpointOptions' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'DomainEndpointOptions' has been provided # And: 'EnforceHTTPS' in 'DomainEndpointOptions' has not been provided or # has been provided and set to a value other than bool(true) # And: 'TLSSecurityPolicy' in 'DomainEndpointOptions' has not been provided or # has been provided and set to a value other than 'Policy-Min-TLS-1-2-2019-07' # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'DomainEndpointOptions' has been provided # And: 'EnforceHTTPS' in 'DomainEndpointOptions' has been provided and set to bool(true) # And: 'TLSSecurityPolicy' in 'DomainEndpointOptions' has not been provided or # has been provided and set to a value other than 'Policy-Min-TLS-1-2-2019-07' # Then: FAIL # Scenario: 5 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'DomainEndpointOptions' has been provided # And: 'EnforceHTTPS' in 'DomainEndpointOptions' has not been provided or # has been provided and set to a value other than bool(true) # And: 'TLSSecurityPolicy' in 'DomainEndpointOptions' has been provided and set # to 'Policy-Min-TLS-1-2-2019-07' # Then: FAIL # Scenario: 6 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an Elasticsearch domain resource # And: 'DomainEndpointOptions' has been provided # And: 'EnforceHTTPS' in 'DomainEndpointOptions' has been provided and set to bool(true) # And: 'TLSSecurityPolicy' in 'DomainEndpointOptions' has been provided and set # to 'Policy-Min-TLS-1-2-2019-07' # Then: PASS # # Constants # let ELASTICSEARCH_DOMAIN_TYPE = "AWS::Elasticsearch::Domain" let ALLOWED_TLS_POLICIES = [ "Policy-Min-TLS-1-2-2019-07" ] let INPUT_DOCUMENT = this # # Assignments # let elasticsearch_domains = Resources.*[ Type == %ELASTICSEARCH_DOMAIN_TYPE ] # # Primary Rules # rule elasticsearch_https_required_check when is_cfn_template(%INPUT_DOCUMENT) %elasticsearch_domains not empty { check(%elasticsearch_domains.Properties) << [CT.OPENSEARCH.PR.8]: Require an Elasticsearch Service domain to use TLSv1.2 [FIX]: Within 'DomainEndpointOptions', set 'EnforceHTTPS' to 'true' and set 'TLSSecurityPolicy' to 'Policy-Min-TLS-1-2-2019-07'. >> } rule elasticsearch_https_required_check when is_cfn_hook(%INPUT_DOCUMENT, %ELASTICSEARCH_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%ELASTICSEARCH_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.8]: Require an Elasticsearch Service domain to use TLSv1.2 [FIX]: Within 'DomainEndpointOptions', set 'EnforceHTTPS' to 'true' and set 'TLSSecurityPolicy' to 'Policy-Min-TLS-1-2-2019-07'. >> } # # Parameterized Rules # rule check(elasticsearch_domain) { %elasticsearch_domain { # Scenario 2 DomainEndpointOptions exists DomainEndpointOptions is_struct DomainEndpointOptions { # Scenarios 3, 4, 5 and 6 EnforceHTTPS exists EnforceHTTPS == true TLSSecurityPolicy exists TLSSecurityPolicy in %ALLOWED_TLS_POLICIES } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists }

CT.OPENSEARCH.PR.8 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' DomainEndpointOptions: EnforceHTTPS: true TLSSecurityPolicy: Policy-Min-TLS-1-2-2019-07

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: ElasticsearchDomain: Type: AWS::Elasticsearch::Domain Properties: ElasticsearchVersion: 7.1 ElasticsearchClusterConfig: InstanceCount: '1' InstanceType: t3.small.elasticsearch EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' DomainEndpointOptions: EnforceHTTPS: true TLSSecurityPolicy: Policy-Min-TLS-1-0-2019-07

[CT.OPENSEARCH.PR.9] Require an Amazon OpenSearch Service domain to encrypt data at rest

This control checks whether Amazon OpenSearch Service domains have encryption-at-rest enabled.

  • Control objective: Encrypt data at rest

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::OpenSearchService::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.9 rule specification

Details and examples

Explanation

For an added layer of security for sensitive data, you should configure your OpenSearch Service domain to be encrypted at rest. When you configure encryption of data at rest, AWS KMS stores and manages your encryption keys. To perform the encryption, AWS KMS uses the Advanced Encryption Standard algorithm with 256-bit keys (AES-256).

Remediation for rule failure

Within EncryptionAtRestOptions, set Enabled to true.

The examples that follow show how to implement this remediation.

Amazon OpenSearch Service Domain - Example

An Amazon OpenSearch Service domain configured with encryption-at-rest enabled The example is shown in JSON and in YAML.

JSON example

{ "OpenSearchServiceDomain": { "Type": "AWS::OpenSearchService::Domain", "Properties": { "EngineVersion": "OpenSearch_1.3", "ClusterConfig": { "InstanceCount": "1", "InstanceType": "t3.small.search" }, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "EncryptionAtRestOptions": { "Enabled": true } } } }

YAML example

OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' EncryptionAtRestOptions: Enabled: true

CT.OPENSEARCH.PR.9 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # opensearch_encrypted_at_rest_check # # Description: # This control checks whether Amazon OpenSearch Service domains have encryption-at-rest enabled. # # Reports on: # AWS::OpenSearchService::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any OpenSearch Service domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'EncryptionAtRestOptions' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'EncryptionAtRestOptions' has been provided # And: In 'EncryptionAtRestOptions', 'Enabled' has not been provided or provided # and set to a value other than bool(true) # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'EncryptionAtRestOptions' has been provided # And: In 'EncryptionAtRestOptions','Enabled' has been provided and set to bool(true) # Then: PASS # # Constants # let OPENSEARCH_SERVICE_DOMAIN_TYPE = "AWS::OpenSearchService::Domain" let INPUT_DOCUMENT = this # # Assignments # let opensearch_service_domains = Resources.*[ Type == %OPENSEARCH_SERVICE_DOMAIN_TYPE ] # # Primary Rules # rule opensearch_encrypted_at_rest_check when is_cfn_template(%INPUT_DOCUMENT) %opensearch_service_domains not empty { check(%opensearch_service_domains.Properties) << [CT.OPENSEARCH.PR.9]: Require an Amazon OpenSearch Service domain to encrypt data at rest [FIX]: Within 'EncryptionAtRestOptions', set 'Enabled' to 'true'. >> } rule opensearch_encrypted_at_rest_check when is_cfn_hook(%INPUT_DOCUMENT, %OPENSEARCH_SERVICE_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%OPENSEARCH_SERVICE_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.9]: Require an Amazon OpenSearch Service domain to encrypt data at rest [FIX]: Within 'EncryptionAtRestOptions', set 'Enabled' to 'true'. >> } # # Parameterized Rules # rule check(opensearch_service_domain) { %opensearch_service_domain { # Scenario 2 EncryptionAtRestOptions exists EncryptionAtRestOptions is_struct EncryptionAtRestOptions { # Scenarios 3 and 4 Enabled exists Enabled == true } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists }

CT.OPENSEARCH.PR.9 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' EncryptionAtRestOptions: Enabled: true

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' EncryptionAtRestOptions: Enabled: false

[CT.OPENSEARCH.PR.10] Require an Amazon OpenSearch Service domain to be created in a user-specified Amazon VPC

This control checks whether Amazon OpenSearch Service domains are configured with VPC option settings that specify a target Amazon VPC.

  • Control objective: Limit network access

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::OpenSearchService::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.10 rule specification

Details and examples

Explanation

Ensure that OpenSearch domains are not attached to public subnets. See Resource-based policies in the Amazon OpenSearch Service Developer Guide. Also ensure that your VPC is configured according to the recommended best practices. See Security best practices for your VPC in the Amazon VPC User Guide.

OpenSearch domains deployed within a VPC can communicate with VPC resources over the private AWS network, without the need to traverse the public internet. This configuration increases the security posture by limiting access to the data in transit. VPCs provide a number of network controls to secure access to OpenSearch domains, including network ACL and security groups. AWS Control Tower recommends that you migrate public OpenSearch domains to VPCs to take advantage of these controls.

Remediation for rule failure

Within VPCOptions, set SubnetIds to a list with one or more AAmazon EC2 subnet IDs.

The examples that follow show how to implement this remediation.

Amazon OpenSearch Service Domain - Example

An Amazon OpenSearch Service domain configured to deploy within an Amazon VPC by means of VPC option settings. The example is shown in JSON and in YAML.

JSON example

{ "OpenSearchServiceDomain": { "Type": "AWS::OpenSearchService::Domain", "Properties": { "EngineVersion": "OpenSearch_1.3", "ClusterConfig": { "InstanceCount": "1", "InstanceType": "t3.small.search" }, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "VPCOptions": { "SubnetIds": [ { "Ref": "Subnet" } ] } } } }

YAML example

OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' VPCOptions: SubnetIds: - !Ref 'Subnet'

CT.OPENSEARCH.PR.10 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # opensearch_in_vpc_only_check # # Description: # This control checks whether Amazon OpenSearch Service domains are configured with VPC option settings that specify a target Amazon VPC. # # Reports on: # AWS::OpenSearchService::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any OpenSearch Service domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'VPCOptions' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'VPCOptions' has been provided # And: 'SubnetIds' in 'VPCOptions' has not been provided or has been provided # as an empty list # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'VPCOptions' has been provided # And: 'SubnetIds' in 'VPCOptions' has been provided as a list with one or more values # Then: PASS # # Constants # let OPENSEARCH_SERVICE_DOMAIN_TYPE = "AWS::OpenSearchService::Domain" let INPUT_DOCUMENT = this # # Assignments # let opensearch_service_domains = Resources.*[ Type == %OPENSEARCH_SERVICE_DOMAIN_TYPE ] # # Primary Rules # rule opensearch_in_vpc_only_check when is_cfn_template(%INPUT_DOCUMENT) %opensearch_service_domains not empty { check(%opensearch_service_domains.Properties) << [CT.OPENSEARCH.PR.10]: Require an Amazon OpenSearch Service domain to be created in a user-specified Amazon VPC [FIX]: Within 'VPCOptions', set 'SubnetIds' to a list with one or more Amazon EC2 subnet IDs. >> } rule opensearch_in_vpc_only_check when is_cfn_hook(%INPUT_DOCUMENT, %OPENSEARCH_SERVICE_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%OPENSEARCH_SERVICE_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.10]: Require an Amazon OpenSearch Service domain to be created in a user-specified Amazon VPC [FIX]: Within 'VPCOptions', set 'SubnetIds' to a list with one or more Amazon EC2 subnet IDs. >> } # # Parameterized Rules # rule check(opensearch_service_domain) { %opensearch_service_domain { # Scenario 2 VPCOptions exists VPCOptions is_struct VPCOptions { # Scenarios 3 and 4 SubnetIds exists SubnetIds is_list SubnetIds not empty } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists }

CT.OPENSEARCH.PR.10 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: VPC: Type: 'AWS::EC2::VPC' Properties: CidrBlock: 10.0.0.0/16 Subnet: Type: 'AWS::EC2::Subnet' Properties: VpcId: Ref: VPC CidrBlock: 10.0.0.0/16 AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: '' OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' VPCOptions: SubnetIds: - Ref: Subnet

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*'

[CT.OPENSEARCH.PR.11] Require an Amazon OpenSearch Service domain to encrypt data sent between nodes

This control checks whether Amazon OpenSearch Service domains have node-to-node encryption enabled.

  • Control objective: Encrypt data in transit

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::OpenSearchService::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.11 rule specification

Details and examples

Explanation

HTTPS (TLS) can help prevent potential attackers from eavesdropping on or manipulating network traffic using person-in-the-middle, or similar, attacks. Only encrypted connections over HTTPS (TLS) should be allowed. Enabling node-to-node encryption for OpenSearch domains ensures that intra-cluster communications are encrypted in transit.

Usage considerations
  • A performance penalty may be associated with this configuration. You should be aware of the performance trade-offs and test them before enabling this option.

Remediation for rule failure

Within NodeToNodeEncryptionOptions, set Enabled to true.

The examples that follow show how to implement this remediation.

Amazon OpenSearch Service Domain - Example

An Amazon OpenSearch Service domain configured with node-to-node encryption enabled. The example is shown in JSON and in YAML.

JSON example

{ "OpenSearchServiceDomain": { "Type": "AWS::OpenSearchService::Domain", "Properties": { "EngineVersion": "OpenSearch_1.3", "ClusterConfig": { "InstanceCount": "1", "InstanceType": "t3.small.search" }, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "NodeToNodeEncryptionOptions": { "Enabled": true } } } }

YAML example

OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: Enabled: true

CT.OPENSEARCH.PR.11 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # opensearch_node_to_node_encryption_check # # Description: # This control checks whether Amazon OpenSearch Service domains have node-to-node encryption enabled. # # Reports on: # AWS::OpenSearchService::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any OpenSearch Service domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'NodeToNodeEncryptionOptions' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'NodeToNodeEncryptionOptions' has been provided # And: In 'NodeToNodeEncryptionOptions', 'Enabled' has not been provided or has been provided and set to a # value other than bool(true) # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'NodeToNodeEncryptionOptions' has been provided # And: In 'NodeToNodeEncryptionOptions', 'Enabled' has been provided and set to a # value of bool(true) # Then: PASS # # Constants # let OPENSEARCH_SERVICE_DOMAIN_TYPE = "AWS::OpenSearchService::Domain" let INPUT_DOCUMENT = this # # Assignments # let opensearch_service_domains = Resources.*[ Type == %OPENSEARCH_SERVICE_DOMAIN_TYPE ] # # Primary Rules # rule opensearch_node_to_node_encryption_check when is_cfn_template(%INPUT_DOCUMENT) %opensearch_service_domains not empty { check(%opensearch_service_domains.Properties) << [CT.OPENSEARCH.PR.11]: Require an Amazon OpenSearch Service domain to encrypt data sent between nodes [FIX]: Within 'NodeToNodeEncryptionOptions', set 'Enabled' to 'true'. >> } rule opensearch_node_to_node_encryption_check when is_cfn_hook(%INPUT_DOCUMENT, %OPENSEARCH_SERVICE_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%OPENSEARCH_SERVICE_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.11]: Require an Amazon OpenSearch Service domain to encrypt data sent between nodes [FIX]: Within 'NodeToNodeEncryptionOptions', set 'Enabled' to 'true'. >> } # # Parameterized Rules # rule check(opensearch_service_domain) { %opensearch_service_domain { # Scenario 2 NodeToNodeEncryptionOptions exists NodeToNodeEncryptionOptions is_struct NodeToNodeEncryptionOptions { # Scenarios 3 and 4 Enabled exists Enabled == true } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists }

CT.OPENSEARCH.PR.11 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: Enabled: true

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: {}

[CT.OPENSEARCH.PR.12] Require an Amazon OpenSearch Service domain to send error logs to Amazon CloudWatch Logs

This control checks whether Amazon OpenSearch Service domains are configured to send error logs to an Amazon CloudWatch Logs log group.

  • Control objective: Establish logging and monitoring

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::OpenSearchService::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.12 rule specification

Details and examples

Explanation

Enable error logs (ES_APPLICATION_LOGS) for OpenSearch Service domains and send those logs to Amazon CloudWatch Logs for retention and response. Domain error logs can assist with security and access audits, and can help to diagnose availability issues.

Remediation for rule failure

Within LogPublishingOptions, provide an ES_APPLICATION_LOGS configuration, set Enabled to true, and set CloudWatchLogsLogGroupArn to the ARN of a valid Amazon CloudWatch Logs log group.

The examples that follow show how to implement this remediation.

Amazon OpenSearch Service Domain - Example

An Amazon OpenSearch Service domain configured to send error logs to Amazon CloudWatch Logs. The example is shown in JSON and in YAML.

JSON example

{ "OpenSearchServiceDomain": { "Type": "AWS::OpenSearchService::Domain", "Properties": { "EngineVersion": "OpenSearch_1.3", "ClusterConfig": { "InstanceCount": "1", "InstanceType": "t3.small.search" }, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "LogPublishingOptions": { "ES_APPLICATION_LOGS": { "CloudWatchLogsLogGroupArn": { "Fn::GetAtt": [ "LogGroup", "Arn" ] }, "Enabled": true } } } } }

YAML example

OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' LogPublishingOptions: ES_APPLICATION_LOGS: CloudWatchLogsLogGroupArn: !GetAtt 'LogGroup.Arn' Enabled: true

CT.OPENSEARCH.PR.12 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # opensearch_application_logging_enabled_check # # Description: # This control checks whether Amazon OpenSearch Service domains are configured to send error logs to an Amazon CloudWatch Logs log group. # # Reports on: # AWS::OpenSearchService::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any OpenSearch Service domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'LogPublishingOptions' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'LogPublishingOptions' has been provided # And: 'ES_APPLICATION_LOGS' in 'LogPublishingOptions' has not been provided # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'LogPublishingOptions' has been specified # And: 'ES_APPLICATION_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'ES_APPLICATION_LOGS' has not been provided or provided and set to # a value other than bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'ES_APPLICATION_LOGS' has not been provided or provided # as an empty string or invalid local reference # Then: FAIL # Scenario: 5 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service Domain resource # And: 'LogPublishingOptions' has been specified # And: 'ES_APPLICATION_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'ES_APPLICATION_LOGS' has been provided and set to bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'ES_APPLICATION_LOGS' has not been provided or provided # as an empty string or invalid local reference # Then: FAIL # Scenario: 6 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service Domain resource # And: 'LogPublishingOptions' has been specified # And: 'ES_APPLICATION_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'ES_APPLICATION_LOGS' has not been provided or provided and set to # a value other than bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'ES_APPLICATION_LOGS' has been provided as a non-empty string # or valid local reference # Then: FAIL # Scenario: 7 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service Domain resource # And: 'LogPublishingOptions' has been specified # And: 'ES_APPLICATION_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'ES_APPLICATION_LOGS' has been provided and set to bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'ES_APPLICATION_LOGS' has been provided as a non-empty string # or valid local reference # Then: PASS # # Constants # let OPENSEARCH_SERVICE_DOMAIN_TYPE = "AWS::OpenSearchService::Domain" let INPUT_DOCUMENT = this # # Assignments # let opensearch_service_domains = Resources.*[ Type == %OPENSEARCH_SERVICE_DOMAIN_TYPE ] # # Primary Rules # rule opensearch_application_logging_enabled_check when is_cfn_template(%INPUT_DOCUMENT) %opensearch_service_domains not empty { check(%opensearch_service_domains.Properties) << [CT.OPENSEARCH.PR.12]: Require an Amazon OpenSearch Service domain to send error logs to Amazon CloudWatch Logs [FIX]: Within 'LogPublishingOptions', provide an 'ES_APPLICATION_LOGS' configuration, set 'Enabled' to 'true', and set 'CloudWatchLogsLogGroupArn' to the ARN of a valid Amazon CloudWatch Logs log group. >> } rule opensearch_application_logging_enabled_check when is_cfn_hook(%INPUT_DOCUMENT, %OPENSEARCH_SERVICE_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%OPENSEARCH_SERVICE_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.12]: Require an Amazon OpenSearch Service domain to send error logs to Amazon CloudWatch Logs [FIX]: Within 'LogPublishingOptions', provide an 'ES_APPLICATION_LOGS' configuration, set 'Enabled' to 'true', and set 'CloudWatchLogsLogGroupArn' to the ARN of a valid Amazon CloudWatch Logs log group. >> } # # Parameterized Rules # rule check(opensearch_service_domain) { %opensearch_service_domain { # Scenario 2 LogPublishingOptions exists LogPublishingOptions is_struct LogPublishingOptions { # Scenario 3 ES_APPLICATION_LOGS exists ES_APPLICATION_LOGS is_struct ES_APPLICATION_LOGS { # Scenarios 4, 5, 6 and 7 Enabled exists Enabled == true CloudWatchLogsLogGroupArn exists check_is_string_and_not_empty(CloudWatchLogsLogGroupArn) or check_local_references(%INPUT_DOCUMENT, CloudWatchLogsLogGroupArn, "AWS::Logs::LogGroup") } } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists } rule check_is_string_and_not_empty(value) { %value { this is_string this != /\A\s*\z/ } } rule check_local_references(doc, reference_properties, referenced_resource_type) { %reference_properties { 'Fn::GetAtt' { query_for_resource(%doc, this[0], %referenced_resource_type) <<Local Stack reference was invalid>> } or Ref { query_for_resource(%doc, this, %referenced_resource_type) <<Local Stack reference was invalid>> } } } rule query_for_resource(doc, resource_key, referenced_resource_type) { let referenced_resource = %doc.Resources[ keys == %resource_key ] %referenced_resource not empty %referenced_resource { Type == %referenced_resource_type } }

CT.OPENSEARCH.PR.12 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain DependsOn: LogGroupPolicy Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' LogPublishingOptions: ES_APPLICATION_LOGS: CloudWatchLogsLogGroupArn: Fn::GetAtt: - LogGroup - Arn Enabled: true LogGroup: Type: AWS::Logs::LogGroup LogGroupPolicy: Type: AWS::Logs::ResourcePolicy Properties: PolicyName: Fn::Sub: ${AWS::StackName}-AllowOS PolicyDocument: Fn::Sub: - '{"Version": "2012-10-17","Statement":[{"Effect":"Allow","Principal": {"Service": ["es.amazonaws.com"]},"Action":["logs:PutLogEvents","logs:CreateLogStream"],"Resource":"${LogGroupArn}","Condition":{"StringEquals":{"aws:SourceAccount": "${AWS::AccountId}"}}}]}' - LogGroupArn: Fn::GetAtt: [LogGroup, Arn]

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*'

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' LogPublishingOptions: ES_APPLICATION_LOGS: Enabled: false

[CT.OPENSEARCH.PR.13] Require an Amazon OpenSearch Service domain to send audit logs to Amazon CloudWatch Logs

This control checks whether Amazon OpenSearch Service domains are configured to send audit logs to an Amazon CloudWatch Logs log group.

  • Control objective: Establish logging and monitoring

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::OpenSearchService::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.13 rule specification

Details and examples

Explanation

Audit logs are highly customizable. They allow you to track user activity on your OpenSearch clusters, including authentication successes and failures, requests to OpenSearch, index changes, and incoming search queries.

Usage considerations
  • Audit log publishing requires advanced security options to be enabled on Amazon OpenSearch Service domains.

  • To enable advanced security options on an Amazon OpenSearch Service domain, you must enable encryption of data at rest by means of the EncryptionAtRestOptions property, node-to-node encryption by means of the NodeToNodeEncryptionOptions property, and enforce HTTPS connections by means of the EnforceHTTPS property within DomainEndpointOptions.

Remediation for rule failure

Within LogPublishingOptions, provide an AUDIT_LOGS configuration, set Enabled to true and CloudWatchLogsLogGroupArn to the ARN of a valid Amazon CloudWatch Logs log group.

The examples that follow show how to implement this remediation.

Amazon OpenSearch Service Domain - Example

An Amazon OpenSearch Service domain configured to send audit logs to Amazon CloudWatch Logs. The example is shown in JSON and in YAML.

JSON example

{ "OpenSearchServiceDomain": { "Type": "AWS::OpenSearchService::Domain", "DependsOn": "LogGroupPolicy", "Properties": { "EngineVersion": "OpenSearch_1.3", "ClusterConfig": { "InstanceCount": "1", "InstanceType": "t3.small.search" }, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "NodeToNodeEncryptionOptions": { "Enabled": true }, "EncryptionAtRestOptions": { "Enabled": true }, "DomainEndpointOptions": { "EnforceHTTPS": true }, "AdvancedSecurityOptions": { "Enabled": true, "InternalUserDatabaseEnabled": false, "MasterUserOptions": { "MasterUserARN": { "Fn::GetAtt": [ "IAMRole", "Arn" ] } } }, "LogPublishingOptions": { "AUDIT_LOGS": { "CloudWatchLogsLogGroupArn": { "Fn::GetAtt": [ "LogGroup", "Arn" ] }, "Enabled": true } } } } }

YAML example

OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain DependsOn: LogGroupPolicy Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: Enabled: true EncryptionAtRestOptions: Enabled: true DomainEndpointOptions: EnforceHTTPS: true AdvancedSecurityOptions: Enabled: true InternalUserDatabaseEnabled: false MasterUserOptions: MasterUserARN: !GetAtt 'IAMRole.Arn' LogPublishingOptions: AUDIT_LOGS: CloudWatchLogsLogGroupArn: !GetAtt 'LogGroup.Arn' Enabled: true

CT.OPENSEARCH.PR.13 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # opensearch_audit_logging_enabled_check # # Description: # This control checks whether Amazon OpenSearch Service domains are configured to send audit logs to an Amazon CloudWatch Logs log group. # # Reports on: # AWS::OpenSearchService::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any OpenSearch Service domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'LogPublishingOptions' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'LogPublishingOptions' has been provided # And: 'AUDIT_LOGS' in 'LogPublishingOptions' has not been provided # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'LogPublishingOptions' has been specified # And: 'AUDIT_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'AUDIT_LOGS' has not been provided or provided and set to # a value other than bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'AUDIT_LOGS' has not been provided or provided # as an empty string or invalid local reference # Then: FAIL # Scenario: 5 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'LogPublishingOptions' has been specified # And: 'AUDIT_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'AUDIT_LOGS' has been provided and set to bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'AUDIT_LOGS' has not been provided or provided # as an empty string or invalid local reference # Then: FAIL # Scenario: 6 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'LogPublishingOptions' has been specified # And: 'AUDIT_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'AUDIT_LOGS' has not been provided or provided and set to # a value other than bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'AUDIT_LOGS' has been provided as a non-empty string # or valid local reference # Then: FAIL # Scenario: 7 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'LogPublishingOptions' has been specified # And: 'AUDIT_LOGS' in 'LogPublishingOptions' has been provided # And: 'Enabled' in 'AUDIT_LOGS' has been provided and set to bool(true) # And: 'CloudWatchLogsLogGroupArn' in 'AUDIT_LOGS' has been provided as a non-empty string # or valid local reference # Then: PASS # # Constants # let OPENSEARCH_SERVICE_DOMAIN_TYPE = "AWS::OpenSearchService::Domain" let INPUT_DOCUMENT = this # # Assignments # let opensearch_service_domains = Resources.*[ Type == %OPENSEARCH_SERVICE_DOMAIN_TYPE ] # # Primary Rules # rule opensearch_audit_logging_enabled_check when is_cfn_template(%INPUT_DOCUMENT) %opensearch_service_domains not empty { check(%opensearch_service_domains.Properties) << [CT.OPENSEARCH.PR.13]: Require an Amazon OpenSearch Service domain to send audit logs to Amazon CloudWatch Logs [FIX]: Within 'LogPublishingOptions', provide an 'AUDIT_LOGS' configuration, set 'Enabled' to 'true' and 'CloudWatchLogsLogGroupArn' to the ARN of a valid Amazon CloudWatch Logs log group. >> } rule opensearch_audit_logging_enabled_check when is_cfn_hook(%INPUT_DOCUMENT, %OPENSEARCH_SERVICE_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%OPENSEARCH_SERVICE_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.13]: Require an Amazon OpenSearch Service domain to send audit logs to Amazon CloudWatch Logs [FIX]: Within 'LogPublishingOptions', provide an 'AUDIT_LOGS' configuration, set 'Enabled' to 'true' and 'CloudWatchLogsLogGroupArn' to the ARN of a valid Amazon CloudWatch Logs log group. >> } # # Parameterized Rules # rule check(opensearch_service_domain) { %opensearch_service_domain { # Scenario 2 LogPublishingOptions exists LogPublishingOptions is_struct LogPublishingOptions { # Scenario 3 AUDIT_LOGS exists AUDIT_LOGS is_struct AUDIT_LOGS { # Scenarios 4, 5, 6 and 7 Enabled exists Enabled == true CloudWatchLogsLogGroupArn exists check_is_string_and_not_empty(CloudWatchLogsLogGroupArn) or check_local_references(%INPUT_DOCUMENT, CloudWatchLogsLogGroupArn, "AWS::Logs::LogGroup") } } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists } rule check_is_string_and_not_empty(value) { %value { this is_string this != /\A\s*\z/ } } rule check_local_references(doc, reference_properties, referenced_resource_type) { %reference_properties { 'Fn::GetAtt' { query_for_resource(%doc, this[0], %referenced_resource_type) <<Local Stack reference was invalid>> } or Ref { query_for_resource(%doc, this, %referenced_resource_type) <<Local Stack reference was invalid>> } } } rule query_for_resource(doc, resource_key, referenced_resource_type) { let referenced_resource = %doc.Resources[ keys == %resource_key ] %referenced_resource not empty %referenced_resource { Type == %referenced_resource_type } }

CT.OPENSEARCH.PR.13 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: IAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: AWS: Ref: AWS::AccountId Action: sts:AssumeRole OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain DependsOn: LogGroupPolicy Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: Enabled: true EncryptionAtRestOptions: Enabled: true DomainEndpointOptions: EnforceHTTPS: true AdvancedSecurityOptions: Enabled: true InternalUserDatabaseEnabled: false MasterUserOptions: MasterUserARN: Fn::GetAtt: - IAMRole - Arn LogPublishingOptions: AUDIT_LOGS: CloudWatchLogsLogGroupArn: Fn::GetAtt: - LogGroup - Arn Enabled: true LogGroup: Type: AWS::Logs::LogGroup LogGroupPolicy: Type: AWS::Logs::ResourcePolicy Properties: PolicyName: AllowES PolicyDocument: Fn::Sub: - '{"Version": "2012-10-17","Statement":[{"Effect":"Allow","Principal": {"Service": ["es.amazonaws.com"]},"Action":["logs:PutLogEvents","logs:CreateLogStream"],"Resource":"${LogGroupArn}","Condition":{"StringEquals":{ "aws:SourceAccount": "${AWS::AccountId}"}}}]}' - LogGroupArn: Fn::GetAtt: [ LogGroup, Arn ]

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: IAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: AWS: Ref: AWS::AccountId Action: sts:AssumeRole OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: Enabled: true EncryptionAtRestOptions: Enabled: true DomainEndpointOptions: EnforceHTTPS: true AdvancedSecurityOptions: Enabled: true InternalUserDatabaseEnabled: false MasterUserOptions: MasterUserARN: Fn::GetAtt: - IAMRole - Arn

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: IAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: AWS: Ref: AWS::AccountId Action: sts:AssumeRole OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: Enabled: true EncryptionAtRestOptions: Enabled: true DomainEndpointOptions: EnforceHTTPS: true AdvancedSecurityOptions: Enabled: true InternalUserDatabaseEnabled: false MasterUserOptions: MasterUserARN: Fn::GetAtt: - IAMRole - Arn LogPublishingOptions: AUDIT_LOGS: Enabled: false

[CT.OPENSEARCH.PR.14] Require an Amazon OpenSearch Service domain to have zone awareness and at least three data nodes

This control checks whether Amazon OpenSearch Service domains are configured with at least three data nodes and zone awareness enabled.

  • Control objective: Improve availability

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::OpenSearchService::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.14 rule specification

Details and examples

Explanation

An OpenSearch domain requires at least three data nodes for high availability and fault-tolerance. Deploying an OpenSearch domain with at least three data nodes ensures that the cluster can remain operative if a node fails.

Remediation for rule failure

Within ClusterConfig, set ZoneAwarenessEnabled to true and InstanceCount to an integer value greater than or equal to three.

The examples that follow show how to implement this remediation.

Amazon OpenSearch Service Domain - Example

An Amazon OpenSearch Service domain configured with three data nodes and zone awareness enabled. The example is shown in JSON and in YAML.

JSON example

{ "OpenSearchServiceDomain": { "Type": "AWS::OpenSearchService::Domain", "Properties": { "EngineVersion": "OpenSearch_1.3", "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "ClusterConfig": { "InstanceType": "t3.small.search", "InstanceCount": 3, "ZoneAwarenessEnabled": true, "ZoneAwarenessConfig": { "AvailabilityZoneCount": 3 } } } } }

YAML example

OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' ClusterConfig: InstanceType: t3.small.search InstanceCount: 3 ZoneAwarenessEnabled: true ZoneAwarenessConfig: AvailabilityZoneCount: 3

CT.OPENSEARCH.PR.14 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # opensearch_data_node_fault_tolerance_check # # Description: # This control checks whether Amazon OpenSearch Service domains are configured with at least three data nodes and zone awareness enabled. # # Reports on: # AWS::OpenSearchService::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any OpenSearch Service domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'ClusterConfig' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'ClusterConfig' has been provided # And: 'ZoneAwarenessEnabled' in 'ClusterConfig' has not been provided # or provided and set to a value other than bool(true) # And: 'InstanceCount' in 'ClusterConfig' has not been provided or # provided and set to an integer value less than three (< 3) # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'ClusterConfig' has been provided # And: 'ZoneAwarenessEnabled' in 'ClusterConfig' has been provided # and set to bool(true) # And: 'InstanceCount' in 'ClusterConfig' has not been provided or # provided and set to an integer value less than three (< 3) # Then: FAIL # Scenario: 5 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'ClusterConfig' has been provided # And: 'ZoneAwarenessEnabled' in 'ClusterConfig' has not been provided # or provided and set to a value other than bool(true) # And: 'InstanceCount' in 'ClusterConfig' has been provided and set to # an integer value greater than or equal to three (>= 3) # Then: FAIL # Scenario: 6 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'ClusterConfig' has been provided # And: 'ZoneAwarenessEnabled' in 'ClusterConfig' has been provided # and set to bool(true) # And: 'InstanceCount' in 'ClusterConfig' has been provided and set to # an integer value greater than or equal to three (>= 3) # Then: PASS # # Constants # let OPENSEARCH_SERVICE_DOMAIN_TYPE = "AWS::OpenSearchService::Domain" let INPUT_DOCUMENT = this # # Assignments # let opensearch_service_domains = Resources.*[ Type == %OPENSEARCH_SERVICE_DOMAIN_TYPE ] # # Primary Rules # rule opensearch_data_node_fault_tolerance_check when is_cfn_template(%INPUT_DOCUMENT) %opensearch_service_domains not empty { check(%opensearch_service_domains.Properties) << [CT.OPENSEARCH.PR.14]: Require an Amazon OpenSearch Service domain to have zone awareness and at least three data nodes [FIX]: Within 'ClusterConfig', set 'ZoneAwarenessEnabled' to 'true' and 'InstanceCount' to an integer value greater than or equal to three. >> } rule opensearch_data_node_fault_tolerance_check when is_cfn_hook(%INPUT_DOCUMENT, %OPENSEARCH_SERVICE_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%OPENSEARCH_SERVICE_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.14]: Require an Amazon OpenSearch Service domain to have zone awareness and at least three data nodes [FIX]: Within 'ClusterConfig', set 'ZoneAwarenessEnabled' to 'true' and 'InstanceCount' to an integer value greater than or equal to three. >> } # # Parameterized Rules # rule check(opensearch_service_domain) { %opensearch_service_domain { # Scenario 2 ClusterConfig exists ClusterConfig is_struct ClusterConfig { # Scenario 3, 4, 5 and 6 ZoneAwarenessEnabled exists ZoneAwarenessEnabled == true InstanceCount exists InstanceCount >= 3 } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists }

CT.OPENSEARCH.PR.14 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' ClusterConfig: InstanceType: t3.small.search InstanceCount: 3 ZoneAwarenessEnabled: true ZoneAwarenessConfig: AvailabilityZoneCount: 3

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceType: t3.small.search ZoneAwarenessEnabled: false InstanceCount: 2 EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*'

[CT.OPENSEARCH.PR.15] Require an Amazon OpenSearch Service domain to use fine-grained access control

This control checks whether Amazon OpenSearch Service domains have fine-grained access control enabled.

  • Control objective: Enforce least privilege

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::OpenSearchService::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.15 rule specification

Details and examples

Explanation

Fine-grained access control offers additional ways of controlling access to your data on Amazon OpenSearch Service.

Usage considerations
  • Fine-grained access control requires that advanced security options must be enabled on Amazon OpenSearch Service domains.

  • To enable advanced security options on an Amazon OpenSearch Service domain, you must enable encryption of data at rest by means of the EncryptionAtRestOptions property, node-to-node encryption by means of the NodeToNodeEncryptionOptions property, and enforce HTTPS connections by means of the EnforceHTTPS property within DomainEndpointOptions.

Remediation for rule failure

Within AdvancedSecurityOptions, set Enabled to true, set InternalUserDatabaseEnabled to true or false, and set MasterUserOptions with an options configuration for your master user.

The examples that follow show how to implement this remediation.

Amazon OpenSearch Service Domain - Example

An Amazon OpenSearch Service domain configured with fine-grained access control. The example is shown in JSON and in YAML.

JSON example

{ "OpenSearchServiceDomain": { "Type": "AWS::OpenSearchService::Domain", "Properties": { "EngineVersion": "OpenSearch_1.3", "ClusterConfig": { "InstanceCount": "1", "InstanceType": "t3.small.search" }, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "NodeToNodeEncryptionOptions": { "Enabled": true }, "EncryptionAtRestOptions": { "Enabled": true }, "DomainEndpointOptions": { "EnforceHTTPS": true }, "AdvancedSecurityOptions": { "Enabled": true, "InternalUserDatabaseEnabled": false, "MasterUserOptions": { "MasterUserARN": { "Fn::GetAtt": [ "IAMRole", "Arn" ] } } } } } }

YAML example

OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: Enabled: true EncryptionAtRestOptions: Enabled: true DomainEndpointOptions: EnforceHTTPS: true AdvancedSecurityOptions: Enabled: true InternalUserDatabaseEnabled: false MasterUserOptions: MasterUserARN: !GetAtt 'IAMRole.Arn'

CT.OPENSEARCH.PR.15 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # opensearch_fine_grained_access_control_enabled_check # # Description: # This control checks whether Amazon OpenSearch Service domains have fine-grained access control enabled. # # Reports on: # AWS::OpenSearchService::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any OpenSearch Service domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'AdvancedSecurityOptions' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'AdvancedSecurityOptions' has been provided # And: 'Enabled' in 'AdvancedSecurityOptions' has not been provided or # has been provided and set to a value other than bool(true) # And: 'InternalUserDatabaseEnabled' in 'AdvancedSecurityOptions' has not been provided or provided and set to a # non boolean value # And: 'MasterUserOptions' in 'AdvancedSecurityOptions' has not been provided or provided and set to a value # other than a struct # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'AdvancedSecurityOptions' has been provided # And: 'Enabled' in 'AdvancedSecurityOptions' has been provided and set to bool(true) # And: 'InternalUserDatabaseEnabled' in 'AdvancedSecurityOptions' has not been provided or provided and set to a # non boolean value # And: 'MasterUserOptions' in 'AdvancedSecurityOptions' has not been provided or provided and set to a value # other than a struct # Then: FAIL # Scenario: 5 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'AdvancedSecurityOptions' has been provided # And: 'Enabled' in 'AdvancedSecurityOptions' has been provided and set to bool(true) # And: 'InternalUserDatabaseEnabled' in 'AdvancedSecurityOptions' has been provided and set to a # boolean value # And: 'MasterUserOptions' in 'AdvancedSecurityOptions' has not been provided or provided and set to a value # other than a struct # Then: FAIL # Scenario: 6 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'AdvancedSecurityOptions' has been provided # And: 'Enabled' in 'AdvancedSecurityOptions' has been provided and set to bool(true) # And: 'InternalUserDatabaseEnabled' in 'AdvancedSecurityOptions' has not been provided or provided and set to a # non boolean value # And: 'MasterUserOptions' in 'AdvancedSecurityOptions' has been provided and set to a struct # Then: FAIL # Scenario: 7 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'AdvancedSecurityOptions' has been provided # And: 'Enabled' in 'AdvancedSecurityOptions' has been provided and set to bool(true) # And: 'InternalUserDatabaseEnabled' in 'AdvancedSecurityOptions' has been provided and set to a # boolean value # And: 'MasterUserOptions' in 'AdvancedSecurityOptions' has been provided and set to a struct # Then: PASS # # Constants # let OPENSEARCH_SERVICE_DOMAIN_TYPE = "AWS::OpenSearchService::Domain" let INPUT_DOCUMENT = this # # Assignments # let opensearch_service_domains = Resources.*[ Type == %OPENSEARCH_SERVICE_DOMAIN_TYPE ] # # Primary Rules # rule opensearch_fine_grained_access_control_enabled_check when is_cfn_template(%INPUT_DOCUMENT) %opensearch_service_domains not empty { check(%opensearch_service_domains.Properties) << [CT.OPENSEARCH.PR.15]: Require an Amazon OpenSearch Service domain to use fine-grained access control [FIX]: Within 'AdvancedSecurityOptions', set 'Enabled' to 'true', set 'InternalUserDatabaseEnabled' to 'true' or 'false', and set 'MasterUserOptions' with an options configuration for your master user. >> } rule opensearch_fine_grained_access_control_enabled_check when is_cfn_hook(%INPUT_DOCUMENT, %OPENSEARCH_SERVICE_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%OPENSEARCH_SERVICE_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.15]: Require an Amazon OpenSearch Service domain to use fine-grained access control [FIX]: Within 'AdvancedSecurityOptions', set 'Enabled' to 'true', set 'InternalUserDatabaseEnabled' to 'true' or 'false', and set 'MasterUserOptions' with an options configuration for your master user. >> } # # Parameterized Rules # rule check(opensearch_service_domain) { %opensearch_service_domain { # Scenario 2 AdvancedSecurityOptions exists AdvancedSecurityOptions is_struct AdvancedSecurityOptions { # Scenarios 3, 4, 5, 6 and 7 Enabled exists Enabled == true InternalUserDatabaseEnabled exists InternalUserDatabaseEnabled in [ true, false ] MasterUserOptions exists MasterUserOptions is_struct } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists }

CT.OPENSEARCH.PR.15 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: IAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: AWS: Ref: AWS::AccountId Action: - sts:AssumeRole OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: Enabled: true EncryptionAtRestOptions: Enabled: true DomainEndpointOptions: EnforceHTTPS: true AdvancedSecurityOptions: Enabled: true InternalUserDatabaseEnabled: false MasterUserOptions: MasterUserARN: Fn::GetAtt: [IAMRole, Arn]

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' NodeToNodeEncryptionOptions: Enabled: true EncryptionAtRestOptions: Enabled: true DomainEndpointOptions: EnforceHTTPS: true AdvancedSecurityOptions: Enabled: false

[CT.OPENSEARCH.PR.16] Require an Amazon OpenSearch Service domain to use TLSv1.2

This control checks whether Amazon OpenSearch Service domains are configured to require HTTPS with a minimum TLS version of TLSv1.2.

  • Control objective: Encrypt data in transit

  • Implementation: AWS CloudFormation guard rule

  • Control behavior: Proactive

  • Resource types: AWS::OpenSearchService::Domain

  • AWS CloudFormation guard rule: CT.OPENSEARCH.PR.16 rule specification

Details and examples

Explanation

HTTPS (TLS) can help prevent potential attackers from using person-in-the-middle, or similar attacks, to eavesdrop on or manipulate network traffic. Only encrypted connections over HTTPS (TLS) should be allowed. Encrypting data in transit can affect performance. You should test your application with this feature to understand the performance profile and the effects of TLS. TLS 1.2 provides several security enhancements over previous versions of TLS.

Remediation for rule failure

Within DomainEndpointOptions, set EnforceHTTPS to true and set TLSSecurityPolicy to Policy-Min-TLS-1-2-2019-07.

The examples that follow show how to implement this remediation.

Amazon OpenSearch Service Domain - Example

An Amazon OpenSearch Service domain configured to require all traffic to the domain arrive over HTTPS with a minimum TLS version of TLSv1.2. The example is shown in JSON and in YAML.

JSON example

{ "OpenSearchServiceDomain": { "Type": "AWS::OpenSearchService::Domain", "Properties": { "EngineVersion": "OpenSearch_1.3", "ClusterConfig": { "InstanceCount": "1", "InstanceType": "t3.small.search" }, "EBSOptions": { "EBSEnabled": true, "Iops": "3000", "VolumeSize": "10", "VolumeType": "gp3" }, "AccessPolicies": { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "*" } ] }, "DomainEndpointOptions": { "EnforceHTTPS": true, "TLSSecurityPolicy": "Policy-Min-TLS-1-2-2019-07" } } } }

YAML example

OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' DomainEndpointOptions: EnforceHTTPS: true TLSSecurityPolicy: Policy-Min-TLS-1-2-2019-07

CT.OPENSEARCH.PR.16 rule specification

# ################################### ## Rule Specification ## ##################################### # # Rule Identifier: # opensearch_https_required_check # # Description: # This control checks whether Amazon OpenSearch Service domains are configured to require HTTPS with a minimum TLS version of TLSv1.2. # # Reports on: # AWS::OpenSearchService::Domain # # Evaluates: # AWS CloudFormation, AWS CloudFormation hook # # Rule Parameters: # None # # Scenarios: # Scenario: 1 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document does not contain any OpenSearch Service domain resources # Then: SKIP # Scenario: 2 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'DomainEndpointOptions' has not been provided # Then: FAIL # Scenario: 3 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'DomainEndpointOptions' has been provided # And: 'EnforceHTTPS' in 'DomainEndpointOptions' has not been provided or # has been provided and set to a value other than bool(true) # And: 'TLSSecurityPolicy' in 'DomainEndpointOptions' has not been provided or # has been provided and set ot a value other than 'Policy-Min-TLS-1-2-2019-07' # Then: FAIL # Scenario: 4 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'DomainEndpointOptions' has been provided # And: 'EnforceHTTPS' in 'DomainEndpointOptions' has been provided and set to bool(true) # And: 'TLSSecurityPolicy' in 'DomainEndpointOptions' has not been provided or # has been provided and set ot a value other than 'Policy-Min-TLS-1-2-2019-07' # Then: FAIL # Scenario: 5 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'DomainEndpointOptions' has been provided # And: 'EnforceHTTPS' in 'DomainEndpointOptions' has not been provided or # has been provided and set to a value other than bool(true) # And: 'TLSSecurityPolicy' in 'DomainEndpointOptions' has been provided and set # to 'Policy-Min-TLS-1-2-2019-07' # Then: FAIL # Scenario: 6 # Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document # And: The input document contains an OpenSearch Service domain resource # And: 'DomainEndpointOptions' has been provided # And: 'EnforceHTTPS' in 'DomainEndpointOptions' has been provided and set to bool(true) # And: 'TLSSecurityPolicy' in 'DomainEndpointOptions' has been provided and set # to 'Policy-Min-TLS-1-2-2019-07' # Then: PASS # # Constants # let OPENSEARCH_SERVICE_DOMAIN_TYPE = "AWS::OpenSearchService::Domain" let ALLOWED_TLS_POLICIES = [ "Policy-Min-TLS-1-2-2019-07" ] let INPUT_DOCUMENT = this # # Assignments # let opensearch_service_domains = Resources.*[ Type == %OPENSEARCH_SERVICE_DOMAIN_TYPE ] # # Primary Rules # rule opensearch_https_required_check when is_cfn_template(%INPUT_DOCUMENT) %opensearch_service_domains not empty { check(%opensearch_service_domains.Properties) << [CT.OPENSEARCH.PR.16]: Require an Amazon OpenSearch Service domain to use TLSv1.2 [FIX]: Within 'DomainEndpointOptions', set 'EnforceHTTPS' to 'true' and set 'TLSSecurityPolicy' to 'Policy-Min-TLS-1-2-2019-07'. >> } rule opensearch_https_required_check when is_cfn_hook(%INPUT_DOCUMENT, %OPENSEARCH_SERVICE_DOMAIN_TYPE) { check(%INPUT_DOCUMENT.%OPENSEARCH_SERVICE_DOMAIN_TYPE.resourceProperties) << [CT.OPENSEARCH.PR.16]: Require an Amazon OpenSearch Service domain to use TLSv1.2 [FIX]: Within 'DomainEndpointOptions', set 'EnforceHTTPS' to 'true' and set 'TLSSecurityPolicy' to 'Policy-Min-TLS-1-2-2019-07'. >> } # # Parameterized Rules # rule check(opensearch_service_domain) { %opensearch_service_domain { # Scenario 2 DomainEndpointOptions exists DomainEndpointOptions is_struct DomainEndpointOptions { # Scenarios 3, 4, 5 and 6 EnforceHTTPS exists EnforceHTTPS == true TLSSecurityPolicy exists TLSSecurityPolicy in %ALLOWED_TLS_POLICIES } } } # # Utility Rules # rule is_cfn_template(doc) { %doc { AWSTemplateFormatVersion exists or Resources exists } } rule is_cfn_hook(doc, RESOURCE_TYPE) { %doc.%RESOURCE_TYPE.resourceProperties exists }

CT.OPENSEARCH.PR.16 example templates

You can view examples of the PASS and FAIL test artifacts for the AWS Control Tower proactive controls.

PASS Example - Use this template to verify a compliant resource creation.

Resources: OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' DomainEndpointOptions: EnforceHTTPS: true TLSSecurityPolicy: Policy-Min-TLS-1-2-2019-07

FAIL Example - Use this template to verify that the control prevents non-compliant resource creation.

Resources: OpenSearchServiceDomain: Type: AWS::OpenSearchService::Domain Properties: EngineVersion: OpenSearch_1.3 ClusterConfig: InstanceCount: '1' InstanceType: t3.small.search EBSOptions: EBSEnabled: true Iops: '3000' VolumeSize: '10' VolumeType: gp3 AccessPolicies: Version: '2012-10-17' Statement: - Effect: Deny Principal: AWS: '*' Action: es:* Resource: '*' DomainEndpointOptions: EnforceHTTPS: true TLSSecurityPolicy: Policy-Min-TLS-1-0-2019-07