9 min read

AWS VPC Security Architecture: Building Fortress-Level Network Security

VPCNetwork SecurityArchitectureDefense in Depth

VPC security architecture forms the foundation of AWS network security, yet many organizations treat it as an afterthought. They focus on application-layer security while neglecting the network controls that could prevent attacks from reaching their applications in the first place.

The challenge with VPC security is that it requires thinking in layers and understanding complex interactions between multiple security controls. A single misconfigured route table, overpermissive security group, or missing network ACL can create security gaps that attackers can exploit. And as your AWS environment grows across multiple VPCs and accounts, these complexities multiply exponentially.

This comprehensive guide explores advanced patterns for building secure, scalable, and maintainable VPC architectures that protect against modern threats. We'll cover defense-in-depth strategies, network segmentation patterns, and monitoring approaches that provide comprehensive network security at enterprise scale.

VPC Security Architecture Principles

Defense in Depth Strategy

# Multi-layer security architecture
Security Layers:
  1. Network ACLs (Subnet level)
  2. Security Groups (Instance level)
  3. WAF (Application level)
  4. Host-based firewalls
  5. Application security controls

Network Segmentation Patterns

{
  "VPCArchitecture": {
    "PublicSubnets": {
      "Purpose": "Load balancers, NAT gateways, bastion hosts",
      "InternetAccess": "Bidirectional",
      "SecurityControls": ["NACLs", "SecurityGroups", "WAF"]
    },
    "PrivateSubnets": {
      "Purpose": "Application servers, databases",
      "InternetAccess": "Outbound only via NAT",
      "SecurityControls": ["NACLs", "SecurityGroups", "VPC Endpoints"]
    },
    "IsolatedSubnets": {
      "Purpose": "Highly sensitive workloads",
      "InternetAccess": "None",
      "SecurityControls": ["NACLs", "SecurityGroups", "Private endpoints"]
    }
  }
}

Advanced VPC Security Patterns

Hub-and-Spoke Architecture

import boto3

def create_hub_spoke_architecture():
    ec2 = boto3.client('ec2')
    
    # Create hub VPC (shared services)
    hub_vpc = ec2.create_vpc(
        CidrBlock='10.0.0.0/16',
        TagSpecifications=[{
            'ResourceType': 'vpc',
            'Tags': [
                {'Key': 'Name', 'Value': 'hub-vpc'},
                {'Key': 'Purpose', 'Value': 'shared-services'}
            ]
        }]
    )
    
    # Create spoke VPCs (workload-specific)
    spoke_vpcs = []
    workloads = ['production', 'staging', 'development']
    
    for i, workload in enumerate(workloads):
        spoke_vpc = ec2.create_vpc(
            CidrBlock=f'10.{i+1}.0.0/16',
            TagSpecifications=[{
                'ResourceType': 'vpc',
                'Tags': [
                    {'Key': 'Name', 'Value': f'{workload}-vpc'},
                    {'Key': 'Environment', 'Value': workload}
                ]
            }]
        )
        spoke_vpcs.append(spoke_vpc)
    
    # Create Transit Gateway for connectivity
    tgw = ec2.create_transit_gateway(
        Description='Hub-spoke transit gateway',
        Options={
            'AmazonSideAsn': 64512,
            'DefaultRouteTableAssociation': 'disable',
            'DefaultRouteTablePropagation': 'disable'
        },
        TagSpecifications=[{
            'ResourceType': 'transit-gateway',
            'Tags': [{'Key': 'Name', 'Value': 'hub-spoke-tgw'}]
        }]
    )
    
    return hub_vpc, spoke_vpcs, tgw

