Skip to content

OpenSearch domains should have fine-grained access control enabled

Fine-grained access control in OpenSearch lets you define index-level, document-level, and field-level permissions using roles mapped to IAM principals or an internal user database. Without it, any authenticated principal with domain-level access can read or modify all indices. This creates a flat access model where a compromised application credential exposes every index in the domain.

Enabling FGAC also activates the OpenSearch Dashboards security plugin, giving you audit logging of authentication events and a UI for managing roles. For domains holding PII, financial records, or log data from multiple tenants, the absence of FGAC makes least-privilege enforcement impossible at the data layer.

Retrofit consideration

Enabling fine-grained access control on an existing domain requires that encryption at rest, node-to-node encryption, and HTTPS enforcement are already enabled. If any are missing, you must enable them first, which can trigger a blue/green deployment. Existing application clients must then be updated to authenticate with either IAM credentials or the internal user database master user, which will break access patterns that relied on open or IP-based policies.

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  = "acscessentialeight.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"

  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"

  advanced_security_options = {
    enabled = true
  }
}

Use AWS provider resources directly. See docs for the resources involved: aws_opensearch_domain.

resource "aws_opensearch_domain" "this" {
  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"
  }

  encrypt_at_rest {
    enabled = true
  }

  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"]
  }

  advanced_security_options {
    enabled                        = true
    internal_user_database_enabled = true

    master_user_options {
      master_user_name     = "admin"
      master_user_password = "ChangeMe123!"
    }
  }
}

What this control checks

On the aws_opensearch_domain resource, advanced_security_options must be present with enabled = true. Inside that block, master_user_options must specify either master_user_arn for an IAM-based master user, or both master_user_name and master_user_password for the internal user database. When using the internal user database, also set internal_user_database_enabled = true. Fine-grained access control has prerequisites: encrypt_at_rest.enabled, node_to_node_encryption.enabled, and domain_endpoint_options.enforce_https must all be true. A domain missing any of these causes apply to fail with an API error. The control fails when advanced_security_options is omitted or enabled is false.

Common pitfalls

  • Prerequisites silently block enablement

    Fine-grained access control requires encrypt_at_rest.enabled, node_to_node_encryption.enabled, and domain_endpoint_options.enforce_https all set to true. If any is missing, Terraform surfaces an API error, but the message often doesn't clearly identify which prerequisite failed. Configure all three before or alongside advanced_security_options to avoid iterating through apply failures.

  • Master user password in state file

    Setting master_user_name and master_user_password directly in master_user_options puts the password in plaintext in Terraform state. Use master_user_arn pointing to an IAM principal instead, or generate a password with random_password, store it via aws_secretsmanager_secret_version, and mark it sensitive to at least limit exposure in logs and plan output.

  • Overly broad domain access policy

    Enabling FGAC does not automatically restrict the domain access policy. A resource-based policy with "Principal": "*" and no conditions still lets any principal reach the endpoint; FGAC handles authorization from there, but you've left the front door open. Tighten the access policy to specific IAM principals or VPC-constrained sources.

  • Blue/green deployment on existing domains

    Enabling advanced_security_options on a running domain triggers a blue/green deployment. Depending on cluster size, that can take anywhere from 15 minutes to several hours. Plan a maintenance window, notify dependent teams, and test client authentication patterns after the cutover before declaring it done.

Audit evidence

AWS Config rule evaluation results showing each OpenSearch domain as compliant satisfy the primary evidence requirement, or equivalent CSPM output. The DescribeDomain API response should show AdvancedSecurityOptions.Enabled: true and AdvancedSecurityOptions.InternalUserDatabaseEnabled reflecting the chosen authentication strategy. Supporting evidence includes the domain's access policy document and OpenSearch Dashboards screenshots showing configured roles with index-level or document-level restrictions.

CloudTrail UpdateDomainConfig events confirm when FGAC was enabled and by whom. If the internal user database is in use, evidence that the master user password is stored in Secrets Manager with automatic rotation is also expected.

Framework-specific interpretation

PCI DSS v4.0: PCI DSS v4.0 Requirement 7 calls for restricting access to cardholder data by business need to know. FGAC addresses this at the data layer: index-level and document-level permissions mean individual roles see only the data they need. Requirement 7.2.1 specifically expects access control models tied to business need, and FGAC's role mapping against IAM principals or internal users is how you meet it.

Tool mappings

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

  • Compliance.tf Control: opensearch_domain_fine_grained_access_enabled

  • AWS Config Managed Rule: OPENSEARCH_ACCESS_CONTROL_ENABLED

  • Checkov Check: CKV2_AWS_52

  • Powerpipe Control: aws_compliance.control.opensearch_domain_fine_grained_access_enabled

  • Prowler Check: opensearch_service_domains_access_control_enabled

  • AWS Security Hub Control: Opensearch.7

Last reviewed: 2026-03-09