Workspace is in a private subnet and its VPC has at least 1 public subnet, 2 private subnets, and a NAT Gateway
Amazon WorkSpaces reside in a VPC that includes 2 private subnets and 1 public subnet, with the WorkSpace launched in a private subnet and the VPC providing NAT Gateway egress.
Risk
Placing WorkSpaces in public subnets or lacking a NAT Gateway exposes desktops to direct Internet reachability, enabling credential attacks and session hijacking, harming confidentiality and integrity. Without controlled egress, updates and directory connectivity can fail, impacting availability and pushing teams to unsafe workarounds.
Run this check with Prowler
Run in Prowler Cloudprowler aws --checks workspaces_vpc_2private_1public_subnets_natRecommendation
Launch WorkSpaces in private subnets and design the VPC with one public and two private subnets. Provide outbound access via a NAT Gateway and restrict inbound exposure per network segmentation and least privilege. Distribute subnets across AZs and avoid assigning public IPs to WorkSpaces for defense in depth.
Remediation
Terraform
# Minimal VPC: 1 public subnet, 2 private subnets, and a NAT Gateway
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.0.0/24"
}
resource "aws_subnet" "private_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24" # PRIVATE SUBNET 1 (required)
}
resource "aws_subnet" "private_b" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24" # PRIVATE SUBNET 2 (required)
}
resource "aws_eip" "nat" {
domain = "vpc"
}
resource "aws_nat_gateway" "nat" {
allocation_id = aws_eip.nat.id # CRITICAL: NAT for private subnets' egress
subnet_id = aws_subnet.public.id # CRITICAL: NAT must be in a public subnet
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route { # CRITICAL: makes this a PUBLIC subnet
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route { # CRITICAL: private subnets use NAT for internet
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat.id
}
}
resource "aws_route_table_association" "private_a" {
subnet_id = aws_subnet.private_a.id # CRITICAL: Workspace subnet must associate to private RT
route_table_id = aws_route_table.private.id
}
resource "aws_route_table_association" "private_b" {
subnet_id = aws_subnet.private_b.id
route_table_id = aws_route_table.private.id # CRITICAL: Ensures at least 2 private subnets
}NativeIaC
# Minimal VPC setup: 1 public subnet, 2 private subnets, and a NAT Gateway
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
IGW:
Type: AWS::EC2::InternetGateway
AttachIGW:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref IGW
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.0/24
PrivateSubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24 # PRIVATE SUBNET 1 (required)
PrivateSubnetB:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24 # PRIVATE SUBNET 2 (required)
NatEip:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NatGw:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatEip.AllocationId # CRITICAL: NAT for private subnets' egress
SubnetId: !Ref PublicSubnet # CRITICAL: NAT must be in a public subnet
PublicRt:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
PublicDefaultRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRt
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref IGW # CRITICAL: makes this a PUBLIC subnet
AssocPublic:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRt
PrivateRt:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
PrivateDefaultRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRt
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGw # CRITICAL: private subnets use NAT for internet
AssocPrivateA:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnetA
RouteTableId: !Ref PrivateRt # CRITICAL: Workspace subnet must associate to private RT
AssocPrivateB:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnetB
RouteTableId: !Ref PrivateRt # CRITICAL: Ensures at least 2 private subnetsOther
Check Code
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.vpc.vpc_client import vpc_client
from prowler.providers.aws.services.workspaces.workspaces_client import (
workspaces_client,
)
class workspaces_vpc_2private_1public_subnets_nat(Check):
def execute(self):
findings = []
for workspace in workspaces_client.workspaces:
report = Check_Report_AWS(metadata=self.metadata(), resource=workspace)
report.status = "PASS"
report.status_extended = f"Workspace {workspace.id} is in a private subnet within a VPC which has 1 public subnet 2 private subnets with a NAT Gateway attached."
vpc_object = None
is_in_private_subnet = False
if workspace.subnet_id:
if vpc_client.vpcs[vpc_client.vpc_subnets[workspace.subnet_id].vpc_id]:
vpc_object = vpc_client.vpcs[
vpc_client.vpc_subnets[workspace.subnet_id].vpc_id
]
if vpc_client.vpc_subnets[workspace.subnet_id]:
if not vpc_client.vpc_subnets[workspace.subnet_id].public:
is_in_private_subnet = True
public_subnets = 0
private_subnets = 0
nat_gateway = False
if vpc_object:
for vpc_subnet in vpc_object.subnets:
if vpc_subnet.public:
public_subnets += 1
if not vpc_subnet.public:
private_subnets += 1
if vpc_subnet.nat_gateway:
nat_gateway = True
# Check NAT Gateway here
if (
public_subnets < 1
or private_subnets < 2
or not nat_gateway
or not is_in_private_subnet
):
report.status = "FAIL"
report.status_extended = f"Workspace {workspace.id} is not in a private subnet or its VPC does not have 1 public subnet and 2 private subnets with a NAT Gateway attached."
findings.append(report)
return findings