Skip to main content
emnode / learn
Compliance Medium severity

AWS Security Hub · ES

ES.3: ES should encrypt node-to-node traffic

Written and reviewed by Emnode · Last reviewed

What does AWS Security Hub ES.3 check?

ES.3 checks whether NodeToNodeEncryptionOptions.Enabled is true on a legacy Elasticsearch domain and reports FAILED when it is off. Domain nodes constantly shuffle data between themselves — replicating shards, rebalancing, and fanning out queries — and this option encrypts that intra-cluster traffic.

Why does ES.3 matter?

With the option off, data moves between nodes in plaintext. The traffic stays inside a service-managed VPC, so exploiting it needs an attacker already present in that network, but compliance regimes don't accept "it's inside a VPC" — an unencrypted channel is a control failure under PCI DSS, HIPAA, FedRAMP, and SOC 2 regardless of the blast radius.

How do I fix ES.3?

  1. Enable node-to-node encryption on the domain where the engine version supports an in-place change.
  2. On older versions, create a new domain with the option on and migrate indices via snapshot restore.
  3. Pair it with HTTPS enforcement and encryption at rest for full coverage.
  4. Default the option on in new-domain templates.

Remediation script · bash

# 1. Bulk-enable free SSE-SQS on every unencrypted queue in the region.
for q in $(aws sqs list-queues --query 'QueueUrls[]' --output text); do
  state=$(aws sqs get-queue-attributes --queue-url $q \
    --attribute-names KmsMasterKeyId SqsManagedSseEnabled --query 'Attributes' --output text)
  [ -z "$state" ] && aws sqs set-queue-attributes --queue-url $q \
    --attributes '{"SqsManagedSseEnabled":"true"}' && echo "encrypted $q"
done

# 2. High-throughput stream: SSE-KMS with a 5-minute data-key reuse window to keep KMS cost flat.
aws kinesis start-stream-encryption --stream-name payment-events \
  --encryption-type KMS \
  --key-id arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab

# 3. Find unencrypted recovery points (Backup.1 reads IsEncrypted per recovery point, not per vault).
aws backup list-recovery-points-by-backup-vault --backup-vault-name prod-backups \
  --query 'RecoveryPoints[?IsEncrypted==`false`].[RecoveryPointArn,ResourceType]' --output table

# 4. Confirm an at-rest Config rule is evaluating so regressions are caught automatically.
aws configservice describe-compliance-by-config-rule --config-rule-names sqs-queue-encrypted \
  --query 'ComplianceByConfigRules[].Compliance.ComplianceType'

Full walkthrough (console steps, edge cases and verification) in the lesson Encrypt other services at rest (queues, streams, logs, ML).

Is ES.3 a false positive?

On older Elasticsearch versions node-to-node encryption can't be toggled on an existing domain — it has to be set at creation, so the fix may be a migration rather than an edit.

Part of the learning path Encrypt everything
  • ES.1 ES domains should encrypt at rest
  • ES.2 A legacy Elasticsearch domain is publicly accessible
  • ES.4 ES error logging to CW should be enabled
  • ES.5 ES domains should have audit logging
  • ES.6 ES domains should have >= 3 data nodes
  • ES.7 ES domains should have >= 3 dedicated master nodes
  • ES.8 ES should use latest TLS policy