Browse Source

Updates GitHub and WAF Module for GitHub Support

Lots of customization added ot the waf module, at hte cost of great
complexity.

* Can disable entire rulesets
* Can add SANs

To be tagged v4.2.6
Fred Damstra [afs macbook] 3 years ago
parent
commit
3d8829c8b9

+ 24 - 1
base/github/elb.tf

@@ -22,13 +22,36 @@ module "elb" {
   redirect_80               = false # GitHub handles port 80, and needs it for LetsEncrypt
 
   # WAF variables
-  waf_enabled = false # TODO: Turn this on
+  waf_enabled = true # TODO: Turn this on
+  fqdns       = local.hostnames
+  # Set WAF to 'count' for now
+  block_settings = {
+    "default"                               = true # Default action. False = count
+    "custom"                                = true # XDR Custom Rules. False = count
+    "admin"                                 = true # Block admin pages.
+    "AWSManagedRulesCommonRuleSet"          = false
+    "AWSManagedRulesAmazonIpReputationList" = false
+    "AWSManagedRulesKnownBadInputsRuleSet"  = false
+    "AWSManagedRulesSQLiRuleSet"            = false # Irrelevant, module is disabled
+    "AWSManagedRulesLinuxRuleSet"           = false # Irrelevant, module is disabled
+    "AWSManagedRulesUnixRuleSet"            = false # Irrelevant, module is disabled
+  }
+
   #excluded_rules_AWSManagedRulesCommonRuleSet = [ "SizeRestrictions_BODY" ]
   #excluded_rules_AWSManagedRulesAmazonIpReputationList = []
   #excluded_rules_AWSManagedRulesKnownBadInputsRuleSet = []
   #excluded_rules_AWSManagedRulesSQLiRuleSet = []
   #excluded_rules_AWSManagedRulesLinuxRuleSet = []
   #excluded_rules_AWSManagedRulesUnixRuleSet = []
+
+  # Excluded Rulesets
+  # There are too many hostnames, so we have to disable some
+  excluded_set_AWSManagedRulesCommonRuleSet          = false
+  excluded_set_AWSManagedRulesAmazonIpReputationList = false
+  excluded_set_AWSManagedRulesKnownBadInputsRuleSet  = false
+  excluded_set_AWSManagedRulesSQLiRuleSet            = true
+  excluded_set_AWSManagedRulesLinuxRuleSet           = true
+  excluded_set_AWSManagedRulesUnixRuleSet            = true
   #additional_blocked_ips = []
   #allowed_ips = []
   #admin_ips = []

+ 22 - 0
base/github/hostnames.tf

@@ -0,0 +1,22 @@
+locals {
+  # For list, see https://docs.github.com/en/enterprise-server@3.1/admin/configuration/configuring-network-settings/enabling-subdomain-isolation
+  # Can't allow wildcards through our WAF module
+  hostnames = [
+    "github.${var.dns_info["public"]["zone"]}",
+    "assets.github.${var.dns_info["public"]["zone"]}",
+    "avatars.github.${var.dns_info["public"]["zone"]}",
+    "codeload.github.${var.dns_info["public"]["zone"]}",
+    "gist.github.${var.dns_info["public"]["zone"]}",
+    "media.github.${var.dns_info["public"]["zone"]}",
+    "pages.github.${var.dns_info["public"]["zone"]}",
+    "raw.github.${var.dns_info["public"]["zone"]}",
+    "render.github.${var.dns_info["public"]["zone"]}",
+    "reply.github.${var.dns_info["public"]["zone"]}",
+    "uploads.github.${var.dns_info["public"]["zone"]}",
+    "docker.github.${var.dns_info["public"]["zone"]}",
+    "npm.github.${var.dns_info["public"]["zone"]}",
+    "rubygems.github.${var.dns_info["public"]["zone"]}",
+    "maven.github.${var.dns_info["public"]["zone"]}",
+    "nuget.github.${var.dns_info["public"]["zone"]}",
+  ]
+}

+ 55 - 0
submodules/load_balancer/static_nlb_to_alb/vars.tf

@@ -88,6 +88,13 @@ variable "waf_enabled" {
   description = "Enable the standard WAF?"
 }
 
+variable "fqdns" {
+  description = "List of FQDNs to allow through the WAF"
+  type        = list(string)
+  default     = [] # Default will allow nothing through, so only valid if waf_enabled is false
+}
+
+## Excluded Rules
 variable "excluded_rules_AWSManagedRulesCommonRuleSet" {
   type = list(string)
   default = [
@@ -120,6 +127,54 @@ variable "excluded_rules_AWSManagedRulesUnixRuleSet" {
   default = []
 }
 
+## Exclude Entire Sets
+variable "excluded_set_AWSManagedRulesCommonRuleSet" {
+  type    = bool
+  default = null
+}
+
+variable "excluded_set_AWSManagedRulesAmazonIpReputationList" {
+  type    = bool
+  default = null
+}
+
+variable "excluded_set_AWSManagedRulesKnownBadInputsRuleSet" {
+  type    = bool
+  default = null
+}
+
+variable "excluded_set_AWSManagedRulesSQLiRuleSet" {
+  type    = bool
+  default = null
+}
+
+variable "excluded_set_AWSManagedRulesLinuxRuleSet" {
+  type    = bool
+  default = null
+}
+
+variable "excluded_set_AWSManagedRulesUnixRuleSet" {
+  type    = bool
+  default = null
+}
+
+variable "block_settings" {
+  type = object(
+    {
+      default                               = bool, # Default action. False = count
+      custom                                = bool, # XDR Custom Rules. False = count
+      admin                                 = bool, # Block /admin access to admin IPs
+      AWSManagedRulesCommonRuleSet          = bool,
+      AWSManagedRulesAmazonIpReputationList = bool,
+      AWSManagedRulesKnownBadInputsRuleSet  = bool,
+      AWSManagedRulesSQLiRuleSet            = bool,
+      AWSManagedRulesLinuxRuleSet           = bool,
+      AWSManagedRulesUnixRuleSet            = bool,
+    }
+  )
+  default = null
+}
+
 variable "additional_blocked_ips" {
   description = "IP addresses that are blocked, in addition to the defaults."
   type        = list(string)

+ 18 - 2
submodules/load_balancer/static_nlb_to_alb/waf.tf

@@ -1,3 +1,8 @@
+locals {
+  fqdns_all = concat(module.public_dns_record.forward, var.subject_alternative_names, var.fqdns)
+  fqdns     = [for fqdn in local.fqdns_all : fqdn if substr(fqdn, 0, 1) != "*"]
+}
+
 module "waf" {
   count = var.waf_enabled ? 1 : 0
 
@@ -9,9 +14,9 @@ module "waf" {
   admin_ips              = var.admin_ips #concat(var.zscalar_ips, var.admin_ips)
 
   resource_arn = aws_lb.external.arn
-  fqdns        = concat(module.public_dns_record.forward, var.subject_alternative_names) # first entry in list will be the WAF name
+  fqdns        = local.fqdns
 
-  # Passthrough
+  # Passthrough Excluded Rules
   excluded_rules_AWSManagedRulesCommonRuleSet          = var.excluded_rules_AWSManagedRulesCommonRuleSet
   excluded_rules_AWSManagedRulesAmazonIpReputationList = var.excluded_rules_AWSManagedRulesAmazonIpReputationList
   excluded_rules_AWSManagedRulesKnownBadInputsRuleSet  = var.excluded_rules_AWSManagedRulesKnownBadInputsRuleSet
@@ -19,6 +24,17 @@ module "waf" {
   excluded_rules_AWSManagedRulesLinuxRuleSet           = var.excluded_rules_AWSManagedRulesLinuxRuleSet
   excluded_rules_AWSManagedRulesUnixRuleSet            = var.excluded_rules_AWSManagedRulesUnixRuleSet
 
+  # Passthrough Excluded Rule Sets
+  excluded_set_AWSManagedRulesCommonRuleSet          = var.excluded_set_AWSManagedRulesCommonRuleSet
+  excluded_set_AWSManagedRulesAmazonIpReputationList = var.excluded_set_AWSManagedRulesAmazonIpReputationList
+  excluded_set_AWSManagedRulesKnownBadInputsRuleSet  = var.excluded_set_AWSManagedRulesKnownBadInputsRuleSet
+  excluded_set_AWSManagedRulesSQLiRuleSet            = var.excluded_set_AWSManagedRulesSQLiRuleSet
+  excluded_set_AWSManagedRulesLinuxRuleSet           = var.excluded_set_AWSManagedRulesLinuxRuleSet
+  excluded_set_AWSManagedRulesUnixRuleSet            = var.excluded_set_AWSManagedRulesUnixRuleSet
+
+
+  block_settings = var.block_settings
+
   # These are passed through and should be the same for module
   aws_partition  = var.aws_partition
   aws_region     = var.aws_region

+ 87 - 10
submodules/wafv2/vars.tf

@@ -1,49 +1,126 @@
+variable "block_settings" {
+  description = "Can change rules to 'count' in order to test before deployment"
+  type = object(
+    {
+      default                               = bool, # Default action. False = count
+      custom                                = bool, # XDR Custom Rules. False = count
+      admin                                 = bool, # /admin folder
+      AWSManagedRulesCommonRuleSet          = bool,
+      AWSManagedRulesAmazonIpReputationList = bool,
+      AWSManagedRulesKnownBadInputsRuleSet  = bool,
+      AWSManagedRulesSQLiRuleSet            = bool,
+      AWSManagedRulesLinuxRuleSet           = bool,
+      AWSManagedRulesUnixRuleSet            = bool,
+    }
+  )
+
+  default = {
+    "default"                               = true # Default action. False = count
+    "custom"                                = true # XDR Custom Rules. False = count
+    "admin"                                 = true
+    "AWSManagedRulesCommonRuleSet"          = true
+    "AWSManagedRulesAmazonIpReputationList" = true
+    "AWSManagedRulesKnownBadInputsRuleSet"  = true
+    "AWSManagedRulesSQLiRuleSet"            = true
+    "AWSManagedRulesLinuxRuleSet"           = true
+    "AWSManagedRulesUnixRuleSet"            = true
+  }
+  nullable = false # If passed in null, default value will be assigned
+}
+
+## Exclude Rules
 variable "excluded_rules_AWSManagedRulesCommonRuleSet" {
   type = list(string)
   default = [
     "SizeRestrictions_BODY" # Breaks too many things
   ]
+  nullable = false
 }
 
 variable "excluded_rules_AWSManagedRulesAmazonIpReputationList" {
-  type    = list(string)
-  default = []
+  type     = list(string)
+  default  = []
+  nullable = false
 }
 
 variable "excluded_rules_AWSManagedRulesKnownBadInputsRuleSet" {
-  type    = list(string)
-  default = []
+  type     = list(string)
+  default  = []
+  nullable = false
 }
 
 variable "excluded_rules_AWSManagedRulesSQLiRuleSet" {
-  type    = list(string)
-  default = []
+  type     = list(string)
+  default  = []
+  nullable = false
 }
 
 variable "excluded_rules_AWSManagedRulesLinuxRuleSet" {
-  type    = list(string)
-  default = []
+  type     = list(string)
+  default  = []
+  nullable = false
 }
 
 variable "excluded_rules_AWSManagedRulesUnixRuleSet" {
-  type    = list(string)
-  default = []
+  type     = list(string)
+  default  = []
+  nullable = false
+}
+
+## Exclude Entire Sets
+variable "excluded_set_AWSManagedRulesCommonRuleSet" {
+  type     = bool
+  default  = false
+  nullable = false
+}
+
+variable "excluded_set_AWSManagedRulesAmazonIpReputationList" {
+  type     = bool
+  default  = false
+  nullable = false
+}
+
+variable "excluded_set_AWSManagedRulesKnownBadInputsRuleSet" {
+  type     = bool
+  default  = false
+  nullable = false
+}
+
+variable "excluded_set_AWSManagedRulesSQLiRuleSet" {
+  type     = bool
+  default  = false
+  nullable = false
+}
+
+variable "excluded_set_AWSManagedRulesLinuxRuleSet" {
+  type     = bool
+  default  = false
+  nullable = false
+}
+
+variable "excluded_set_AWSManagedRulesUnixRuleSet" {
+  type     = bool
+  default  = false
+  nullable = false
 }
 
 variable "additional_blocked_ips" {
   description = "IP addresses that are blocked, in addition to the defaults."
   type        = list(string)
   default     = []
+  nullable    = false
 }
 variable "allowed_ips" {
   description = "IP Addresses that are always allowed"
   type        = list(string)
   default     = []
+  nullable    = false
 }
 variable "admin_ips" {
   description = "IP Addressed that are allowed to the admin interface"
   type        = list(string)
   default     = []
+  nullable    = false
 }
 variable "resource_arn" { type = string }
 variable "fqdns" { type = list(string) }

+ 65 - 43
submodules/wafv2/waf.tf

@@ -11,6 +11,46 @@
 
 locals {
   waf_name = replace(var.fqdns[0], ".", "_")
+
+  # A complicated building of managed rules. Each one builds on the previous.
+  managed_rules_0 = []
+  managed_rules_1 = var.excluded_set_AWSManagedRulesCommonRuleSet ? local.managed_rules_0 : concat(local.managed_rules_0, [{
+    "excluded_rules" : var.excluded_rules_AWSManagedRulesCommonRuleSet,
+    "name" : "AWSManagedRulesCommonRuleSet",
+    "override_action" : var.block_settings["AWSManagedRulesCommonRuleSet"] ? "none" : "count",
+    "priority" : 510
+  }])
+  managed_rules_2 = var.excluded_set_AWSManagedRulesAmazonIpReputationList ? local.managed_rules_1 : concat(local.managed_rules_1, [{
+    "excluded_rules" : var.excluded_rules_AWSManagedRulesAmazonIpReputationList,
+    "name" : "AWSManagedRulesAmazonIpReputationList",
+    "override_action" : var.block_settings["AWSManagedRulesAmazonIpReputationList"] ? "none" : "count",
+    "priority" : 520
+  }])
+  managed_rules_3 = var.excluded_set_AWSManagedRulesKnownBadInputsRuleSet ? local.managed_rules_2 : concat(local.managed_rules_2, [{
+    "excluded_rules" : var.excluded_rules_AWSManagedRulesKnownBadInputsRuleSet,
+    "name" : "AWSManagedRulesKnownBadInputsRuleSet",
+    "override_action" : var.block_settings["AWSManagedRulesKnownBadInputsRuleSet"] ? "none" : "count",
+    "priority" : 530
+  }])
+  managed_rules_4 = var.excluded_set_AWSManagedRulesSQLiRuleSet ? local.managed_rules_3 : concat(local.managed_rules_3, [{
+    "excluded_rules" : var.excluded_rules_AWSManagedRulesSQLiRuleSet,
+    "name" : "AWSManagedRulesSQLiRuleSet",
+    "override_action" : var.block_settings["AWSManagedRulesSQLiRuleSet"] ? "none" : "count",
+    "priority" : 540
+  }])
+  managed_rules_5 = var.excluded_set_AWSManagedRulesLinuxRuleSet ? local.managed_rules_4 : concat(local.managed_rules_4, [{
+    "excluded_rules" : var.excluded_rules_AWSManagedRulesLinuxRuleSet,
+    "name" : "AWSManagedRulesLinuxRuleSet",
+    "override_action" : var.block_settings["AWSManagedRulesLinuxRuleSet"] ? "none" : "count",
+    "priority" : 550
+  }])
+  managed_rules_6 = var.excluded_set_AWSManagedRulesUnixRuleSet ? local.managed_rules_5 : concat(local.managed_rules_5, [{
+    "excluded_rules" : var.excluded_rules_AWSManagedRulesUnixRuleSet,
+    "name" : "AWSManagedRulesUnixRuleSet",
+    "override_action" : var.block_settings["AWSManagedRulesUnixRuleSet"] ? "none" : "count",
+    "priority" : 560
+  }])
+  managed_rules = local.managed_rules_6
 }
 
 resource "aws_wafv2_ip_set" "blocked" {
@@ -70,7 +110,17 @@ resource "aws_wafv2_rule_group" "xdr_custom_rules" {
     priority = 100
 
     action {
-      block {}
+      # WAF rule's strange data format makes this a little complex,
+      # but the end result is that if block_settings["custom"] is
+      # set to true, it will block. Otherwise, it will count.
+      dynamic "block" {
+        for_each = var.block_settings["custom"] ? ["block"] : []
+        content {}
+      }
+      dynamic "count" {
+        for_each = var.block_settings["custom"] ? [] : ["count"]
+        content {}
+      }
     }
 
     statement {
@@ -102,7 +152,17 @@ resource "aws_wafv2_rule_group" "xdr_custom_rules" {
     priority = 110
 
     action {
-      block {}
+      # WAF rule's strange data format makes this a little complex,
+      # but the end result is that if block_settings["custom"] is
+      # set to true, it will block. Otherwise, it will count.
+      dynamic "block" {
+        for_each = var.block_settings["admin"] ? ["block"] : []
+        content {}
+      }
+      dynamic "count" {
+        for_each = var.block_settings["admin"] ? [] : ["count"]
+        content {}
+      }
     }
 
     statement {
@@ -137,7 +197,6 @@ resource "aws_wafv2_rule_group" "xdr_custom_rules" {
             }
           }
         }
-
       }
     }
 
@@ -291,7 +350,7 @@ module "wafv2" {
 
   alb_arn        = var.resource_arn
   associate_alb  = true
-  default_action = "block" # Note: The final action is actually to 'allow', provided the host header is correct
+  default_action = var.block_settings["default"] ? "block" : "allow" # Note: Even in block, the final action is actually to 'allow' if the host header is correct
 
   filtered_header_rule = {
     header_types = var.fqdns
@@ -324,7 +383,7 @@ module "wafv2" {
       name            = aws_wafv2_rule_group.xdr_custom_rules.name
       arn             = aws_wafv2_rule_group.xdr_custom_rules.arn
       priority        = 100
-      override_action = "none"
+      override_action = var.block_settings["custom"] ? "none" : "count"
       excluded_rules  = []
     }
   ]
@@ -340,44 +399,7 @@ module "wafv2" {
 
   # AWS managed rulesets
   # Baseline was from trussworks/wafv2/aws, but copied here to be customized for our use and renumbered.
-  managed_rules = [
-    {
-      "excluded_rules" : var.excluded_rules_AWSManagedRulesCommonRuleSet,
-      "name" : "AWSManagedRulesCommonRuleSet",
-      "override_action" : "none",
-      "priority" : 510
-    },
-    {
-      "excluded_rules" : var.excluded_rules_AWSManagedRulesAmazonIpReputationList,
-      "name" : "AWSManagedRulesAmazonIpReputationList",
-      "override_action" : "none",
-      "priority" : 520
-    },
-    {
-      "excluded_rules" : var.excluded_rules_AWSManagedRulesKnownBadInputsRuleSet,
-      "name" : "AWSManagedRulesKnownBadInputsRuleSet",
-      "override_action" : "none",
-      "priority" : 530
-    },
-    {
-      "excluded_rules" : var.excluded_rules_AWSManagedRulesSQLiRuleSet,
-      "name" : "AWSManagedRulesSQLiRuleSet",
-      "override_action" : "none",
-      "priority" : 540
-    },
-    {
-      "excluded_rules" : var.excluded_rules_AWSManagedRulesLinuxRuleSet,
-      "name" : "AWSManagedRulesLinuxRuleSet",
-      "override_action" : "none",
-      "priority" : 550
-    },
-    {
-      "excluded_rules" : var.excluded_rules_AWSManagedRulesUnixRuleSet,
-      "name" : "AWSManagedRulesUnixRuleSet",
-      "override_action" : "none",
-      "priority" : 560
-    }
-  ]
+  managed_rules = local.managed_rules
 
   depends_on = [aws_wafv2_rule_group.xdr_custom_rules]
   tags       = var.tags