Back to Checks
aws

EMR cluster is not publicly accessible

emr_cluster_publicly_accesible
Service: emrSeverity: medium
OfficialBuilt-inVerified
Categories:internet-exposed
Resource Type:Other

Amazon EMR clusters are assessed for public network exposure by examining master and core/task node security groups for inbound rules that allow any source (0.0.0.0/0 or ::/0).

Only active clusters are considered, and findings identify exposure via the specific security groups attached to the cluster nodes.

Risk

Open Internet ingress to EMR nodes enables direct access to services and UIs, facilitating brute force, RCE, and data theft. Adversaries can pivot inside the VPC, alter jobs and outputs (integrity), exfiltrate datasets (confidentiality), or abuse compute for mining, degrading availability.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks emr_cluster_publicly_accesible

Recommendation

Apply least privilege and defense in depth:

  • Place clusters in private subnets; avoid public IPs
  • Deny 0.0.0.0/0 and ::/0 in node security groups; allow trusted CIDRs only
  • Keep EMR Block Public Access enabled with minimal exceptions
  • Use bastion/SSM, private connectivity, and logging for hardened access

Remediation

Terraform

# Restrict EMR SG ingress to avoid 0.0.0.0/0 or ::/0
resource "aws_security_group_rule" "<example_resource_name>" {
  type              = "ingress"
  from_port         = 22
  to_port           = 22
  protocol          = "tcp"
  security_group_id = "<example_resource_id>"  # EMR master/core SG
  cidr_blocks       = ["10.0.0.0/8"]           # CRITICAL: restrict source; not 0.0.0.0/0 or ::/0
}

NativeIaC

# CloudFormation: Security Group without public ingress for EMR nodes
Resources:
  <example_resource_name>:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: SG for EMR without public access
      VpcId: <example_resource_id>
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 10.0.0.0/8  # CRITICAL: restrict source; do not use 0.0.0.0/0 or ::/0 to avoid public access

Other

1In AWS Console, go to EMR > Clusters and open the affected cluster
2In the cluster details, note the Security Groups for Master and Core/Task under Network and security
3Open the EC2 Console > Security Groups and select each noted group
4Edit Inbound rules and remove any rule with Source 0.0.0.0/0 or ::/0
5If access is required, re-add only from specific CIDR(s) you control, then Save

Check Code

from copy import deepcopy

from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.ec2.ec2_client import ec2_client
from prowler.providers.aws.services.ec2.lib.security_groups import check_security_group
from prowler.providers.aws.services.emr.emr_client import emr_client
from prowler.providers.aws.services.emr.emr_service import ClusterStatus


class emr_cluster_publicly_accesible(Check):
    def execute(self):
        findings = []
        for cluster in emr_client.clusters.values():
            if cluster.status not in (
                ClusterStatus.TERMINATED,
                ClusterStatus.TERMINATED_WITH_ERRORS,
            ):
                report = Check_Report_AWS(metadata=self.metadata(), resource=cluster)
                report.status = "PASS"
                report.status_extended = (
                    f"EMR Cluster {cluster.id} is not publicly accessible."
                )
                # If EMR cluster is Public, it is required to check
                # their Security Groups for the Master,
                # the Slaves and the additional ones
                if cluster.public:
                    # Check Public Master Security Groups
                    master_node_sg_groups = deepcopy(
                        cluster.master.additional_security_groups_id
                    )
                    if master_node_sg_groups:
                        master_node_sg_groups.append(cluster.master.security_group_id)
                    else:
                        master_node_sg_groups = [cluster.master.security_group_id]

                    master_public_security_groups = []
                    for master_sg in master_node_sg_groups:
                        master_sg_public = False
                        for sg in ec2_client.security_groups.values():
                            if sg.id == master_sg:
                                for ingress_rule in sg.ingress_rules:
                                    if check_security_group(ingress_rule, "-1"):
                                        master_sg_public = True
                                        break
                            if master_sg_public:
                                master_public_security_groups.append(sg.id)
                                break

                    # Check Public Slave Security Groups
                    slave_node_sg_groups = deepcopy(
                        cluster.slave.additional_security_groups_id
                    )
                    if slave_node_sg_groups:
                        slave_node_sg_groups.append(cluster.slave.security_group_id)
                    else:
                        slave_node_sg_groups = [cluster.slave.security_group_id]

                    slave_public_security_groups = []
                    for slave_sg in slave_node_sg_groups:
                        slave_sg_public = False
                        for sg in ec2_client.security_groups.values():
                            if sg.id == slave_sg:
                                for ingress_rule in sg.ingress_rules:
                                    if check_security_group(ingress_rule, "-1"):
                                        slave_sg_public = True
                                        break
                            if slave_sg_public:
                                slave_public_security_groups.append(sg.id)
                                break

                    if master_public_security_groups or slave_public_security_groups:
                        report.status = "FAIL"
                        report.status_extended = f"EMR Cluster {cluster.id} is publicly accessible through the following Security Groups:"
                        report.status_extended += (
                            f" Master Node {master_public_security_groups}"
                            if master_public_security_groups
                            else ""
                        )
                        report.status_extended += (
                            f" Slaves Nodes {slave_public_security_groups}"
                            if slave_public_security_groups
                            else ""
                        )

                findings.append(report)

        return findings