def implement_segmented_routing(tgw_id, vpc_attachments):
    """Implement segmented routing with custom route tables"""
    ec2 = boto3.client('ec2')
    
    # Create route tables for different traffic patterns
    route_tables = {
        'shared-services': ec2.create_transit_gateway_route_table(
            TransitGatewayId=tgw_id,
            TagSpecifications=[{
                'ResourceType': 'transit-gateway-route-table',
                'Tags': [{'Key': 'Name', 'Value': 'shared-services-rt'}]
            }]
        ),
        'production': ec2.create_transit_gateway_route_table(
            TransitGatewayId=tgw_id,
            TagSpecifications=[{
                'ResourceType': 'transit-gateway-route-table', 
                'Tags': [{'Key': 'Name', 'Value': 'production-rt'}]
            }]
        ),
        'non-production': ec2.create_transit_gateway_route_table(
            TransitGatewayId=tgw_id,
            TagSpecifications=[{
                'ResourceType': 'transit-gateway-route-table',
                'Tags': [{'Key': 'Name', 'Value': 'non-production-rt'}]
            }]
        )
    }
    
    # Associate and propagate routes based on security requirements
    for attachment in vpc_attachments:
        environment = attachment['environment']
        
        if environment == 'production':
            # Production can only communicate with shared services
            ec2.associate_transit_gateway_route_table(
                TransitGatewayAttachmentId=attachment['id'],
                TransitGatewayRouteTableId=route_tables['production']['TransitGatewayRouteTable']['TransitGatewayRouteTableId']
            )
        else:
            # Non-production environments can communicate with each other
            ec2.associate_transit_gateway_route_table(
                TransitGatewayAttachmentId=attachment['id'],
                TransitGatewayRouteTableId=route_tables['non-production']['TransitGatewayRouteTable']['TransitGatewayRouteTableId']
            )

Micro-Segmentation with Security Groups

def create_microsegmentation_security_groups():
    """Create fine-grained security groups for micro-segmentation"""
    ec2 = boto3.client('ec2')
    
    # Application tier security groups
    security_groups = {
        'web-tier': {
            'description': 'Web tier - accepts traffic from ALB only',
            'ingress_rules': [
                {
                    'IpProtocol': 'tcp',
                    'FromPort': 80,
                    'ToPort': 80,
                    'UserIdGroupPairs': [{'GroupId': 'sg-alb'}]
                },
                {
                    'IpProtocol': 'tcp', 
                    'FromPort': 443,
                    'ToPort': 443,
                    'UserIdGroupPairs': [{'GroupId': 'sg-alb'}]
                }
            ]
        },
        'app-tier': {
            'description': 'Application tier - accepts traffic from web tier only',
            'ingress_rules': [
                {
                    'IpProtocol': 'tcp',
                    'FromPort': 8080,
                    'ToPort': 8080,
                    'UserIdGroupPairs': [{'GroupId': 'sg-web-tier'}]
                }
            ]
        },
        'db-tier': {
            'description': 'Database tier - accepts traffic from app tier only',
            'ingress_rules': [
                {
                    'IpProtocol': 'tcp',
                    'FromPort': 3306,
                    'ToPort': 3306,
                    'UserIdGroupPairs': [{'GroupId': 'sg-app-tier'}]
                }
            ]
        },
        'management': {
            'description': 'Management access - restricted to admin networks',
            'ingress_rules': [
                {
                    'IpProtocol': 'tcp',
                    'FromPort': 22,
                    'ToPort': 22,
                    'IpRanges': [{'CidrIp': '10.0.100.0/24', 'Description': 'Admin subnet'}]
                }
            ]
        }
    }
    
    created_sgs = {}
    for sg_name, config in security_groups.items():
        sg = ec2.create_security_group(
            GroupName=sg_name,
            Description=config['description'],
            VpcId='vpc-12345678'
        )
        
        # Add ingress rules
        if config['ingress_rules']:
            ec2.authorize_security_group_ingress(
                GroupId=sg['GroupId'],
                IpPermissions=config['ingress_rules']
            )
        
        created_sgs[sg_name] = sg['GroupId']
    
    return created_sgs

Network Access Control Lists (NACLs)

Stateless Firewall Rules

