Back to Checks
aws

Workspace is in a private subnet and its VPC has at least 1 public subnet, 2 private subnets, and a NAT Gateway

workspaces_vpc_2private_1public_subnets_nat
Service: workspacesSeverity: high
OfficialBuilt-inVerified
Categories:trust-boundariesinternet-exposed
Resource Type:Other

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 Cloud
Run
prowler aws --checks workspaces_vpc_2private_1public_subnets_nat

Recommendation

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 subnets

Other

1In the AWS Console, go to VPC > Internet gateways and ensure an Internet Gateway is attached to the VPC.
2Go to VPC > Subnets and ensure the VPC has at least one subnet to use as PUBLIC and two subnets to use as PRIVATE (preferably in different AZs). Create missing subnets if needed.
3Go to VPC > NAT Gateways and Create NAT gateway in the PUBLIC subnet, allocating an Elastic IP.
4Go to VPC > Route tables:
5- For the PUBLIC subnet's route table: add or ensure a 0.0.0.0/0 route targets the Internet Gateway (this marks it public).
6- For the PRIVATE subnets' route table(s): add or ensure a 0.0.0.0/0 route targets the NAT Gateway and remove any 0.0.0.0/0 route to an Internet Gateway. This makes them private with egress via NAT.
7Ensure the WorkSpace's subnet is one of the PRIVATE subnets by associating its subnet to the private route table (Routes: 0.0.0.0/0 -> NAT Gateway).
8Verify: the VPC now has >=1 public subnet, >=2 private subnets, at least one NAT Gateway, and the WorkSpace's subnet is private.

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