Skip to main content

Ensure No EC2 Instances Expose Elasticsearch and Kibana Ports to the Internet

Overview

This check identifies EC2 instances that allow inbound traffic from the internet on Elasticsearch and Kibana ports (TCP 9200, 9300, and 5601). These ports should never be accessible from the public internet.

Severity: Critical

Risk

Elasticsearch and Kibana are commonly used for log aggregation, search, and data visualization. Exposing these services to the internet creates serious security risks:

  • Data exposure: Unauthorized users can query your Elasticsearch indices and view sensitive data
  • Data tampering: Attackers can modify or delete your indices via the Elasticsearch API
  • Cluster compromise: Port 9300 (transport protocol) can allow attackers to join or control your cluster
  • Lateral movement: A compromised Elasticsearch instance can be used as a pivot point to attack other systems in your network

Remediation Steps

Prerequisites

You need permission to modify EC2 security groups in your AWS account.

Required IAM permissions

Your IAM user or role needs these permissions:

  • ec2:DescribeSecurityGroups
  • ec2:RevokeSecurityGroupIngress
  • ec2:AuthorizeSecurityGroupIngress (if adding replacement rules)

AWS Console Method

  1. Open the EC2 Console
  2. Click Security Groups in the left navigation under "Network & Security"
  3. Select the security group attached to the affected EC2 instance
  4. Click the Inbound rules tab
  5. Click Edit inbound rules
  6. Find rules that allow traffic on ports 9200, 9300, or 5601 from 0.0.0.0/0 or ::/0
  7. For each problematic rule, either:
    • Delete the rule by clicking "Delete" if access is not needed
    • Restrict the source to a trusted CIDR (e.g., 10.0.0.0/8 for internal networks)
  8. Click Save rules
AWS CLI (optional)

Remove internet-exposed rules

Replace <SECURITY_GROUP_ID> with your actual security group ID (e.g., sg-0123456789abcdef0):

# Remove IPv4 internet exposure (0.0.0.0/0)
aws ec2 revoke-security-group-ingress \
--region us-east-1 \
--group-id <SECURITY_GROUP_ID> \
--ip-permissions '[
{"IpProtocol":"tcp","FromPort":9200,"ToPort":9200,"IpRanges":[{"CidrIp":"0.0.0.0/0"}]},
{"IpProtocol":"tcp","FromPort":9300,"ToPort":9300,"IpRanges":[{"CidrIp":"0.0.0.0/0"}]},
{"IpProtocol":"tcp","FromPort":5601,"ToPort":5601,"IpRanges":[{"CidrIp":"0.0.0.0/0"}]}
]'

# Remove IPv6 internet exposure (::/0)
aws ec2 revoke-security-group-ingress \
--region us-east-1 \
--group-id <SECURITY_GROUP_ID> \
--ip-permissions '[
{"IpProtocol":"tcp","FromPort":9200,"ToPort":9200,"Ipv6Ranges":[{"CidrIpv6":"::/0"}]},
{"IpProtocol":"tcp","FromPort":9300,"ToPort":9300,"Ipv6Ranges":[{"CidrIpv6":"::/0"}]},
{"IpProtocol":"tcp","FromPort":5601,"ToPort":5601,"Ipv6Ranges":[{"CidrIpv6":"::/0"}]}
]'

Add restricted access rules (if needed)

If internal access is required, add rules with a trusted CIDR:

aws ec2 authorize-security-group-ingress \
--region us-east-1 \
--group-id <SECURITY_GROUP_ID> \
--ip-permissions '[
{"IpProtocol":"tcp","FromPort":9200,"ToPort":9200,"IpRanges":[{"CidrIp":"10.0.0.0/8","Description":"Elasticsearch HTTP from internal only"}]},
{"IpProtocol":"tcp","FromPort":9300,"ToPort":9300,"IpRanges":[{"CidrIp":"10.0.0.0/8","Description":"Elasticsearch transport from internal only"}]},
{"IpProtocol":"tcp","FromPort":5601,"ToPort":5601,"IpRanges":[{"CidrIp":"10.0.0.0/8","Description":"Kibana from internal only"}]}
]'
CloudFormation (optional)

