How to Block a Single IP Address in AWS EC2 Security Groups While Allowing All Other Traffic


3 views

When working with AWS EC2 security groups, you'll notice they are stateful and only support allow rules by default. This means you can't explicitly create "deny" rules like in traditional firewalls. However, there are effective workarounds to block specific IP addresses.

Since security groups work on a whitelist model, we can implement an implicit deny by carefully crafting our allow rules. Here's how:

# Example security group rules in AWS CLI format
aws ec2 authorize-security-group-ingress \
    --group-id sg-1234567890 \
    --protocol tcp \
    --port 80 \
    --cidr 0.0.0.0/0

aws ec2 authorize-security-group-ingress \
    --group-id sg-1234567890 \
    --protocol tcp \
    --port 80 \
    --cidr 192.0.2.0/24 \
    --cidr 198.51.100.0/24

For more granular control, use Network ACLs which do support explicit deny rules:

# Create a network ACL rule to block a specific IP
aws ec2 create-network-acl-entry \
    --network-acl-id acl-12345678 \
    --rule-number 100 \
    --protocol 6 \
    --port-range From=80,To=80 \
    --cidr-block 203.0.113.5/32 \
    --rule-action deny \
    --ingress

Here's a Terraform example for managing this infrastructure-as-code:

resource "aws_security_group" "web" {
  name        = "web-sg"
  description = "Allow HTTP but block specific IP"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # Use separate security group for the IP you want to block
  # and don't attach it to any instances
}

resource "aws_network_acl" "main" {
  vpc_id = aws_vpc.main.id

  egress {
    protocol   = "tcp"
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 80
    to_port    = 80
  }

  ingress {
    protocol   = "tcp"
    rule_no    = 200
    action     = "deny"
    cidr_block = "203.0.113.5/32"
    from_port  = 80
    to_port    = 80
  }
}

Remember that security groups are evaluated before Network ACLs. The evaluation order is:

  1. Security Group rules (allow only)
  2. Network ACL rules (allow/deny)

AWS Security Groups operate as stateful firewalls that only support allow rules by design. This means you can't directly create "deny" rules like in traditional firewalls. The default behavior is implicit denial - anything not explicitly allowed is blocked.

To block a specific IP while maintaining open access, you'll need to:


1. Create a custom "allow all" rule for your port (e.g., 0.0.0.0/0 for port 80)
2. Add another rule with higher priority that explicitly denies the bad IP

Here's how to achieve this using AWS CLI:


# First, allow all traffic on port 80 (HTTP)
aws ec2 authorize-security-group-ingress \
    --group-id your-sg-id \
    --protocol tcp \
    --port 80 \
    --cidr 0.0.0.0/0

# Then create a higher priority rule that blocks the specific IP
aws ec2 authorize-security-group-ingress \
    --group-id your-sg-id \
    --ip-permissions 'IpProtocol=tcp,FromPort=80,ToPort=80,IpRanges=[{CidrIp=192.0.2.1/32,Description="Block bad bot"}]' \
    --tag-specifications 'ResourceType=security-group-rule,Tags=[{Key=Blocked,Value=True}]'

Remember that Security Group rules are evaluated in order, and the first matching rule is applied. AWS automatically orders rules by security (more specific CIDRs first), but you can't manually reorder them. For more complex scenarios, consider:

  • Using AWS WAF for HTTP/HTTPS traffic
  • Implementing Network ACLs for subnet-level blocking
  • Creating a Lambda function to automatically update rules

For web applications, AWS WAF provides more granular control:


{
    "Name": "BlockBadBot",
    "Priority": 1,
    "Action": {
        "Block": {}
    },
    "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "BlockBadBot"
    },
    "Statement": {
        "IPSetReferenceStatement": {
            "ARN": "arn:aws:wafv2:us-east-1:123456789012:regional/ipset/BlockedIPs"
        }
    }
}