Check ID: workspaces_vpc_2private_1public_subnets_nat
Provider: AWS
Service: workspaces
Severity: HIGH
Categories: trust-boundaries, internet-exposed
Resource type: Other

What this check verifies

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.

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

# 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
}

References

Where this check fires

This check runs on every scheduled scan against your AWS account. Findings appear at Cloud Security → Findings filterable by Check ID = workspaces_vpc_2private_1public_subnets_nat. To re-evaluate after a fix, hit Run now on the account’s schedule under Cloud Security → Schedules — the next scan re-checks this control and marks the finding fixed or keeps it persistent.
← Back to AWS checks