def create_security_focused_nacls():
    """Create NACLs with security-first approach"""
    ec2 = boto3.client('ec2')
    
    # Public subnet NACL - restrictive inbound, permissive outbound
    public_nacl_rules = [
        # Inbound rules
        {
            'RuleNumber': 100,
            'Protocol': '6',  # TCP
            'RuleAction': 'allow',
            'PortRange': {'From': 80, 'To': 80},
            'CidrBlock': '0.0.0.0/0'
        },
        {
            'RuleNumber': 110,
            'Protocol': '6',  # TCP
            'RuleAction': 'allow',
            'PortRange': {'From': 443, 'To': 443},
            'CidrBlock': '0.0.0.0/0'
        },
        {
            'RuleNumber': 120,
            'Protocol': '6',  # TCP
            'RuleAction': 'allow',
            'PortRange': {'From': 1024, 'To': 65535},  # Ephemeral ports
            'CidrBlock': '0.0.0.0/0'
        },
        # Deny all other inbound traffic
        {
            'RuleNumber': 32767,
            'Protocol': '-1',
            'RuleAction': 'deny',
            'CidrBlock': '0.0.0.0/0'
        }
    ]
    
    # Private subnet NACL - very restrictive
    private_nacl_rules = [
        # Allow traffic from public subnets only
        {
            'RuleNumber': 100,
            'Protocol': '6',  # TCP
            'RuleAction': 'allow',
            'PortRange': {'From': 80, 'To': 80},
            'CidrBlock': '10.0.1.0/24'  # Public subnet CIDR
        },
        {
            'RuleNumber': 110,
            'Protocol': '6',  # TCP
            'RuleAction': 'allow',
            'PortRange': {'From': 443, 'To': 443},
            'CidrBlock': '10.0.1.0/24'
        },
        # Allow return traffic
        {
            'RuleNumber': 120,
            'Protocol': '6',  # TCP
            'RuleAction': 'allow',
            'PortRange': {'From': 1024, 'To': 65535},
            'CidrBlock': '0.0.0.0/0'
        }
    ]
    
    return public_nacl_rules, private_nacl_rules

def implement_nacl_logging():
    """Enable VPC Flow Logs for NACL monitoring"""
    ec2 = boto3.client('ec2')
    
    # Create flow log for rejected traffic
    flow_log = ec2.create_flow_log(
        ResourceType='VPC',
        ResourceIds=['vpc-12345678'],
        TrafficType='REJECT',
        LogDestinationType='cloud-watch-logs',
        LogGroupName='/aws/vpc/flowlogs',
        DeliverLogsPermissionArn='arn:aws:iam::123456789012:role/flowlogsRole',
        TagSpecifications=[{
            'ResourceType': 'vpc-flow-log',
            'Tags': [
                {'Key': 'Name', 'Value': 'security-flow-logs'},
                {'Key': 'Purpose', 'Value': 'security-monitoring'}
            ]
        }]
    )
    
    return flow_log

VPC Endpoints for Secure Service Access

Private Connectivity Patterns

def create_vpc_endpoints_for_security():
    """Create VPC endpoints to avoid internet traffic"""
    ec2 = boto3.client('ec2')
    
    # Gateway endpoints (S3, DynamoDB)
    gateway_endpoints = [
        {
            'ServiceName': 'com.amazonaws.us-east-1.s3',
            'VpcEndpointType': 'Gateway',
            'RouteTableIds': ['rtb-12345678']
        },
        {
            'ServiceName': 'com.amazonaws.us-east-1.dynamodb',
            'VpcEndpointType': 'Gateway', 
            'RouteTableIds': ['rtb-12345678']
        }
    ]
    
    # Interface endpoints (other AWS services)
    interface_endpoints = [
        {
            'ServiceName': 'com.amazonaws.us-east-1.ec2',
            'VpcEndpointType': 'Interface',
            'SubnetIds': ['subnet-12345678'],
            'SecurityGroupIds': ['sg-vpc-endpoints']
        },
        {
            'ServiceName': 'com.amazonaws.us-east-1.ssm',
            'VpcEndpointType': 'Interface',
            'SubnetIds': ['subnet-12345678'],
            'SecurityGroupIds': ['sg-vpc-endpoints']
        },
        {
            'ServiceName': 'com.amazonaws.us-east-1.kms',
            'VpcEndpointType': 'Interface',
            'SubnetIds': ['subnet-12345678'],
            'SecurityGroupIds': ['sg-vpc-endpoints']
        }
    ]
    
    created_endpoints = []
    
    # Create gateway endpoints
    for endpoint_config in gateway_endpoints:
        endpoint = ec2.create_vpc_endpoint(
            VpcId='vpc-12345678',
            ServiceName=endpoint_config['ServiceName'],
            VpcEndpointType=endpoint_config['VpcEndpointType'],
            RouteTableIds=endpoint_config['RouteTableIds']
        )
        created_endpoints.append(endpoint)
    
    # Create interface endpoints
    for endpoint_config in interface_endpoints:
        endpoint = ec2.create_vpc_endpoint(
            VpcId='vpc-12345678',
            ServiceName=endpoint_config['ServiceName'],
            VpcEndpointType=endpoint_config['VpcEndpointType'],
            SubnetIds=endpoint_config['SubnetIds'],
            SecurityGroupIds=endpoint_config['SecurityGroupIds'],
            PrivateDnsEnabled=True
        )
        created_endpoints.append(endpoint)
    
    return created_endpoints

