Back to blog
AWSBest PracticesCloud SecurityDatabasesIdentity & Access

Elasticsearch Domain Allows Global Access: Why It Happens and How to Fix It

Learn why an Elasticsearch/OpenSearch domain with a wildcard access policy exposes your data, and how to scope, fix, and prevent global access on AWS.

TL;DR

This check flags Elasticsearch or OpenSearch domains whose access policy grants access to any principal ("Principal": "*"), which can expose your search cluster and all its data to the entire internet. Fix it by replacing the wildcard with a scoped policy that limits access to specific IAM roles, accounts, or source IP ranges.

Amazon OpenSearch Service (still widely called Elasticsearch by everyone who used it before the 2021 rename) runs your search and analytics workloads, and those workloads usually hold something sensitive: application logs, customer records, audit trails, e-commerce catalogs. When a domain ships with an access policy that allows * as the principal, you have effectively published that data to whoever finds the endpoint. This check catches exactly that misconfiguration.


What this check detects

The elasticsearch_public check inspects the resource-based access policy attached to each OpenSearch/Elasticsearch domain in your AWS account. It fails when the policy contains a statement that allows access from any principal without further restriction. In practice that means a statement shaped like this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "es:*",
      "Resource": "arn:aws:es:us-east-1:123456789012:domain/my-domain/*"
    }
  ]
}

The combination of "Principal": "*" and "Effect": "Allow" with no Condition block is the dangerous pattern. There is no IAM identity gate and no IP filter, so any request that can reach the domain endpoint is authorized by the policy.

Note: OpenSearch domains can run inside a VPC or with a public endpoint. A wildcard policy on a VPC domain is still risky because anything inside the VPC (or reachable through it) can hit the cluster, but a wildcard policy on a public-endpoint domain is the worst case: the access policy is the only thing standing between the internet and your data.


Why it matters

An open access policy is not theoretical risk. Exposed Elasticsearch and OpenSearch clusters are one of the most consistently breached resource types on the public internet, precisely because they are easy to find and often hold structured, high-value data.

  • Data exfiltration. An attacker who reaches the endpoint can run _search queries against every index. If you store PII, session tokens, or internal logs, all of it is readable.
  • Index deletion and ransom. The "meow" attacks and similar automated campaigns have wiped thousands of open clusters, sometimes replacing the data with a ransom note. With es:* granted, a wildcard principal can call the delete index API.
  • Pivot into your environment. Logs often contain credentials, internal hostnames, and API keys. A search cluster is a goldmine for the reconnaissance phase of a larger breach.
  • Compliance exposure. Public access to data covered by GDPR, HIPAA, or PCI DSS is a reportable event in most frameworks, with the legal and financial fallout that follows.

The uncomfortable part is how easily this happens. When you create a domain through the console with "fine-grained access control" turned off and pick the "allow open access" option to get something working quickly, AWS attaches a wildcard policy for you. That "temporary" setting then survives into production.

Warning: A wildcard access policy is not the same as a public endpoint, and fixing one does not fix the other. You can have a public endpoint with a tightly scoped policy (acceptable) or a VPC endpoint with a wildcard policy (still bad). Treat the access policy as a separate control from network placement.


How to fix it

The fix is to replace the wildcard principal with a scoped policy. Pick the model that matches how the domain is actually used.

Step 1: Find the offending domains

# List all domains in the region
aws opensearch list-domain-names --query 'DomainNames[].DomainName' --output text

# Inspect the access policy for one domain
aws opensearch describe-domain \
  --domain-name my-domain \
  --query 'DomainStatus.AccessPolicies' \
  --output text | jq .

Look for any statement with "Principal": "*" (or {"AWS": "*"}) and an Allow effect that has no restricting condition.

Step 2: Write a scoped policy

For most workloads, the cleanest option is to allow only specific IAM roles, the identities your applications actually use:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "arn:aws:iam::123456789012:role/app-search-role",
          "arn:aws:iam::123456789012:role/log-ingest-role"
        ]
      },
      "Action": "es:ESHttp*",
      "Resource": "arn:aws:es:us-east-1:123456789012:domain/my-domain/*"
    }
  ]
}

