Skip to content

OpenSearch domains should have Cognito authentication enabled for Kibana

Without Cognito authentication, Kibana dashboards are protected only by network-level controls such as VPC placement or IP-based access policies. Those controls carry no user identity, meaning anyone with network access can query indices, view sensitive data, or modify visualizations with no individual accountability.

Cognito adds identity federation, MFA support, and per-user audit trails. You can also integrate external identity providers (SAML, OIDC) through a Cognito User Pool, which gives you centralized control over who can reach the dashboards and a record of who did.

Retrofit consideration

Enabling Cognito on an existing domain requires a pre-configured Cognito User Pool, Identity Pool, and an IAM role with the AmazonOpenSearchServiceCognitoAccess policy attached. Users currently relying on open or IP-based access will lose access until they are provisioned in the User Pool, so coordinate the cutover with affected teams before applying.

Implementation

Choose the approach that matches how you manage Terraform.

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"

  cognito_options = {
    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
  }

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

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

What this control checks

The aws_opensearch_domain resource must include a cognito_options block with enabled = true. The block requires three additional arguments: user_pool_id, identity_pool_id, and role_arn, where role_arn points to an IAM role that grants OpenSearch permission to configure Cognito. A domain missing the cognito_options block, or with enabled = false, fails this control. The referenced IAM role must have the AmazonOpenSearchServiceCognitoAccess managed policy attached. Because the Cognito User Pool and Identity Pool must exist before the domain is created or updated, you typically manage them as separate aws_cognito_user_pool and aws_cognito_identity_pool resources.

Common pitfalls

  • IAM role trust policy misconfiguration

    The IAM role passed to role_arn in cognito_options must have a trust policy allowing es.amazonaws.com to assume it. If the trust relationship references only cognito-identity.amazonaws.com, OpenSearch cannot configure the Cognito integration and the domain update will fail. This is the most common reason the cognito_options block applies cleanly in Terraform but the domain never actually enables the feature.

  • Identity Pool authenticated role mismatch

    The Cognito Identity Pool must have its authenticated role configured to allow access to the OpenSearch domain. If aws_cognito_identity_pool_roles_attachment maps the authenticated role to an IAM role without OpenSearch permissions, users will authenticate successfully through Cognito and then hit 403 errors on the dashboard. Authentication passing and authorization failing are easy to conflate during testing.

  • Cognito options removed on unrelated domain updates

    Accidentally dropping the cognito_options block during a refactor disables Cognito authentication on the next terraform apply. Terraform will show the change in the plan, but it is easy to miss when the diff is buried in a larger update. Add lifecycle preconditions or a policy-as-code check to catch this before it reaches production.

  • Region availability of Cognito

    Cognito User Pools are not available in every AWS region or partition. If you deploy OpenSearch domains in a region or partition where Cognito support is absent, this control cannot be satisfied. Verify current regional availability for both services before committing to an architecture that depends on this combination.

Audit evidence

AWS Config rule results showing each domain as compliant, or aws opensearch describe-domain --domain-name <name> output with CognitoOptions.Enabled: true and populated UserPoolId, IdentityPoolId, and RoleArn fields. CloudTrail UpdateDomainConfig or CreateDomain events confirm when Cognito was enabled and by whom.

The Cognito User Pool configuration showing password policies and MFA settings works as supporting evidence for frameworks that require authentication strength documentation.

Framework-specific interpretation

Tool mappings

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

  • Compliance.tf Control: opensearch_domain_cognito_authentication_enabled_for_kibana

  • Powerpipe Controls: aws_compliance.control.es_domain_cognito_authentication_enabled, aws_compliance.control.opensearch_domain_cognito_authentication_enabled_for_kibana

  • Prowler Check: opensearch_service_domains_use_cognito_authentication_for_kibana

Last reviewed: 2026-03-09