Copyright (c) 2025 MindMesh Academy. All rights reserved. This content is proprietary and may not be reproduced or distributed without permission.

3.1.1. Security Groups and Network ACLs (NACLs)

Security Groups (SGs) and Network ACLs (NACLs) are virtual firewalls that control traffic flow within a VPC, enabling granular network isolation and protection at different layers.

Scenario: You need to protect your web application running on EC2 instances. Only HTTPS traffic from the internet should reach your web servers. Your web servers need to communicate with a database in a private subnet, but the database should not be directly accessible from the internet.

Security Groups (SGs) and Network Access Control Lists (NACLs) are the foundational network security features within an Amazon VPC. Network specialists configure these to precisely control traffic.

Key Characteristics and Differences:
FeatureSecurity Groups (SGs)Network ACLs (NACLs)
ScopeInstance-level (ENI)Subnet-level
StatefulnessStatefulStateless
RulesAllow rules onlyAllow and Deny rules
EvaluationAll rules evaluatedRules processed in order
  • Security Groups (SGs):
    • Level: Instance-level firewall. Applies to individual EC2 instances or Elastic Network Interfaces (ENIs).
    • Stateful: If you allow inbound traffic (e.g., HTTP on port 80), return outbound traffic is automatically allowed for the same connection.
    • Allow-Only: You define only allow rules. Traffic that doesn't explicitly match an allow rule is implicitly denied.
    • Evaluation: All rules are evaluated before deciding whether to allow traffic.
    • Use Cases: Controlling application port access, allowing specific IP addresses or other Security Groups access.
  • Network Access Control Lists (NACLs):
    • Level: Subnet-level firewall. Applies to all resources within a subnet.
    • Stateless: Inbound and outbound traffic rules are evaluated separately; you must explicitly allow return traffic for connections.
    • Allow and Deny: You can explicitly allow or deny traffic.
    • Evaluation: Rules are processed in order (lowest numbered rule first). The first matching rule is applied.
    • Use Cases: Coarse-grained subnet-level filtering, broad deny rules (e.g., blocking malicious IP addresses).
Practical Implementation: Configuring Security Groups and NACLs
# Assuming VPC_ID, WEB_SUBNET_ID, APP_SUBNET_ID, DB_SUBNET_ID are defined

# 1. Create Security Group for Web Servers (Allow HTTPS from Internet, Outbound to App SG)
WEB_SG_ID=$(aws ec2 create-security-group \
  --group-name WebServerSG \
  --description "Allow HTTPS to web servers" \
  --vpc-id $VPC_ID \
  --query GroupId --output text)
echo "Web Server SG ID: $WEB_SG_ID"

aws ec2 authorize-security-group-ingress \
  --group-id $WEB_SG_ID \
  --protocol tcp \
  --port 443 \
  --cidr 0.0.0.0/0

# 2. Create Security Group for Application Servers (Inbound from Web SG, Outbound to DB SG)
APP_SG_ID=$(aws ec2 create-security-group \
  --group-name AppServerSG \
  --description "Allow traffic from web servers" \
  --vpc-id $VPC_ID \
  --query GroupId --output text)
echo "App Server SG ID: $APP_SG_ID"

aws ec2 authorize-security-group-ingress \
  --group-id $APP_SG_ID \
  --protocol tcp \
  --port 8080 \
  --source-group $WEB_SG_ID # Reference Web SG

# 3. Create Security Group for Database Servers (Inbound from App SG)
DB_SG_ID=$(aws ec2 create-security-group \
  --group-name DbServerSG \
  --description "Allow traffic from app servers" \
  --vpc-id $VPC_ID \
  --query GroupId --output text)
echo "DB Server SG ID: $DB_SG_ID"

aws ec2 authorize-security-group-ingress \
  --group-id $DB_SG_ID \
  --protocol tcp \
  --port 5432 \
  --source-group $APP_SG_ID # Reference App SG

# 4. Configure Network ACLs (Example: Deny all inbound to DB subnet from internet)
# Get default NACL for DB subnet (or create new)
DB_NACL_ID=$(aws ec2 describe-network-acls --filters "Name=vpc-id,Values=$VPC_ID" "Name=association.subnet-id,Values=$DB_SUBNET_ID" --query "NetworkAcls[0].NetworkAclId" --output text)
echo "DB Subnet NACL ID: $DB_NACL_ID"

# Add a DENY rule for all inbound traffic from 0.0.0.0/0 (rule number lower than default ALLOW)
aws ec2 create-network-acl-entry \
  --network-acl-id $DB_NACL_ID \
  --rule-number 100 \
  --protocol -1 \
  --rule-action DENY \
  --egress \
  --cidr-block 0.0.0.0/0

# Add a DENY rule for all outbound traffic to 0.0.0.0/0 (rule number lower than default ALLOW)
aws ec2 create-network-acl-entry \
  --network-acl-id $DB_NACL_ID \
  --rule-number 100 \
  --protocol -1 \
  --rule-action DENY \
  --ingress \
  --cidr-block 0.0.0.0/0

⚠️ Common Pitfall: Forgetting that NACLs are stateless. If you allow inbound traffic, you must explicitly allow the corresponding outbound return traffic, and vice-versa.

Key Trade-Offs:
  • Granularity (Security Groups) vs. Broad Control (Network ACLs): Security Groups provide fine-grained, stateful control for specific instances. Network ACLs provide broad, stateless allow/deny rules for entire subnets.

Reflection Question: How do Security Groups (instance-level, stateful) and Network ACLs (subnet-level, stateless) fundamentally provide virtual firewall capabilities at different layers, enabling granular network isolation and protection for your AWS resources?

💡 Tip: Remember that NACLs are evaluated before Security Groups for inbound traffic, and after Security Groups for outbound traffic.