If you genuinely need IP-based access from a known range (a corporate egress range or a NAT gateway), use a condition instead of a wildcard with no guard:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "es:ESHttp*",
      "Resource": "arn:aws:es:us-east-1:123456789012:domain/my-domain/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": ["203.0.113.0/24"]
        }
      }
    }
  ]
}

Step 3: Apply the policy

Danger: Updating the access policy changes who can reach the cluster immediately. If you remove a principal that a running service depends on, that service will start getting 403 errors. Confirm every active client (ingestion pipelines, dashboards, application roles) is represented in the new policy before you apply it in production.

aws opensearch update-domain-config \
  --domain-name my-domain \
  --access-policies file://scoped-policy.json

The domain enters a processing state for a few minutes while the change rolls out. Verify it took effect:

aws opensearch describe-domain \
  --domain-name my-domain \
  --query 'DomainStatus.AccessPolicies' \
  --output text | jq .

Console route

  1. Open the OpenSearch Service console and select the domain.
  2. Go to Security configuration and choose Edit.
  3. Under Access policy, switch from the open policy to Configure domain level access policy (or import a JSON policy).
  4. Add statements scoped to your IAM roles or source IPs, then save.

Tip: For new domains, enable fine-grained access control (FGAC). It layers a master user and role-based document/field-level security on top of the resource policy, so even if the domain policy is more permissive than intended, FGAC still gates the actual data access.


How to prevent it from happening again

Manual cleanup is necessary once, but the goal is to make a wildcard policy impossible to ship. Push the control left into your IaC and CI pipeline.

Define domains in Terraform with a scoped policy

resource "aws_opensearch_domain" "logs" {
  domain_name    = "logs"
  engine_version = "OpenSearch_2.11"

  access_policies = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { AWS = aws_iam_role.log_ingest.arn }
      Action    = "es:ESHttp*"
      Resource  = "arn:aws:es:${var.region}:${var.account_id}:domain/logs/*"
    }]
  })
}

Gate it in CI with a policy-as-code check

A Conftest/OPA rule that rejects any plan containing a wildcard principal stops the misconfiguration before it reaches AWS:

# policy/opensearch.rego
package main

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_opensearch_domain"
  policy := json.unmarshal(resource.change.after.access_policies)
  stmt := policy.Statement[_]
  stmt.Effect == "Allow"
  stmt.Principal == "*"
  msg := sprintf("Domain %s allows global access via wildcard principal", [resource.address])
}

Run it against the plan output in your pipeline:

terraform plan -out=tfplan
terraform show -json tfplan > plan.json
conftest test plan.json --policy policy/

Tip: Pair the build-time gate with continuous monitoring. Lensix runs the elasticsearch_public check across your accounts on a schedule, so a domain that gets opened up through a console click or an out-of-band change is flagged even when it never went through your pipeline.


Best practices

  • Default to VPC-only domains. Unless you have a concrete reason for a public endpoint, place the domain in a private subnet and reach it through your VPC. This removes the internet as an attack surface entirely.
  • Never use a bare wildcard principal. If you must allow broad access for a specific reason, always pair it with an aws:SourceIp or aws:SourceVpc condition.
  • Use IAM roles, not IP allowlists, where you can. Identity-based access is more durable than IP ranges, which drift as infrastructure changes.
  • Enable fine-grained access control and HTTPS enforcement. FGAC plus EnforceHTTPS and node-to-node encryption gives you defense in depth.
  • Scope the action. Prefer es:ESHttp* or even specific HTTP verbs over es:*, which would also grant configuration and management permissions.
  • Audit regularly. Access policies tend to accumulate temporary statements. Review them on a cadence and remove anything that no longer maps to an active client.

A search cluster open to the world is one of the fastest paths to a data breach, and it is one of the easiest to prevent. Scope the policy, push the check into CI, and monitor for drift, and this stays a non-event.