DNS Security in VPC

DNS Filtering and Monitoring

def implement_dns_security():
    """Implement DNS security controls"""
    route53resolver = boto3.client('route53resolver')
    
    # Create DNS firewall rule group
    firewall_rule_group = route53resolver.create_firewall_rule_group(
        Name='security-dns-firewall',
        Tags=[
            {'Key': 'Purpose', 'Value': 'DNS security filtering'}
        ]
    )
    
    # Block known malicious domains
    malicious_domains = [
        'malware-domain.com',
        'phishing-site.net',
        'command-control.org'
    ]
    
    # Create domain list for blocking
    domain_list = route53resolver.create_firewall_domain_list(
        Name='blocked-domains',
        Domains=malicious_domains,
        Tags=[
            {'Key': 'Purpose', 'Value': 'Malicious domain blocking'}
        ]
    )
    
    # Create firewall rule
    firewall_rule = route53resolver.create_firewall_rule(
        FirewallRuleGroupId=firewall_rule_group['FirewallRuleGroup']['Id'],
        FirewallDomainListId=domain_list['FirewallDomainList']['Id'],
        Priority=100,
        Action='BLOCK',
        BlockResponse='NXDOMAIN',
        Name='block-malicious-domains'
    )
    
    # Associate with VPC
    association = route53resolver.associate_firewall_rule_group(
        FirewallRuleGroupId=firewall_rule_group['FirewallRuleGroup']['Id'],
        VpcId='vpc-12345678',
        Priority=100,
        Name='vpc-dns-security'
    )
    
    return firewall_rule_group, domain_list, association

VPC Security Monitoring

Flow Log Analysis

def analyze_vpc_flow_logs():
    """Analyze VPC flow logs for security insights"""
    
    # CloudWatch Insights query for suspicious traffic
    suspicious_traffic_query = """
    fields @timestamp, srcaddr, dstaddr, srcport, dstport, protocol, action
    | filter action = "REJECT"
    | stats count() by srcaddr
    | sort count desc
    | limit 20
    """
    
    # Query for data exfiltration patterns
    data_exfiltration_query = """
    fields @timestamp, srcaddr, dstaddr, bytes
    | filter bytes > 1000000
    | stats sum(bytes) as total_bytes by srcaddr, dstaddr
    | sort total_bytes desc
    | limit 10
    """
    
    # Query for port scanning
    port_scanning_query = """
    fields @timestamp, srcaddr, dstaddr, dstport, action
    | filter action = "REJECT"
    | stats count_distinct(dstport) as unique_ports by srcaddr
    | sort unique_ports desc
    | limit 10
    """
    
    return {
        'suspicious_traffic': suspicious_traffic_query,
        'data_exfiltration': data_exfiltration_query,
        'port_scanning': port_scanning_query
    }

