Skip to content

8.2 VPC and Security Groups

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.

ComponentPurpose
Internet GatewayConnects VPC to the internet. Stateless, horizontally scaled.
NAT GatewayAllows private instances to reach the internet. Blocks inbound initiation.
Route TableDetermines where traffic goes. Each subnet has exactly 1 route table.
VPC PeeringDirect connection between 2 VPCs. Traffic stays on the provider backbone.
Transit GatewayHub connecting multiple VPCs and on-premises networks.
DestinationTargetPurpose
10.0.0.0/16localTraffic within the VPC stays local
0.0.0.0/0igw-abc123Internet-bound traffic (public subnet)
0.0.0.0/0nat-xyz789Internet-bound traffic (private subnet)
172.16.0.0/12vgw-def456On-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.

DirectionDefault BehaviorTypical Configuration
InboundDenyAllow specific ports from specific sources
OutboundAllowRestrict 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.

RuleProtocolPortSourcePurpose
Allow HTTPSTCP4430.0.0.0/0Public web access
Allow SSHTCP2210.0.50.0/24Admin access from management subnet
Allow ModbusTCP502sg-scadaSCADA 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.

FeatureSecurity GroupNACL
Applied toInstance (VM)Subnet
StatefulYes (return traffic allowed automatically)No (explicit rules for both directions)
Rule evaluationUnion of allows (evaluates rules together)Top-to-bottom, first match
DefaultDeny inbound, allow outboundAllow inbound and outbound
Use caseInstance-level controlSubnet-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.