Public subnets route to IGW, private subnets do not
Place databases and application servers in private subnets. Place load balancers and NAT gateways in public subnets.
Cloud deployment and service models define the management boundary. The VPC is the virtual network hosting the workloads.
A VPC (Virtual Private Cloud) is a logically isolated network within a public cloud. A VPC behaves like a private network: define the IP address range, create subnets, configure route tables, and apply security controls.
A VPC contains 1 or more subnets. Each subnet maps to a single Availability Zone (AZ) and has a dedicated route table.
A public subnet has a route table entry sending internet-bound traffic (0.0.0.0/0) to an Internet Gateway (IGW). Instances in a public subnet have public IP addresses and are directly reachable from the internet.
A private subnet has no route to the IGW. Instances in a private subnet are unreachable from the internet. To allow outbound internet access (for software updates, for example), route traffic through a NAT Gateway in the public subnet.
| Component | Purpose |
|---|---|
| Internet Gateway | Connects VPC to the internet. Stateless, horizontally scaled. |
| NAT Gateway | Allows private instances to reach the internet. Blocks inbound initiation. |
| Route Table | Determines where traffic goes. Each subnet has exactly 1 route table. |
| VPC Peering | Direct connection between 2 VPCs. Traffic stays on the provider backbone. |
| Transit Gateway | Hub connecting multiple VPCs and on-premises networks. |
| Destination | Target | Purpose |
|---|---|---|
| 10.0.0.0/16 | local | Traffic within the VPC stays local |
| 0.0.0.0/0 | igw-abc123 | Internet-bound traffic (public subnet) |
| 0.0.0.0/0 | nat-xyz789 | Internet-bound traffic (private subnet) |
| 172.16.0.0/12 | vgw-def456 | On-premises traffic via VPN gateway |
A security group acts as a stateful virtual firewall around an individual instance (VM). Every instance belongs to 1 or more security groups.
Security groups are stateful: allowing inbound traffic on port 443 automatically allows the response traffic outbound. A separate outbound rule for return traffic is unnecessary. The concept matches a stateful firewall on-premises.
Each security group has separate inbound and outbound rule sets. The default security group denies inbound traffic and allows outbound traffic.
| Direction | Default Behavior | Typical Configuration |
|---|---|---|
| Inbound | Deny | Allow specific ports from specific sources |
| Outbound | Allow | Restrict to specific destinations in high-security environments |
Each rule specifies a protocol (TCP, UDP, ICMP), a port range, and a source (inbound) or destination (outbound). The source accepts a CIDR block, another security group, or a prefix list.
| Rule | Protocol | Port | Source | Purpose |
|---|---|---|---|---|
| Allow HTTPS | TCP | 443 | 0.0.0.0/0 | Public web access |
| Allow SSH | TCP | 22 | 10.0.50.0/24 | Admin access from management subnet |
| Allow Modbus | TCP | 502 | sg-scada | SCADA servers (referenced by security group) |
Referencing another security group as the source (instead of an IP range) means “allow traffic from instances in the referenced security group.” This approach decouples rules from specific IP addresses. IP addresses change when instances are replaced.
| Feature | Security Group | NACL |
|---|---|---|
| Applied to | Instance (VM) | Subnet |
| Stateful | Yes (return traffic allowed automatically) | No (explicit rules for both directions) |
| Rule evaluation | Union of allows (evaluates rules together) | Top-to-bottom, first match |
| Default | Deny inbound, allow outbound | Allow inbound and outbound |
| Use case | Instance-level control | Subnet-level defense in depth |
Use security groups as the primary control. Use NACLs as a second layer for subnet-wide policies (blocking entire IP ranges, for example).
The following script uses the AWS SDK for Python (boto3) to list security group rules in a VPC. Run the script to audit whether a security group allows overly permissive access to OT protocol ports.
import boto3
OT_PORTS = {502, 44818, 2222, 4840, 20000, 102}
def audit_security_groups(vpc_id: str): ec2 = boto3.client("ec2") response = ec2.describe_security_groups( Filters=[{"Name": "vpc-id", "Values": [vpc_id]}] ) findings = [] for sg in response["SecurityGroups"]: for rule in sg.get("IpPermissions", []): from_port = rule.get("FromPort", 0) to_port = rule.get("ToPort", 0) for ip_range in rule.get("IpRanges", []): cidr = ip_range.get("CidrIp", "") # Flag rules that allow OT ports from broad ranges if cidr == "0.0.0.0/0" and any( from_port <= p <= to_port for p in OT_PORTS ): findings.append({ "sg": sg["GroupId"], "name": sg["GroupName"], "port_range": f"{from_port}-{to_port}", "source": cidr, }) return findings
findings = audit_security_groups("vpc-0123456789abcdef0")for f in findings: print(f"FINDING: {f['sg']} ({f['name']}) allows ports {f['port_range']} " f"from {f['source']}")if not findings: print("No overly permissive OT port rules found.")A security group allowing Modbus TCP (port 502) from 0.0.0.0/0 is a significant finding. Modbus has no authentication. Exposing Modbus to the internet gives an attacker full read/write access to PLC registers.
Public subnets route to IGW, private subnets do not
Place databases and application servers in private subnets. Place load balancers and NAT gateways in public subnets.
Security groups are stateful, NACLs are stateless
Security groups allow return traffic automatically. NACLs require explicit rules for both directions.
Audit OT ports in security groups
Exposing Modbus, EtherNet/IP, or S7comm ports to 0.0.0.0/0 is unacceptable. Audit security groups on a regular schedule.
VPCs and security groups help protect cloud workloads. The next page covers connecting the cloud to on-premises networks using Direct Connect and VPN, plus NFV concepts.