This template creates a security group with Elasticsearch and Kibana ports restricted to a trusted internal network:

AWSTemplateFormatVersion: '2010-09-09'
Description: Secure security group - no Elasticsearch/Kibana internet exposure

Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: VPC where the security group will be created
TrustedCidr:
Type: String
Description: Trusted CIDR for Elasticsearch/Kibana access
Default: '10.0.0.0/8'

Resources:
SecureElasticsearchSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group with restricted Elasticsearch/Kibana access
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 9200
ToPort: 9200
CidrIp: !Ref TrustedCidr
Description: Elasticsearch HTTP from trusted network only
- IpProtocol: tcp
FromPort: 9300
ToPort: 9300
CidrIp: !Ref TrustedCidr
Description: Elasticsearch transport from trusted network only
- IpProtocol: tcp
FromPort: 5601
ToPort: 5601
CidrIp: !Ref TrustedCidr
Description: Kibana from trusted network only
Tags:
- Key: Name
Value: secure-elasticsearch-sg

Outputs:
SecurityGroupId:
Description: ID of the secure security group
Value: !Ref SecureElasticsearchSecurityGroup

Deploy with:

aws cloudformation deploy \
--region us-east-1 \
--template-file template.yaml \
--stack-name secure-elasticsearch-sg \
--parameter-overrides VpcId=<YOUR_VPC_ID> TrustedCidr=10.0.0.0/8
Terraform (optional)
variable "vpc_id" {
description = "VPC ID where the security group will be created"
type = string
}

variable "trusted_cidr" {
description = "Trusted CIDR block for Elasticsearch/Kibana access"
type = string
default = "10.0.0.0/8"
}

resource "aws_security_group" "elasticsearch" {
name = "secure-elasticsearch-sg"
description = "Security group with restricted Elasticsearch/Kibana access"
vpc_id = var.vpc_id

# Elasticsearch HTTP - restricted to trusted network
ingress {
description = "Elasticsearch HTTP from trusted network only"
from_port = 9200
to_port = 9200
protocol = "tcp"
cidr_blocks = [var.trusted_cidr]
}

# Elasticsearch transport - restricted to trusted network
ingress {
description = "Elasticsearch transport from trusted network only"
from_port = 9300
to_port = 9300
protocol = "tcp"
cidr_blocks = [var.trusted_cidr]
}

# Kibana - restricted to trusted network
ingress {
description = "Kibana from trusted network only"
from_port = 5601
to_port = 5601
protocol = "tcp"
cidr_blocks = [var.trusted_cidr]
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}

tags = {
Name = "secure-elasticsearch-sg"
}
}

output "security_group_id" {
description = "ID of the secure security group"
value = aws_security_group.elasticsearch.id
}

Verification

After making changes, verify the fix:

  1. Return to the Security Groups page in the EC2 console
  2. Select the modified security group
  3. Check the Inbound rules tab
  4. Confirm no rules allow ports 9200, 9300, or 5601 from 0.0.0.0/0 or ::/0
CLI verification
# Check for any remaining internet-exposed Elasticsearch/Kibana rules
aws ec2 describe-security-groups \
--region us-east-1 \
--group-ids <SECURITY_GROUP_ID> \
--query 'SecurityGroups[].IpPermissions[?FromPort==`9200` || FromPort==`9300` || FromPort==`5601`]' \
--output table

Review the output to ensure no rules have 0.0.0.0/0 or ::/0 as the source.

Additional Resources

Notes

  • Service disruption warning: Removing security group rules will immediately block traffic. Coordinate with your team before making changes.
  • Consider using AWS OpenSearch Service: If you need managed Elasticsearch, Amazon OpenSearch Service provides built-in security features including VPC support and fine-grained access control.
  • Enable authentication: Even with network restrictions, always enable Elasticsearch security features (authentication, TLS encryption).
  • Use private subnets: Deploy Elasticsearch instances in private subnets without public IP addresses for defense in depth.
  • Access via bastion or VPN: For administrative access, use a bastion host, AWS Systems Manager Session Manager, or VPN instead of internet exposure.