Skip to content

Commit

Permalink
Add challenge for browser-based requests
Browse files Browse the repository at this point in the history
  • Loading branch information
mbklein committed Jan 4, 2024
1 parent b5109c2 commit 034196a
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 114 deletions.
5 changes: 3 additions & 2 deletions firewall/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ This terraform project includes the resources required to set up the WAF ACL(s)

## Secrets

* `allowed_user_agents` – The list of user agents to allow through without bot control
* `firewall_type` – The type of firewall to create (`IP` for NUL IPs, `SECURITY` for managed security rulesets)
* `nul_ips` – A list of IP ranges representing the NUL staff offices and VPN
* `rdc_home_ips` – A list of IP addresses representing home offices of NUL RDC staffers for convenience
* `global_rate_limit` – Rate limit (# requests per 5 minutes) for clients not caught by any other rule
* `high_traffic_ips` – Known high-traffic IPs to block
* `resources` – A map of indicating the resources to be protected
* Example: `{ name = "my-app", arn = "arn:aws:elasticloadbalancing:..." }`

Expand Down
13 changes: 2 additions & 11 deletions firewall/ip_address_sets.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ resource "aws_wafv2_ip_set" "nul_ip_set" {
description = "NU Library IPv4 Addresses"
scope = "REGIONAL"
ip_address_version = "IPV4"
addresses = var.nul_ips
addresses = local.nul_ips
tags = local.tags
}

Expand All @@ -12,16 +12,7 @@ resource "aws_wafv2_ip_set" "nul_ipv6_set" {
description = "NU Library IPv6 Addresses"
scope = "REGIONAL"
ip_address_version = "IPV6"
addresses = var.nul_ips_v6
tags = local.tags
}

resource "aws_wafv2_ip_set" "rdc_home_ip_set" {
name = "rdc-home-ips"
description = "Home IP Addresses of RDC Users"
scope = "REGIONAL"
ip_address_version = "IPV4"
addresses = var.rdc_home_ips
addresses = local.nul_ips_v6
tags = local.tags
}

Expand Down
24 changes: 2 additions & 22 deletions firewall/ip_firewall.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
resource "aws_wafv2_web_acl" "ip_firewall" {
count = var.firewall_type == "IP" ? 1 : 0
count = local.ip_firewall ? 1 : 0
name = "staging-ip-acl"
description = "Protect staging resources using IP restrictions"
scope = "REGIONAL"
Expand Down Expand Up @@ -50,26 +50,6 @@ resource "aws_wafv2_web_acl" "ip_firewall" {
sampled_requests_enabled = true
}
}
rule {
name = "allow-rdc-home-ips"
priority = 3

action {
allow {}
}

statement {
ip_set_reference_statement {
arn = aws_wafv2_ip_set.rdc_home_ip_set.arn
}
}

visibility_config {
cloudwatch_metrics_enabled = false
metric_name = "Allow_RDC_Home_IPs"
sampled_requests_enabled = true
}
}

visibility_config {
cloudwatch_metrics_enabled = false
Expand All @@ -79,7 +59,7 @@ resource "aws_wafv2_web_acl" "ip_firewall" {
}

resource "aws_wafv2_web_acl_association" "ip_firewall" {
for_each = var.firewall_type == "IP" ? var.resources : {}
for_each = local.ip_firewall ? var.resources : {}
resource_arn = each.value
web_acl_arn = aws_wafv2_web_acl.ip_firewall[0].arn
}
5 changes: 4 additions & 1 deletion firewall/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ module "core" {
}

locals {
namespace = module.core.outputs.stack.namespace
namespace = module.core.outputs.stack.namespace
ip_firewall = var.firewall_type == "IP"
security_firewall = var.firewall_type == "SECURITY"

tags = merge(
module.core.outputs.stack.tags,
{
Expand Down
20 changes: 20 additions & 0 deletions firewall/nul_ips.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
locals {
nul_ips = [
"129.105.184.0/24",
"129.105.19.0/24",
"129.105.203.0/24",
"129.105.29.0/24",
"165.124.126.38/32",
"165.124.144.0/23",
"165.124.160.0/21",
"165.124.199.32/29",
"165.124.200.24/29",
"165.124.201.96/28",
"165.124.202.0/24"
]
nul_ips_v6 = [
"2620:10d:2000:3000:0:0224::/96",
"2620:10d:2000:3000:0:0078::/96",
"2620:10d:2000:3000:0:0077::/96"
]
}
136 changes: 73 additions & 63 deletions firewall/security_firewall.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
locals {
count_only = var.firewall_type != "SECURITY"
excluded_rules = {
AWSManagedRulesCommonRuleSet = ["CrossSiteScripting_BODY", "GenericRFI_BODY", "SizeRestrictions_BODY"]
AWSManagedRulesKnownBadInputsRuleSet = []
Expand All @@ -14,6 +13,7 @@ resource "aws_cloudwatch_log_group" "security_firewall_log" {
}

resource "aws_wafv2_web_acl" "security_firewall" {
count = local.security_firewall ? 1 : 0
name = "${local.namespace}-load-balancer-firewall"
scope = "REGIONAL"
tags = local.tags
Expand All @@ -32,18 +32,23 @@ resource "aws_wafv2_web_acl" "security_firewall" {
name = "${local.namespace}-allow-nul-ips"
priority = 0

rule_label {
name = "nul:internal-ip:v4"
}

action {
allow {}
count {}
}


statement {
ip_set_reference_statement {
arn = aws_wafv2_ip_set.nul_ip_set.arn
}
}

visibility_config {
cloudwatch_metrics_enabled = false
cloudwatch_metrics_enabled = true
metric_name = "${local.namespace}-allow-nul-ips"
sampled_requests_enabled = true
}
Expand All @@ -53,8 +58,12 @@ resource "aws_wafv2_web_acl" "security_firewall" {
name = "${local.namespace}-${local.namespace}-allow-nul-ips-v6"
priority = 1

rule_label {
name = "nul:internal-ip:v6"
}

action {
allow {}
count {}
}

statement {
Expand All @@ -64,7 +73,7 @@ resource "aws_wafv2_web_acl" "security_firewall" {
}

visibility_config {
cloudwatch_metrics_enabled = false
cloudwatch_metrics_enabled = true
metric_name = "${local.namespace}-allow-nul-ips"
sampled_requests_enabled = true
}
Expand Down Expand Up @@ -143,7 +152,6 @@ resource "aws_wafv2_web_acl" "security_firewall" {
search_string = "/api/"

field_to_match {

uri_path {}
}

Expand All @@ -164,19 +172,11 @@ resource "aws_wafv2_web_acl" "security_firewall" {
}

rule {
name = "AmazonIPReputationList"
name = "${local.namespace}-aws-managed-ip-reputation-list"
priority = 4

override_action {
dynamic "none" {
for_each = toset(local.count_only ? [] : [1])
content {}
}

dynamic "count" {
for_each = toset(local.count_only ? [1] : [])
content {}
}
none {}
}

statement {
Expand All @@ -194,19 +194,11 @@ resource "aws_wafv2_web_acl" "security_firewall" {
}

rule {
name = "AWSManagedRulesBotControlRuleSet"
name = "${local.namespace}-aws-managed-bot-control"
priority = 5

override_action {
dynamic "none" {
for_each = toset(local.count_only ? [] : [1])
content {}
}

dynamic "count" {
for_each = toset(local.count_only ? [1] : [])
content {}
}
none {}
}

statement {
Expand Down Expand Up @@ -240,15 +232,7 @@ resource "aws_wafv2_web_acl" "security_firewall" {
priority = 6

action {
dynamic "block" {
for_each = toset(local.count_only ? [] : [1])
content {}
}

dynamic "count" {
for_each = toset(local.count_only ? [1] : [])
content {}
}
block {}
}

statement {
Expand All @@ -264,11 +248,45 @@ resource "aws_wafv2_web_acl" "security_firewall" {
}
}

# Block requests from a single IP exceeding 750 requests per 5 minute period
# Challenge browsers that exceed the rate limit
rule {
name = "${local.namespace}-rate-limiter"
name = "${local.namespace}-browser-rate-limiter"
priority = 7

action {
challenge {}
}

statement {
rate_based_statement {
aggregate_key_type = "IP"
limit = var.global_rate_limit

scope_down_statement {
not_statement {
statement {
label_match_statement {
scope = "LABEL"
key = "awswaf:managed:aws:bot-control:bot:category:http_library"
}
}
}
}
}
}

visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "${local.namespace}-rate-limiter"
sampled_requests_enabled = true
}
}

# Rate limit (HTTP status 429) HTTP client libraries that exceed the rate limit
rule {
name = "${local.namespace}-http-client-rate-limiter"
priority = 8

action {
block {
custom_response {
Expand All @@ -281,7 +299,14 @@ resource "aws_wafv2_web_acl" "security_firewall" {
statement {
rate_based_statement {
aggregate_key_type = "IP"
limit = 300
limit = var.global_rate_limit

scope_down_statement {
label_match_statement {
scope = "LABEL"
key = "awswaf:managed:aws:bot-control:bot:category:http_library"
}
}
}
}

Expand All @@ -293,19 +318,11 @@ resource "aws_wafv2_web_acl" "security_firewall" {
}

rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 8
name = "${local.namespace}-aws-managed-managed-common"
priority = 9

override_action {
dynamic "none" {
for_each = toset(local.count_only ? [] : [1])
content {}
}

dynamic "count" {
for_each = toset(local.count_only ? [1] : [])
content {}
}
none {}
}

statement {
Expand Down Expand Up @@ -335,19 +352,11 @@ resource "aws_wafv2_web_acl" "security_firewall" {
}

rule {
name = "AWSManagedRulesKnownBadInputsRuleSet"
priority = 9
name = "${local.namespace}-aws-managed-known-bad-inputs"
priority = 10

override_action {
dynamic "none" {
for_each = toset(local.count_only ? [] : [1])
content {}
}

dynamic "count" {
for_each = toset(local.count_only ? [1] : [])
content {}
}
none {}
}

statement {
Expand Down Expand Up @@ -380,8 +389,9 @@ resource "aws_wafv2_web_acl" "security_firewall" {
}

resource "aws_wafv2_web_acl_logging_configuration" "security_firewall" {
count = local.security_firewall ? 1 : 0
log_destination_configs = [aws_cloudwatch_log_group.security_firewall_log.arn]
resource_arn = aws_wafv2_web_acl.security_firewall.arn
resource_arn = aws_wafv2_web_acl.security_firewall[0].arn

logging_filter {
default_behavior = "KEEP"
Expand All @@ -400,7 +410,7 @@ resource "aws_wafv2_web_acl_logging_configuration" "security_firewall" {
}

resource "aws_wafv2_web_acl_association" "security_firewall" {
for_each = var.firewall_type == "SECURITY" ? var.resources : {}
for_each = local.security_firewall ? var.resources : {}
resource_arn = each.value
web_acl_arn = aws_wafv2_web_acl.security_firewall.arn
web_acl_arn = aws_wafv2_web_acl.security_firewall[0].arn
}
Loading

2 comments on commit 034196a

@mstork
Copy link

@mstork mstork commented on 034196a Jan 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove the 5 IP ranges listed below from nul_ips. They are no longer in use, and I recommend removing them.

"165.124.160.0/21" # Legacy "regular" VPN IP range
"165.124.126.38/32" # Legacy SSL VPN Website Exit IP
"165.124.199.32/29" # Legacy SSL VPN
"165.124.200.24/29" # Legacy SSL VPN
"165.124.201.96/28" # Legacy SSL VPN

You do not have the Mudd staff IP range of "129.105.121.128/25" and the NU Cloud IP ranges of 129.105.238.192/26, 129.105.20.0/25, 129.105.20.128/26, 129.105.20.192/27. I do not know if they are needed here, only listing them out in the event you do.

The exit IPs for all VPN traffic are 165.124.167.1 to 165.124.167.4. There is no differentiation between students, staff or like Library staff. Same with the above IP ranges, not sure if you need to exclude them from the rate limiting or not.

@mbklein
Copy link
Contributor Author

@mbklein mbklein commented on 034196a Jan 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @mstork!

Please sign in to comment.