Back to Checks
aws

ElastiCache cluster is not using public subnets

elasticache_cluster_uses_public_subnet
Service: elasticacheSeverity: medium
OfficialBuilt-inVerified
Categories:internet-exposed
Resource Type:Other

ElastiCache resources (Redis nodes and Memcached clusters) are assessed for placement in public subnets.

The finding identifies cache subnet groups that include subnets configured with Internet routing instead of private-only subnets.

Risk

Hosting caches in public subnets can permit direct or misconfigured Internet access, impacting CIA:

  • Confidentiality: unauthorized reads and key dumps
  • Integrity: cache poisoning or key tampering
  • Availability: scanning and DDoS

Attackers may pivot from the cache to lateral movement within the VPC.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks elasticache_cluster_uses_public_subnet

Recommendation

Place caches in private subnets only and ensure route tables lack Internet egress. Apply least privilege with tight security groups limited to required ports and trusted sources.

For external access, use VPC peering, VPN, or PrivateLink. Enable encryption in transit and Redis AUTH for layered controls.

Remediation

CLI

aws elasticache modify-cache-cluster --cache-cluster-id <example_resource_id> --cache-subnet-group-name <example_resource_name> --apply-immediately

Terraform

# Terraform: ensure the cluster uses a subnet group with only private subnets
resource "aws_elasticache_subnet_group" "private" {
  name       = "<example_resource_name>"
  subnet_ids = ["<example_resource_id>", "<example_resource_id>"] # private subnets only
}

resource "aws_elasticache_cluster" "cache" {
  cluster_id      = "<example_resource_id>"
  engine          = "redis"
  node_type       = "cache.t3.micro"
  num_cache_nodes = 1
  subnet_group_name = aws_elasticache_subnet_group.private.name  # CRITICAL: restricts cluster to private subnets
}

NativeIaC

# CloudFormation: move ElastiCache into private subnets via a private subnet group
Resources:
  PrivateCacheSubnetGroup:
    Type: AWS::ElastiCache::SubnetGroup
    Properties:
      Description: Private subnets only
      SubnetIds:
        - <example_resource_id>  # private subnet
        - <example_resource_id>  # private subnet

  CacheCluster:
    Type: AWS::ElastiCache::CacheCluster
    Properties:
      CacheClusterId: <example_resource_id>
      Engine: redis
      CacheNodeType: cache.t3.micro
      NumCacheNodes: 1
      CacheSubnetGroupName: !Ref PrivateCacheSubnetGroup  # CRITICAL: forces the cluster to use only private subnets

Other

1In the AWS Console, go to ElastiCache > Subnet groups
2Click Create cache subnet group and select only private subnets (no route to an Internet Gateway)
3Go to ElastiCache > Redis or Memcached, select your cluster
4Click Modify, set Subnet group to the private subnet group
5Check Apply immediately and click Modify to save

Check Code

from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.elasticache.elasticache_client import (
    elasticache_client,
)
from prowler.providers.aws.services.vpc.vpc_client import vpc_client


class elasticache_cluster_uses_public_subnet(Check):
    def execute(self):
        findings = []
        for cluster in elasticache_client.clusters.values():
            report = Check_Report_AWS(metadata=self.metadata(), resource=cluster)

            report.status = "PASS"
            if cluster.engine == "redis":
                report.status_extended = (
                    f"Elasticache Redis Node {cluster.id} is not using public subnets."
                )
            else:
                report.status_extended = f"Elasticache Memcached Cluster {cluster.id} is not using public subnets."

            public_subnets = []
            for subnet in cluster.subnets:
                if (
                    subnet in vpc_client.vpc_subnets
                    and vpc_client.vpc_subnets[subnet].public
                ):
                    public_subnets.append(vpc_client.vpc_subnets[subnet].id)

            if len(public_subnets) > 0:
                report.status = "FAIL"
                if cluster.engine == "redis":
                    report.status_extended = f"Elasticache Redis Node {cluster.id} is using {', '.join(public_subnets)} public subnets."
                else:
                    report.status_extended = f"Elasticache Memcached Cluster {cluster.id} is using {', '.join(public_subnets)} public subnets."

            findings.append(report)

        return findings