This post explains how to design and deploy an AWS VPC architecture that satisfies FAR 52.204-21 and CMMC 2.0 Level 1 control SC.L1-B.1.XI — isolating public subnetworks from internal/private subnetworks — with a focused, practical CloudFormation example you can adapt for a small business environment.
What SC.L1-B.1.XI Requires and Key Objectives
At Level 1, SC.L1-B.1.XI requires organizations to isolate public subnetworks (subnets which allow inbound Internet access) from internal/private subnetworks that host Controlled Unclassified Information (CUI) or internal services; the key objectives are preventing direct Internet access to internal hosts, limiting attack surface, and ensuring that public-facing components (ALBs, NAT gateways, bastions) are the only resources exposed to 0.0.0.0/0. For implementation this means segregated route tables, tightly-scoped security groups, optional Network ACLs, and monitoring such as VPC Flow Logs and AWS Config rules to detect drift.
High-level VPC Architecture Pattern
A recommended pattern for small businesses is a 4-subnet VPC per AZ: two public subnets (one per AZ) that host ALBs, NAT Gateways, or managed services that must be Internet-facing, and two private subnets (one per AZ) that host application servers and databases with no direct route to the Internet. Public subnets get a route to an Internet Gateway (IGW); private subnets route outbound via NAT Gateway in the public subnet. Security groups should only allow 0.0.0.0/0 to reach the ALB SG and should not permit direct inbound to private instance SGs. Use SSM Session Manager for admin access instead of an Internet-accessible bastion host where possible.
Step-by-step Implementation Notes (practical)
1) Choose a VPC CIDR (e.g., 10.0.0.0/16) and split into public (10.0.0.0/24, 10.0.1.0/24) and private (10.0.2.0/24, 10.0.3.0/24) subnets across AZs. 2) Create an Internet Gateway and attach it to the VPC; associate a public route table with public subnets only. 3) Provision one Elastic IP + NAT Gateway per AZ (or use 1 NAT GW for cost savings but note HA tradeoffs) and create a private route table where 0.0.0.0/0 points to the NAT. 4) Define security groups: ALB-SG (HTTP/HTTPS from 0.0.0.0/0), App-SG (only allow ingress from ALB-SG on app ports), DB-SG (only allow ingress from App-SG). 5) Enable VPC Flow Logs to a CloudWatch Log Group or S3 and enable AWS Config rules like "restricted-common-ports" and "vpc-default-security-group-closed" to continuously evaluate compliance.
Sample CloudFormation (YAML) — deploys the pattern
The following minimal CloudFormation template creates a VPC with two public subnets, two private subnets, an IGW, a NAT Gateway, route tables and example security groups. Adapt AZ selection, CIDRs and resource counts to your environment and add outputs, tags, logging, and monitoring as needed.
AWSTemplateFormatVersion: '2010-09-09'
Description: VPC with isolated public and private subnets (sample for SC.L1-B.1.XI)
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags: [{Key: Name, Value: vpc-isolated-sample}]
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags: [{Key: Name, Value: vpc-igw}]
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
PublicSubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: true
Tags: [{Key: Name, Value: public-subnet-a}]
PublicSubnetB:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [1, !GetAZs '']
MapPublicIpOnLaunch: true
Tags: [{Key: Name, Value: public-subnet-b}]
PrivateSubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: false
Tags: [{Key: Name, Value: private-subnet-a}]
PrivateSubnetB:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.3.0/24
AvailabilityZone: !Select [1, !GetAZs '']
MapPublicIpOnLaunch: false
Tags: [{Key: Name, Value: private-subnet-b}]
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags: [{Key: Name, Value: public-rt}]
PublicRoute:
Type: AWS::EC2::Route
DependsOn: VPCGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicRTAssocA:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetA
RouteTableId: !Ref PublicRouteTable
PublicRTAssocB:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetB
RouteTableId: !Ref PublicRouteTable
NatEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NatGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatEIP.AllocationId
SubnetId: !Ref PublicSubnetA
Tags: [{Key: Name, Value: nat-gw}]
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags: [{Key: Name, Value: private-rt}]
PrivateRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway
PrivateRTAssocA:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnetA
RouteTableId: !Ref PrivateRouteTable
PrivateRTAssocB:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnetB
RouteTableId: !Ref PrivateRouteTable
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: ALB SG - allows HTTP/HTTPS from Internet
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Tags: [{Key: Name, Value: alb-sg}]
AppSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: App SG - only allows traffic from ALB SG
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref ALBSecurityGroup
Tags: [{Key: Name, Value: app-sg}]
Outputs:
VpcId:
Description: VPC ID
Value: !Ref VPC
PublicSubnetA:
Value: !Ref PublicSubnetA
PrivateSubnetA:
Value: !Ref PrivateSubnetA
How a Small Business Might Use This Pattern
Example: a small government contractor runs a public-facing marketing site and an internal proposal management app. Place the public web ALB in public subnets with the ALB-SG open to the Internet; app servers and the database run in private subnets with App-SG and DB-SG allowing traffic only from the ALB and other internal SGs. Admin access uses SSM Session Manager (no public SSH). This set up meets the isolation requirement: internal subnets have no direct IGW route and only limited inbound flows from explicitly allowed sources.
Compliance Tips and Best Practices
1) Tag every subnet and resource with purpose and compliance_tier so auditors can filter resources quickly. 2) Enable VPC Flow Logs and retain logs for the required retention period — stream to S3 with lifecycle rules and encryption. 3) Use AWS Config managed rules (e.g., vpc-default-security-group-closed, restricted-common-ports) and custom Config rules to detect public subnets hosting non-approved instances. 4) Prefer SSM over bastion hosts and use least-privilege IAM for engineers. 5) Regularly scan security groups for 0.0.0.0/0 openings using automated scripts or AWS Trusted Advisor/third-party tooling.
Risk of Not Isolating Public Subnetworks
Failing to isolate public subnets increases risk of direct compromise and lateral movement: if an attacker reaches an Internet-exposed instance in the same subnet as internal servers, they can pivot to databases and CUI, causing data leaks and regulatory noncompliance. For contractors under FAR 52.204-21, this could lead to audit findings, contract penalties, or loss of eligibility for sensitive work. Operationally you also risk undetected exfiltration if you lack flow logs and config tracking.
In summary, isolating public subnetworks is straightforward but essential: use separate route tables, NAT gateways for outbound from private subnets, strict security groups, VPC Flow Logs, and AWS Config to enforce and monitor the state. The provided CloudFormation template gives a practical starting point; adapt CIDRs, HA choices (multiple NATs per AZ), and logging/retention policies to match your small business's risk posture and contractual obligations under FAR and CMMC.