EC2 Security Groups: Your Virtual Firewall in the Cloud
Imagine this: You’ve launched an EC2 instance (a virtual server) in AWS. By default, it’s completely isolated - nothing can reach it, and it can’t reach anything. That’s actually good for security, but you probably need it to do something. Security Groups are your virtual firewall that controls exactly what traffic is allowed. Let me show you how to configure them properly.
What are Security Groups, Really?
Think of Security Groups like this:
Real-world analogy: Your EC2 instance is a house. Security Groups are the security system that controls:
- Who can knock on the door (inbound traffic)
- What doors/windows are open (ports)
- Where traffic can come from (source IPs)
- Where traffic can go (outbound traffic)
Key characteristics:
- Stateful - If you allow traffic in, the response is automatically allowed out
- Default deny - Everything is blocked unless explicitly allowed
- Virtual firewall - Applied at the instance level, not the network level
Security Groups vs Network ACLs
You might hear about Network ACLs (Access Control Lists). Here’s the difference:
| Feature | Security Groups | Network ACLs |
|---|---|---|
| Level | Instance level | Subnet level |
| Stateful | Yes (automatic return traffic) | No (stateless) |
| Rules | Allow only | Allow and Deny |
| Evaluation | All rules evaluated | Rules evaluated in order |
For most use cases, Security Groups are what you need. Network ACLs are for additional subnet-level security.
Understanding Security Group Rules
Security Group rules have 4 components:
- Type - The protocol (TCP, UDP, ICMP, etc.)
- Protocol - Usually TCP for web traffic
- Port Range - Which ports (e.g., 80 for HTTP, 443 for HTTPS)
- Source/Destination - Where traffic can come from/go to
Inbound Rules (Ingress)
Control what traffic can reach your instance:
1
2
3
4
Type: SSH
Protocol: TCP
Port: 22
Source: 10.0.0.0/16 (your VPC)
This says: “Allow SSH (port 22) traffic from IPs in the 10.0.0.0/16 range.”
Outbound Rules (Egress)
Control what traffic can leave your instance:
1
2
3
4
Type: HTTPS
Protocol: TCP
Port: 443
Destination: 0.0.0.0/0 (anywhere)
This says: “Allow HTTPS (port 443) traffic to anywhere.”
Important: By default, outbound traffic is allowed to anywhere. You should restrict this for better security!
Common Security Group Patterns
Pattern 1: Web Server
A web server needs:
- HTTP (port 80) from the internet
- HTTPS (port 443) from the internet
- SSH (port 22) from your office IP only
Inbound Rules:
1
2
3
HTTP (80) | TCP | 80 | 0.0.0.0/0
HTTPS (443) | TCP | 443 | 0.0.0.0/0
SSH (22) | TCP | 22 | YOUR_OFFICE_IP/32
Outbound Rules:
1
2
HTTPS (443) | TCP | 443 | 0.0.0.0/0 (for API calls)
HTTP (80) | TCP | 80 | 0.0.0.0/0 (for package updates)
Pattern 2: Database Server
A database should NEVER be accessible from the internet:
Inbound Rules:
1
2
MySQL (3306) | TCP | 3306 | 10.0.1.0/24 (only from app servers)
SSH (22) | TCP | 22 | 10.0.0.0/16 (only from VPC)
Outbound Rules:
1
HTTPS (443) | TCP | 443 | 0.0.0.0/0 (for updates)
Pattern 3: Compliance Scanner
Your compliance scanner needs:
- Outbound access to AWS APIs
- SSH from your management network
- No inbound access from internet
Inbound Rules:
1
SSH (22) | TCP | 22 | 10.0.0.0/16 (management network)
Outbound Rules:
1
HTTPS (443) | TCP | 443 | 0.0.0.0/0 (AWS APIs)
Creating Security Groups with AWS CLI
Create a Security Group
1
2
3
4
5
# Create security group
aws ec2 create-security-group \
--group-name compliance-scanner-sg \
--description "Security group for compliance scanner" \
--vpc-id vpc-12345678
Output:
1
2
3
{
"GroupId": "sg-1234567890abcdef0"
}
Add Inbound Rules
1
2
3
4
5
6
# Allow SSH from VPC
aws ec2 authorize-security-group-ingress \
--group-id sg-1234567890abcdef0 \
--protocol tcp \
--port 22 \
--cidr 10.0.0.0/16
Add Outbound Rules
1
2
3
4
5
6
# Allow HTTPS outbound
aws ec2 authorize-security-group-egress \
--group-id sg-1234567890abcdef0 \
--protocol tcp \
--port 443 \
--cidr 0.0.0.0/0
Creating Security Groups with Python (Boto3)
Here’s how to create and configure security groups programmatically:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import boto3
from botocore.exceptions import ClientError
def create_security_group(vpc_id, group_name, description):
"""Create a security group."""
ec2_client = boto3.client('ec2')
try:
response = ec2_client.create_security_group(
GroupName=group_name,
Description=description,
VpcId=vpc_id
)
group_id = response['GroupId']
print(f"Created security group: {group_id}")
return group_id
except ClientError as e:
if e.response['Error']['Code'] == 'InvalidGroup.Duplicate':
# Group already exists, get its ID
response = ec2_client.describe_security_groups(
GroupNames=[group_name]
)
return response['SecurityGroups'][0]['GroupId']
else:
print(f"Error creating security group: {e}")
return None
def add_inbound_rule(group_id, protocol, port, source):
"""Add an inbound rule to a security group."""
ec2_client = boto3.client('ec2')
try:
ec2_client.authorize_security_group_ingress(
GroupId=group_id,
IpPermissions=[
{
'IpProtocol': protocol,
'FromPort': port,
'ToPort': port,
'IpRanges': [
{
'CidrIp': source,
'Description': f'Allow {protocol} from {source}'
}
]
}
]
)
print(f"Added inbound rule: {protocol}:{port} from {source}")
except ClientError as e:
if e.response['Error']['Code'] == 'InvalidPermission.Duplicate':
print(f"Rule already exists: {protocol}:{port} from {source}")
else:
print(f"Error adding rule: {e}")
def add_outbound_rule(group_id, protocol, port, destination):
"""Add an outbound rule to a security group."""
ec2_client = boto3.client('ec2')
try:
ec2_client.authorize_security_group_egress(
GroupId=group_id,
IpPermissions=[
{
'IpProtocol': protocol,
'FromPort': port,
'ToPort': port,
'IpRanges': [
{
'CidrIp': destination,
'Description': f'Allow {protocol} to {destination}'
}
]
}
]
)
print(f"Added outbound rule: {protocol}:{port} to {destination}")
except ClientError as e:
if e.response['Error']['Code'] == 'InvalidPermission.Duplicate':
print(f"Rule already exists: {protocol}:{port} to {destination}")
else:
print(f"Error adding rule: {e}")
def create_compliance_scanner_sg(vpc_id):
"""Create a security group for compliance scanner."""
group_id = create_security_group(
vpc_id,
'compliance-scanner-sg',
'Security group for automated compliance scanner'
)
if group_id:
# Inbound: SSH from VPC only
add_inbound_rule(group_id, 'tcp', 22, '10.0.0.0/16')
# Outbound: HTTPS for AWS APIs
add_outbound_rule(group_id, 'tcp', 443, '0.0.0.0/0')
# Outbound: HTTP for package updates (optional, can be restricted)
add_outbound_rule(group_id, 'tcp', 80, '0.0.0.0/0')
return group_id
return None
# Example usage
if __name__ == "__main__":
vpc_id = "vpc-12345678" # Your VPC ID
sg_id = create_compliance_scanner_sg(vpc_id)
print(f"Security group ready: {sg_id}")
Auditing Security Groups for Risks
Here’s a script to find risky security group configurations:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import boto3
# Risky ports that shouldn't be open to the internet
RISKY_PORTS = {
22: "SSH",
3389: "RDP",
445: "SMB",
139: "NetBIOS",
1433: "SQL Server",
3306: "MySQL",
5432: "PostgreSQL",
27017: "MongoDB"
}
def audit_security_groups():
"""Audit all security groups for risky configurations."""
ec2_client = boto3.client('ec2')
# Get all security groups
response = ec2_client.describe_security_groups()
print("=" * 70)
print("Security Group Security Audit")
print("=" * 70)
for sg in response['SecurityGroups']:
sg_id = sg['GroupId']
sg_name = sg['GroupName']
vpc_id = sg['VpcId']
print(f"\nSecurity Group: {sg_name} ({sg_id})")
print(f"VPC: {vpc_id}")
print("-" * 70)
issues = []
# Check inbound rules
for rule in sg.get('IpPermissions', []):
port = rule.get('FromPort')
protocol = rule.get('IpProtocol')
# Check if port is risky
if port in RISKY_PORTS:
# Check if open to internet (0.0.0.0/0)
for ip_range in rule.get('IpRanges', []):
cidr = ip_range.get('CidrIp', '')
if cidr == '0.0.0.0/0':
issues.append(
f"CRITICAL: Port {port} ({RISKY_PORTS[port]}) "
f"is open to the internet (0.0.0.0/0)"
)
# Check for overly permissive rules
for ip_range in rule.get('IpRanges', []):
cidr = ip_range.get('CidrIp', '')
if cidr == '0.0.0.0/0' and protocol != '-1':
if port not in [80, 443]: # HTTP/HTTPS might be OK
issues.append(
f"WARNING: Port {port} ({protocol}) open to "
f"internet (0.0.0.0/0)"
)
if issues:
print("⚠️ Security Issues Found:")
for issue in issues:
print(f" - {issue}")
else:
print("✅ No obvious security issues found")
# Show current rules
print("\nCurrent Inbound Rules:")
if sg.get('IpPermissions'):
for rule in sg['IpPermissions']:
port = rule.get('FromPort', 'All')
protocol = rule.get('IpProtocol', 'all')
sources = [ip.get('CidrIp', '') for ip in rule.get('IpRanges', [])]
print(f" {protocol}:{port} from {', '.join(sources) if sources else 'N/A'}")
if __name__ == "__main__":
audit_security_groups()
Security Best Practices
1. Principle of Least Privilege
Bad:
1
Allow: All traffic (0.0.0.0/0) on all ports
Good:
1
2
Allow: SSH (22) from 10.0.0.0/16 only
Allow: HTTPS (443) from 0.0.0.0/0 (if needed for web server)
2. Restrict Outbound Traffic
Bad:
1
Allow: All outbound traffic (default)
Good:
1
2
3
Allow: HTTPS (443) to 0.0.0.0/0 (for AWS APIs)
Allow: HTTP (80) to specific update servers only
Block: Everything else
3. Use Specific IP Ranges
Bad:
1
Source: 0.0.0.0/0 (entire internet)
Good:
1
2
Source: 10.0.1.0/24 (specific subnet)
Source: YOUR_OFFICE_IP/32 (your office IP only)
4. Reference Other Security Groups
Instead of IP addresses, reference other security groups:
1
2
3
4
5
6
# Allow app servers to access database
aws ec2 authorize-security-group-ingress \
--group-id sg-database \
--protocol tcp \
--port 3306 \
--source-group sg-app-servers
This is more secure and flexible than IP addresses!
5. Regular Audits
Run security group audits regularly:
1
2
3
# Use AWS Config to monitor security groups
aws configservice put-config-rule \
--config-rule file://sg-audit-rule.json
Common Mistakes
Mistake 1: Opening SSH to Internet
Don’t do this:
1
SSH (22) from 0.0.0.0/0
Do this instead:
1
2
SSH (22) from YOUR_IP/32
# Or use AWS Systems Manager Session Manager (no SSH needed!)
Mistake 2: Opening Database Ports to Internet
Don’t do this:
1
MySQL (3306) from 0.0.0.0/0
Do this instead:
1
2
MySQL (3306) from 10.0.1.0/24 (app servers only)
# Or use RDS with proper security groups
Mistake 3: Not Restricting Outbound Traffic
Don’t do this:
1
Allow all outbound (default)
Do this instead:
1
2
3
Allow only what's needed:
- HTTPS (443) for AWS APIs
- Specific ports for specific services
Key Takeaways
- Security Groups = Virtual Firewall - Control traffic to/from instances
- Default Deny - Everything blocked unless explicitly allowed
- Stateful - Return traffic automatically allowed
- Least Privilege - Only allow what’s needed
- Restrict Outbound - Don’t allow all outbound traffic
- Use Specific IPs - Not 0.0.0.0/0 when possible
- Reference Other SGs - More secure than IP addresses
- Regular Audits - Check for risky configurations
Practice Exercise
Try this yourself:
- Create a security group for a web server
- Add rules for HTTP, HTTPS, and SSH
- Restrict SSH to your IP only
- Audit your security groups for risks
- Create a security group that references another
Resources to Learn More
What’s Next?
Now that you understand Security Groups, you’re ready to:
- Learn about VPC networking (our next post!)
- Configure secure multi-tier architectures
- Build properly secured applications
Remember: Security Groups are your first line of defense. Configure them carefully!
💡 Pro Tip: Use AWS Systems Manager Session Manager instead of SSH when possible. It doesn’t require opening port 22, and all sessions are logged. Much more secure!
Ready to dive deeper into networking? Check out our next post on AWS VPC, where we’ll learn how to build secure network architectures!