EFS Mount Target Not Publicly Accessible
Overview
This check verifies that Amazon Elastic File System (EFS) mount targets are not deployed in public subnets. A mount target is considered publicly accessible when it resides in a subnet with mapPublicIpOnLaunch=true, which automatically assigns public IP addresses to resources in that subnet.
EFS uses the Network File System (NFS) protocol to provide shared file storage. Mount targets are the network interfaces that EC2 instances use to connect to EFS file systems.
Risk
When EFS mount targets are placed in public subnets, they become exposed to potential internet-based attacks:
- Unauthorized access: NFS protocol scanning from the internet could discover and potentially exploit your file systems
- Data breach: Attackers may attempt to read sensitive files if they gain access
- Data tampering: Malicious actors could modify or delete critical data
- Service disruption: DDoS attacks or resource exhaustion could make your file systems unavailable
Even with restrictive security group rules, having a public IP address weakens your security posture and violates the principle of defense in depth.
Remediation Steps
Prerequisites
You need:
- AWS Console access with permissions to manage EFS and EC2 resources
- At least one private subnet in your VPC (a subnet without a route to an Internet Gateway)
Required IAM permissions
Your IAM user or role needs these permissions:
elasticfilesystem:DescribeMountTargetselasticfilesystem:CreateMountTargetelasticfilesystem:DeleteMountTargetec2:DescribeSubnetsec2:DescribeSecurityGroupsec2:CreateNetworkInterfaceec2:DeleteNetworkInterface
AWS Console Method
-
Identify the affected mount targets
- Go to EFS Console in us-east-1
- Select your file system from the list
- Click the Network tab to see all mount targets
-
Check which subnets are public
- Note the Subnet ID for each mount target
- Go to VPC Console > Subnets
- Find each subnet and check the Auto-assign public IPv4 address column
- Subnets showing "Yes" are public subnets
-
Create a new mount target in a private subnet
- Return to the EFS Console and select your file system
- Click the Network tab, then click Manage
- Click Add mount target
- Select an Availability Zone that matches your public mount target
- Choose a private subnet (one with auto-assign public IP disabled)
- Select an appropriate Security group (restrict NFS access to known sources)
- Click Save
-
Update your applications
- Wait for the new mount target to become available (state changes to "Available")
- Update any EC2 instances or applications to use the new mount target IP or DNS name
- Unmount the file system from instances using the old mount target
-
Delete the public mount target
- Once all applications are using the new mount target, return to Network tab
- Click Manage
- Remove the mount target in the public subnet by clicking the X next to it
- Click Save
AWS CLI (optional)
List mount targets for your file system
aws efs describe-mount-targets \
--file-system-id fs-12345678 \
--region us-east-1
Check if a subnet is public
aws ec2 describe-subnets \
--subnet-ids subnet-abc123 \
--query 'Subnets[0].MapPublicIpOnLaunch' \
--region us-east-1
If the output is true, the subnet is public.
Create a mount target in a private subnet
aws efs create-mount-target \
--file-system-id fs-12345678 \
--subnet-id subnet-private123 \
--security-groups sg-12345678 \
--region us-east-1
Replace:
fs-12345678with your EFS file system IDsubnet-private123with your private subnet IDsg-12345678with your security group ID
Delete the public mount target
After migrating your applications to the new mount target:
aws efs delete-mount-target \
--mount-target-id fsmt-abc123 \
--region us-east-1
Warning: Deleting a mount target immediately disconnects any instances using it. Make sure all applications have been migrated first.
CloudFormation (optional)
This template creates an EFS file system with a mount target in a private subnet:
AWSTemplateFormatVersion: '2010-09-09'
Description: EFS file system with mount targets in private subnets
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: VPC where EFS will be created
PrivateSubnetId:
Type: AWS::EC2::Subnet::Id
Description: Private subnet for the mount target (must have MapPublicIpOnLaunch=false)
AllowedSecurityGroupId:
Type: AWS::EC2::SecurityGroup::Id
Description: Security group allowed to access EFS
Resources:
EFSFileSystem:
Type: AWS::EFS::FileSystem
Properties:
Encrypted: true
PerformanceMode: generalPurpose
ThroughputMode: bursting
FileSystemTags:
- Key: Name
Value: secure-efs
EFSSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for EFS mount targets
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 2049
ToPort: 2049
SourceSecurityGroupId: !Ref AllowedSecurityGroupId
Description: NFS access from allowed sources
Tags:
- Key: Name
Value: efs-mount-target-sg
EFSMountTarget:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId: !Ref EFSFileSystem
SubnetId: !Ref PrivateSubnetId
SecurityGroups:
- !Ref EFSSecurityGroup
Outputs:
FileSystemId:
Description: EFS File System ID
Value: !Ref EFSFileSystem
Export:
Name: !Sub '${AWS::StackName}-FileSystemId'
MountTargetId:
Description: EFS Mount Target ID
Value: !Ref EFSMountTarget
EFSDNSName:
Description: DNS name for mounting the EFS file system
Value: !Sub '${EFSFileSystem}.efs.${AWS::Region}.amazonaws.com'
Deploy with:
aws cloudformation deploy \
--template-file template.yaml \
--stack-name secure-efs \
--parameter-overrides \
VpcId=vpc-12345678 \
PrivateSubnetId=subnet-private123 \
AllowedSecurityGroupId=sg-12345678 \
--region us-east-1
Terraform (optional)
# EFS File System with mount targets in private subnets
resource "aws_efs_file_system" "secure" {
creation_token = "secure-efs"
encrypted = true
tags = {
Name = "secure-efs"
}
}
# Mount target in a private subnet
resource "aws_efs_mount_target" "private" {
file_system_id = aws_efs_file_system.secure.id
subnet_id = var.private_subnet_id
security_groups = [aws_security_group.efs.id]
}
# Security group for EFS mount target
resource "aws_security_group" "efs" {
name = "efs-mount-target-sg"
description = "Security group for EFS mount targets"
vpc_id = var.vpc_id
ingress {
description = "NFS from allowed sources"
from_port = 2049
to_port = 2049
protocol = "tcp"
security_groups = [var.allowed_security_group_id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "efs-mount-target-sg"
}
}
variable "vpc_id" {
description = "VPC ID where EFS mount targets will be created"
type = string
}
variable "private_subnet_id" {
description = "Private subnet ID for the mount target (must have mapPublicIpOnLaunch=false)"
type = string
}
variable "allowed_security_group_id" {
description = "Security group ID allowed to access EFS"
type = string
}
output "efs_id" {
description = "EFS file system ID"
value = aws_efs_file_system.secure.id
}
output "mount_target_id" {
description = "EFS mount target ID"
value = aws_efs_mount_target.private.id
}
Deploy with:
terraform init
terraform plan -var="vpc_id=vpc-12345678" \
-var="private_subnet_id=subnet-private123" \
-var="allowed_security_group_id=sg-12345678"
terraform apply
Verification
After remediation, verify that all mount targets are in private subnets:
-
In the AWS Console:
- Go to EFS Console > File Systems > [Your File System] > Network
- Note all subnet IDs
- Go to VPC Console > Subnets
- Confirm each subnet has Auto-assign public IPv4 address set to "No"
-
Test connectivity:
- From an EC2 instance in the same VPC, mount the file system using the mount target DNS name
- Verify you can read and write files as expected
CLI verification commands
List all mount targets and their subnets:
aws efs describe-mount-targets \
--file-system-id fs-12345678 \
--query 'MountTargets[*].[MountTargetId,SubnetId,LifeCycleState]' \
--output table \
--region us-east-1
Check if any mount target subnets are public:
# Get subnet IDs for all mount targets
SUBNET_IDS=$(aws efs describe-mount-targets \
--file-system-id fs-12345678 \
--query 'MountTargets[*].SubnetId' \
--output text \
--region us-east-1)
# Check each subnet
for subnet in $SUBNET_IDS; do
PUBLIC=$(aws ec2 describe-subnets \
--subnet-ids "$subnet" \
--query 'Subnets[0].MapPublicIpOnLaunch' \
--output text \
--region us-east-1)
echo "Subnet $subnet: MapPublicIpOnLaunch=$PUBLIC"
done
All subnets should show MapPublicIpOnLaunch=False.
Additional Resources
- AWS Documentation: Amazon EFS
- AWS Documentation: Creating Mount Targets
- AWS Documentation: EFS Security
- AWS Documentation: VPC Subnets
Notes
- Service interruption: Deleting a mount target immediately disconnects any instances using it. Plan a maintenance window and migrate applications before removing public mount targets.
- One mount target per AZ: You can only have one mount target per Availability Zone. To move a mount target to a private subnet in the same AZ, you must delete the existing one first.
- DNS resolution: If using the EFS DNS name for mounting, ensure your VPC has DNS resolution enabled and instances can resolve the EFS endpoint.
- Security groups: Even in private subnets, apply least-privilege security group rules. Only allow NFS traffic (port 2049) from specific security groups that need access.
- Network ACLs: For defense in depth, consider adding Network ACL rules to restrict traffic to your EFS mount targets.
- VPN/Direct Connect access: If you need to access EFS from on-premises, use AWS VPN or Direct Connect to your VPC rather than exposing mount targets publicly.