Skip to content

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 existing aws_opensearch_domain that 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 check terraform plan for forces replacement before applying to any live domain.

  • Unsupported instance types

    t2 instances don't support encryption at rest. Setting instance_type to any t2.* variant (e.g., t2.small.search) causes domain creation to fail even with encrypt_at_rest { enabled = true } in the config. Use t3 or 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 allow kms:Decrypt, kms:GenerateDataKey, kms:DescribeKey, and kms:CreateGrant for 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_domain resource uses the same encrypt_at_rest block, but migrating to aws_opensearch_domain requires 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.

Tool mappings

Use these identifiers to cross-reference this control across tools, reports, and evidence.

  • Compliance.tf Control: opensearch_domain_encryption_at_rest_enabled

  • AWS Config Managed Rules: ELASTICSEARCH_ENCRYPTED_AT_REST, OPENSEARCH_ENCRYPTED_AT_REST

  • Checkov Check: CKV_AWS_5

  • Powerpipe Controls: aws_compliance.control.es_domain_encryption_at_rest_enabled, aws_compliance.control.opensearch_domain_encryption_at_rest_enabled

  • Prowler Check: opensearch_service_domains_encryption_at_rest_enabled

  • AWS Security Hub Controls: ES.1, Opensearch.1

  • KICS Query: 24e16922-4330-4e9d-be8a-caa90299466a

  • Trivy Check: AWS-0048

Last reviewed: 2026-03-09