OpenSearch domains should have encryption at rest enabled
OpenSearch domains commonly store search indices, log aggregations, and analytics datasets containing PII, financial records, or security telemetry. Without encryption at rest, this data sits unprotected on underlying EBS volumes and in automated snapshots, exposed to physical media compromise or unauthorized storage-layer access.
Once an OpenSearch domain is created without encryption at rest, you cannot enable it retroactively. The domain must be deleted and recreated, which means reindexing all data. Enforcing this at provisioning time is far cheaper than fixing it later, particularly for domains with terabytes of indexed data and complex access policies.
Retrofit consideration
Encryption at rest cannot be enabled on an existing OpenSearch domain. You must delete and recreate it, which means reindexing all data. Plan for downtime, snapshot-based migration, and updated domain endpoint references across every consumer.
Implementation
Choose the approach that matches how you manage Terraform.
Use the compliance.tf module to enforce this control by default. See get started with compliance.tf.
module "opensearch" {
source = "pcidss.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
module "opensearch" {
source = "hipaa.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
module "opensearch" {
source = "nist800171.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
module "opensearch" {
source = "nydfs23.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
module "opensearch" {
source = "eugmpannex11.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
module "opensearch" {
source = "cfrpart11.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
module "opensearch" {
source = "hipaasecurity2003.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
module "opensearch" {
source = "nistcsfv11.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
module "opensearch" {
source = "pcidssv321.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
If you use terraform-aws-modules/opensearch/aws, set the right module inputs for this control. You can later migrate to the compliance.tf module with minimal changes because it is compatible by design.
module "opensearch" {
source = "terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
encrypt_at_rest = {
enabled = true
}
}
Use AWS provider resources directly. See docs for the resources involved: aws_opensearch_domain.
resource "aws_opensearch_domain" "this" {
advanced_security_options {
enabled = true
internal_user_database_enabled = true
master_user_options {
master_user_name = "admin"
master_user_password = "ChangeMe123!"
}
}
auto_tune_options {
desired_state = "DISABLED"
}
cluster_config {
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
cognito_options {
enabled = true
identity_pool_id = "us-east-1:12345678-1234-1234-1234-123456789012"
role_arn = "arn:aws:iam::123456789012:role/example-role"
user_pool_id = "us-east-1_AbCdEfGhI"
}
domain_endpoint_options {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "pofix-abc123"
ebs_options {
ebs_enabled = true
volume_size = 10
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
log_publishing_options {
cloudwatch_log_group_arn = local.es_log_group_arn
log_type = "AUDIT_LOGS"
}
log_publishing_options {
cloudwatch_log_group_arn = local.es_log_group_arn
log_type = "ES_APPLICATION_LOGS"
}
log_publishing_options {
cloudwatch_log_group_arn = local.es_log_group_arn
log_type = "SEARCH_SLOW_LOGS"
}
log_publishing_options {
cloudwatch_log_group_arn = local.es_log_group_arn
log_type = "INDEX_SLOW_LOGS"
}
node_to_node_encryption {
enabled = true
}
vpc_options {
security_group_ids = ["sg-12345678"]
subnet_ids = ["subnet-12345678"]
}
encrypt_at_rest {
enabled = true
}
}
What this control checks
In the aws_opensearch_domain resource, an encrypt_at_rest block must be present with enabled set to true. It fails if the block is omitted or if enabled is false. The optional kms_key_id argument takes a customer-managed KMS key ARN; without it, the domain uses the AWS-managed aws/es key. The control does not require a specific key type, only that encryption is active. Teams migrating from the deprecated aws_elasticsearch_domain resource will find the same block structure applies, but switching to aws_opensearch_domain is recommended.
Common pitfalls
Immutable setting requires domain recreation
Add
encrypt_at_rest { enabled = true }to an existingaws_opensearch_domainthat was created without it and Terraform will plan a destroy-and-recreate, meaning full data loss unless you snapshot first. This is an immutable property with no in-place upgrade path. Always checkterraform planforforces replacementbefore applying to any live domain.Unsupported instance types
t2instances don't support encryption at rest. Settinginstance_typeto anyt2.*variant (e.g.,t2.small.search) causes domain creation to fail even withencrypt_at_rest { enabled = true }in the config. Uset3or a newer instance family.KMS key policy must grant OpenSearch access
When specifying a customer-managed key via
kms_key_id, the key policy must allowkms:Decrypt,kms:GenerateDataKey,kms:DescribeKey, andkms:CreateGrantfor OpenSearch usage. Depending on your policy design, grant these to the OpenSearch service principal (es.amazonaws.com), the service-linked role (AWSServiceRoleForAmazonOpenSearchService), or both. A missing permission fails domain creation with a generic KMS error that doesn't identify which action is blocked.Legacy aws_elasticsearch_domain resource
The deprecated
aws_elasticsearch_domainresource uses the sameencrypt_at_restblock, but migrating toaws_opensearch_domainrequires a state move (terraform state mv) to avoid triggering an unintended destroy-create cycle. Plan the resource migration and encryption enforcement together rather than tackling them separately.
Audit evidence
An auditor expects Config rule results for the managed rule OPENSEARCH_ENCRYPTED_AT_REST showing compliant evaluations across all OpenSearch domains. Also useful: the OpenSearch domain configuration page in the AWS Console showing 'Encryption at rest: Enabled' with the associated KMS key ARN, or the output of aws opensearch describe-domain --domain-name <name> confirming EncryptionAtRestOptions.Enabled is true.
CloudTrail logs for CreateDomain and UpdateDomainConfig API calls show that encryption was specified at creation time and has not been altered. Historical pass results from Security Hub or a CSPM tool add a continuous-compliance signal to the evidence package.
Framework-specific interpretation
PCI DSS v4.0: Requirement 3.4 says PAN must be unreadable wherever it's stored; Requirement 3.5 covers cryptographic key protection. For OpenSearch domains that may index cardholder data, encryption at rest helps address the storage-layer piece of both requirements, though key management practices under 3.5 need separate attention.
HIPAA Omnibus Rule 2013: 45 CFR 164.312(a)(2)(iv) lists encryption as an addressable specification under the technical safeguard standard. For OpenSearch domains indexing or analyzing health data, enabling encryption at rest satisfies this specification. KMS-backed keys also provide the access controls and audit trail HIPAA auditors expect to see.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
opensearch_domain_encryption_at_rest_enabledAWS Config Managed Rules:
ELASTICSEARCH_ENCRYPTED_AT_REST,OPENSEARCH_ENCRYPTED_AT_RESTCheckov Check:
CKV_AWS_5Powerpipe Controls:
aws_compliance.control.es_domain_encryption_at_rest_enabled,aws_compliance.control.opensearch_domain_encryption_at_rest_enabledProwler Check:
opensearch_service_domains_encryption_at_rest_enabledAWS Security Hub Controls:
ES.1,Opensearch.1KICS Query:
24e16922-4330-4e9d-be8a-caa90299466aTrivy Check:
AWS-0048
Last reviewed: 2026-03-09