def create_security_dashboards():
    """Create CloudWatch dashboards for VPC security monitoring"""
    cloudwatch = boto3.client('cloudwatch')
    
    dashboard_body = {
        "widgets": [
            {
                "type": "log",
                "properties": {
                    "query": "SOURCE '/aws/vpc/flowlogs'\n| fields @timestamp, srcaddr, dstaddr, action\n| filter action = \"REJECT\"\n| stats count() by srcaddr\n| sort count desc\n| limit 20",
                    "region": "us-east-1",
                    "title": "Top Rejected Source IPs",
                    "view": "table"
                }
            },
            {
                "type": "metric",
                "properties": {
                    "metrics": [
                        ["AWS/VPC", "PacketsDroppedBySecurityGroup"],
                        [".", "PacketsDroppedByNetworkAcl"]
                    ],
                    "period": 300,
                    "stat": "Sum",
                    "region": "us-east-1",
                    "title": "Dropped Packets"
                }
            }
        ]
    }
    
    dashboard = cloudwatch.put_dashboard(
        DashboardName='VPC-Security-Monitoring',
        DashboardBody=json.dumps(dashboard_body)
    )
    
    return dashboard

Automated Security Response

Lambda-based Incident Response

import json
import boto3

def lambda_handler(event, context):
    """Automated response to VPC security events"""
    
    # Parse CloudWatch alarm
    message = json.loads(event['Records'][0]['Sns']['Message'])
    alarm_name = message['AlarmName']
    
    if 'suspicious-traffic' in alarm_name.lower():
        handle_suspicious_traffic(message)
    elif 'port-scan' in alarm_name.lower():
        handle_port_scanning(message)
    elif 'data-exfiltration' in alarm_name.lower():
        handle_data_exfiltration(message)
    
    return {'statusCode': 200}

def handle_suspicious_traffic(alarm_data):
    """Handle suspicious traffic alerts"""
    ec2 = boto3.client('ec2')
    
    # Extract source IP from alarm
    source_ip = extract_source_ip_from_alarm(alarm_data)
    
    if source_ip:
        # Create NACL rule to block the IP
        nacl_id = get_public_subnet_nacl()
        
        # Add deny rule with high priority
        ec2.create_network_acl_entry(
            NetworkAclId=nacl_id,
            RuleNumber=50,  # High priority
            Protocol='-1',  # All protocols
            RuleAction='deny',
            CidrBlock=f'{source_ip}/32'
        )
        
        # Send notification
        send_security_notification(
            f"Blocked suspicious IP {source_ip} via NACL rule",
            alarm_data
        )

def handle_port_scanning(alarm_data):
    """Handle port scanning detection"""
    # Implement automated blocking and investigation
    pass

def handle_data_exfiltration(alarm_data):
    """Handle potential data exfiltration"""
    # Implement data loss prevention measures
    pass

Conclusion

VPC security architecture requires a comprehensive approach that combines multiple security controls and monitoring capabilities. Key takeaways:

  • Layer security controls at network, subnet, and instance levels
  • Implement micro-segmentation with security groups and NACLs
  • Use VPC endpoints to avoid internet traffic for AWS services
  • Monitor traffic patterns with VPC Flow Logs and CloudWatch
  • Automate incident response for rapid threat mitigation

Effective VPC security is not just about configuration - it's about creating a living, breathing security ecosystem that adapts to threats and provides comprehensive visibility into your network traffic patterns.

The architecture patterns shown here provide a foundation for enterprise-grade VPC security, but remember that security is an ongoing process that requires continuous monitoring, testing, and improvement.

Comprehensive Network Security Visibility

VPC security architecture is only as effective as your ability to monitor and maintain it. As your environment grows and evolves, you need visibility into how your network security controls are performing and whether they're providing the protection you expect.

While this guide focuses on VPC-level security, remember that network security and IAM security are interconnected. An attacker who gains access to your network through a VPC misconfiguration will likely attempt to escalate privileges through IAM vulnerabilities.

AccessLens provides the IAM visibility that complements your VPC security monitoring:

  • Cross-account access analysis that shows how IAM roles can be used to bypass network controls
  • Privilege escalation detection that identifies paths attackers might use after gaining network access
  • Trust relationship mapping that reveals how network compromise in one account could affect others
  • Compliance monitoring that ensures your IAM controls align with your network security policies

Enhance your network security strategy with comprehensive IAM analysis from AccessLens.

Network security and IAM security work together to protect your AWS environment. Don't let gaps in one area undermine the effectiveness of the other.