Browse Source

Merge pull request #263 from mdr-engineering/feature/ftd_MSOCI-1795_CustomerSearchHead

Updates to Customer Searchhead + New WAFv2 submodule!
Frederick Damstra 4 years ago
parent
commit
bc229fb33d

+ 5 - 1
base/splunk_servers/customer_searchhead/certificate.tf

@@ -1,8 +1,12 @@
 #Certificate 
 resource "aws_acm_certificate" "cert" {
-  domain_name       = "${local.alb_name}.${var.dns_info["public"]["zone"]}"
+  domain_name       = "${local.dns_short_name}.${var.dns_info["public"]["zone"]}"
   validation_method = "DNS"
 
+  lifecycle {
+    create_before_destroy = true
+  }
+
   tags = merge(var.standard_tags, var.tags)
 }
 

+ 17 - 13
base/splunk_servers/customer_searchhead/elb.tf

@@ -1,17 +1,21 @@
 locals {
   # alb_clients access the SH
-  alb_clients = toset(concat(
-    var.cidr_map["vpc-access"], # VPN users
-    var.cidr_map["vpc-system-services"], # Salt master, etc
-    var.cidr_map["vpc-private-services"], # fm-shared search, qcompliance, phantom
-    var.trusted_ips,
-    var.splunk_customer_cidrs,
-  ))
+  #
+  # Old way: restricted
+  #alb_clients = toset(concat(
+  #  var.cidr_map["vpc-access"], # VPN users
+  #  var.cidr_map["vpc-system-services"], # Salt master, etc
+  #  var.cidr_map["vpc-private-services"], # fm-shared search, qcompliance, phantom
+  #  var.trusted_ips,
+  #  var.splunk_customer_cidrs,
+  #))
+  # New way: WAF protects us
+  alb_clients = [ "0.0.0.0/0" ]
 }
 
 resource "aws_lb" "searchhead-alb" {
-  name               = var.alb_name != "" ? "${local.alb_name}-alb" : "${var.prefix}-cust-sh"
-  internal           = true
+  name               = local.alb_name
+  internal           = false
   load_balancer_type = "application"
   # Not supported for NLB
   security_groups    = [aws_security_group.searchhead-alb-sg.id]
@@ -62,7 +66,7 @@ resource "aws_lb_listener" "searchhead-alb-listener-http" {
 #########################
 # Targets
 resource "aws_lb_target_group" "searchhead-alb-target-8000" {
-  name     = var.alb_name != "" ? "${local.alb_name}-customer-alb-target-8000" : "${var.prefix}-cust-alb-8000"
+  name     = "${local.alb_name}-8000"
   port     = 8000
   protocol = "HTTPS"
   target_type = "instance"
@@ -93,7 +97,7 @@ resource "aws_lb_target_group_attachment" "searchhead-alb-target-8000-instance"
 #########################
 # Security Group for ALB
 resource "aws_security_group" "searchhead-alb-sg" {
-  name = var.alb_name != "" ? "${local.alb_name}-customer-alb-sh" : "${var.prefix}-customer-sh-alb-sg"
+  name = "${local.alb_name}-customer-alb-sh"
   description = "Security Group for the Customer Searchhead ALB"
   vpc_id = var.vpc_id
   tags = merge(var.standard_tags, var.tags)
@@ -130,10 +134,10 @@ resource "aws_security_group_rule" "searchhead-alb-8000-out" {
 
 #########################
 # DNS Entry
-module "public_dns_record_hec_ack" {
+module "public_dns_record_cust-elb" {
   source = "../../../submodules/dns/public_ALIAS_record"
 
-  name = local.alb_name
+  name = local.dns_short_name
 
   target_dns_name = aws_lb.searchhead-alb.dns_name
   target_zone_id  = aws_lb.searchhead-alb.zone_id

+ 2 - 1
base/splunk_servers/customer_searchhead/main.tf

@@ -2,7 +2,8 @@
 locals {
   ami_selection = "minion" # master, minion, ...
   instance_name = var.instance_name != "" ? var.instance_name : "${ var.prefix }-splunk-cust-sh"
-  alb_name = var.alb_name != "" ? var.alb_name : "${ var.prefix }-splunk"
+  alb_name = "${ var.prefix }-splunk-cust-sh"
+  dns_short_name = "search.${ var.prefix }"
 }
 
 # Rather than pass in the aws security group, we just look it up. This will

+ 4 - 0
base/splunk_servers/customer_searchhead/outputs.tf

@@ -1,3 +1,7 @@
+output fqdn {
+  value = module.public_dns_record_cust-elb.forward
+}
+
 output instance_arn {
   value = aws_instance.instance.arn
 }

+ 1 - 6
base/splunk_servers/customer_searchhead/vars.tf

@@ -9,12 +9,6 @@ variable "instance_name" {
   default = ""
 }
 
-variable "alb_name" {
-  description = "[Optional] Override the ALB Name"
-  type = string
-  default = ""
-}
-
 variable "prefix" {
   description = "Prefix for Instance Names"
   type = string
@@ -70,3 +64,4 @@ variable "aws_partition_alias" { type = string }
 variable "aws_account_id" { type = string }
 variable "common_services_account" { type = string }
 variable "instance_termination_protection" { type = bool }
+variable "splunk_prefix" { type = string }

+ 11 - 80
base/splunk_servers/customer_searchhead/waf.tf

@@ -1,84 +1,15 @@
-# trussworks/wafv2/aws has a basic WAF with the AWS Managed Ruleset
-# See https://registry.terraform.io/modules/trussworks/wafv2/aws/latest
-#
-# Attempted to add some sane defaults so we can customize as needed
-resource "aws_wafv2_ip_set" "ipset" {
-  name = "blocked_ips"
+module "waf" {
+  source = "../../../submodules/wafv2"
 
-  scope              = "REGIONAL"
-  ip_address_version = "IPV4"
-
-  addresses = [
-  ]
-}
-
-module "wafv2" {
-  source = "trussworks/wafv2/aws"
-  version = "~> 2.0"
-
-  name   = local.alb_name
-  scope = "REGIONAL"
-
-  alb_arn       = aws_lb.searchhead-alb.arn
-  associate_alb = true
-
-  ip_sets_rule = [
-    {
-      name       = "blocked_ips"
-      action     = "block"
-      priority   = 1
-      ip_set_arn = aws_wafv2_ip_set.ipset.arn
-    }
-  ]
-
-  # A rate-based rule tracks the rate of requests for each originating IP address, and triggers the rule action when the rate exceeds a limit that you specify on the number of requests in any 5-minute time span
-  ip_rate_based_rule = {
-    name     = "Rate_Limit"
-    priority = 5
-    limit    = 900 # 900 requests per 5 minutes= 3 requests/second (sustained for 5 minutes)
-    action   = "block"
-  }
+  # Custom to resource
+  allowed_ips = [ ] # bypasses filters, so should not be needed/used unless warranted
+  additional_blocked_ips = [ ] # NOTE: There is a standard list in the submodule
+  resource_arn = aws_lb.searchhead-alb.arn
+  fqdns = keys(module.public_dns_record_cust-elb.forward) # first entry in list will be the WAF name
 
+  # These are passed through and should be the same for module
   tags = merge(var.standard_tags, var.tags)
+  aws_partition = var.aws_partition
+  aws_region = var.aws_region
+  aws_account_id = var.aws_account_id
 }
-
-resource "aws_wafv2_web_acl_logging_configuration" "waf_logs" {
-  log_destination_configs = [ "arn:${var.aws_partition}:firehose:${var.aws_region}:${var.aws_account_id}:deliverystream/aws-waf-logs-splunk" ]
-  resource_arn            = module.wafv2.web_acl_id
-
-#  logging_filter {
-#    default_behavior = "KEEP"
-#
-#    filter {
-#      behavior = "DROP"
-#
-#      condition {
-#        action_condition {
-#          action = "COUNT"
-#        }
-#      }
-#
-#      condition {
-#        label_name_condition {
-#          label_name = "awswaf:111122223333:rulegroup:testRules:LabelNameZ"
-#        }
-#      }
-#
-#      requirement = "MEETS_ALL"
-#    }
-#
-#    filter {
-#      behavior = "KEEP"
-#
-#      condition {
-#        action_condition {
-#          action = "ALLOW"
-#        }
-#      }
-#
-#      requirement = "MEETS_ANY"
-#    }
-#  }
-}
-
-

+ 8 - 0
submodules/wafv2/blocklist.tf

@@ -0,0 +1,8 @@
+locals {
+  blocked_ips = [
+    "172.16.0.0/12",
+    "192.168.0.0/16",
+    "169.254.0.0/16",
+    "127.0.0.0/8"
+  ]
+}

+ 9 - 0
submodules/wafv2/vars.tf

@@ -0,0 +1,9 @@
+variable "allowed_ips" { type = list(string) }
+variable "additional_blocked_ips" { type = list(string) }
+variable "resource_arn" { type = string }
+variable "fqdns" { type = list(string) }
+
+variable "tags" { type = map }
+variable "aws_partition" { type = string }
+variable "aws_region" { type = string }
+variable "aws_account_id" { type = string }

+ 208 - 0
submodules/wafv2/waf.tf

@@ -0,0 +1,208 @@
+# trussworks/wafv2/aws has a basic WAF with the AWS Managed Ruleset
+# See https://registry.terraform.io/modules/trussworks/wafv2/aws/latest
+#
+# Attempted to add some sane defaults so we can customize as needed
+#
+# IMPORTANT NOTES:
+#   An 'Allow' action stops processing immediately. Avoid them!
+
+# Goals:
+#  - US IPs only  -  https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-type-geo-match.html
+
+resource "aws_wafv2_ip_set" "blocked" {
+  name = "blocked_ips"
+
+  scope              = "REGIONAL"
+  ip_address_version = "IPV4"
+
+  addresses = toset(concat(var.additional_blocked_ips, local.blocked_ips))
+}
+
+resource "aws_wafv2_ip_set" "allowed" {
+  name = "allowed_ips"
+
+  scope              = "REGIONAL"
+  ip_address_version = "IPV4"
+
+  addresses = var.allowed_ips
+}
+
+resource "aws_wafv2_rule_group" "xdr_custom_rules" {
+  name = "xdr_custom_rules"
+  scope    = "REGIONAL"
+  capacity = 1
+
+  # Note, there is visibilty config for the group and for the rule
+  visibility_config {
+    cloudwatch_metrics_enabled = true
+    metric_name                = "xdr_custom_rules"
+    sampled_requests_enabled   = true
+  }
+
+  rule {
+    name = "Block_Nonpermitted_Countries"
+    priority = 100
+
+    action {
+      block {}
+    }
+
+    statement {
+      not_statement {
+        statement {
+          geo_match_statement {
+            country_codes = [
+              "US",
+              "DE",
+            ]
+          }
+        }
+      }
+    }
+
+    visibility_config {
+      cloudwatch_metrics_enabled = true
+      metric_name                = "Block_Nonpermitted_Countries"
+      sampled_requests_enabled   = true
+    }
+  }
+
+  # Add additional custom rules here
+}
+
+module "wafv2" {
+  source = "trussworks/wafv2/aws"
+  version = "= 2.4.0"
+
+  name   = replace(var.fqdns[0], ".", "_")
+  scope = "REGIONAL"
+
+  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
+
+  filtered_header_rule = {
+    header_types = var.fqdns
+    header_value = "host"
+    priority     = 900
+    action       = "allow"
+  }
+
+  # IP based rules are processed first. 
+  # If an IP is blocked, it is blocked no matter what.
+  # If an IP is allowed, it is allowed only if it's not blocked, but no other WAF rules are processed.
+  ip_sets_rule = [
+    {
+      name       = "blocked_ips"
+      action     = "block"
+      priority   = 10
+      ip_set_arn = aws_wafv2_ip_set.blocked.arn
+    },
+    {
+      name       = "allowed_ips"
+      action     = "allow"
+      priority   = 20
+      ip_set_arn = aws_wafv2_ip_set.allowed.arn
+    }
+  ]
+
+  # Custom Rules are defined above
+  group_rules = [
+    {
+      name            = aws_wafv2_rule_group.xdr_custom_rules.name
+      arn             = aws_wafv2_rule_group.xdr_custom_rules.arn
+      priority        = 100
+      override_action = "none"
+      excluded_rules  = []
+    }
+  ]
+
+  # A rate-based rule tracks the rate of requests for each originating IP address, and triggers the rule action when the rate exceeds a limit that you specify on the number of requests in any 5-minute time span
+  ip_rate_based_rule = {
+    name     = "Rate_Limit"
+    priority = 200
+    limit    = 900 # 900 requests per 5 minutes= 3 requests/second (sustained for 5 minutes)
+    action   = "block"
+  }
+
+  # AWS managed rulesets
+  # Baseline was from trussworks/wafv2/aws, but copied here to be customized for our use and renumbered.
+  managed_rules =  [
+    {
+      "excluded_rules": [],
+      "name": "AWSManagedRulesCommonRuleSet",
+      "override_action": "none",
+      "priority": 510
+    },
+    {
+      "excluded_rules": [],
+      "name": "AWSManagedRulesAmazonIpReputationList",
+      "override_action": "none",
+      "priority": 520
+    },
+    {
+      "excluded_rules": [],
+      "name": "AWSManagedRulesKnownBadInputsRuleSet",
+      "override_action": "none",
+      "priority": 530
+    },
+    {
+      "excluded_rules": [],
+      "name": "AWSManagedRulesSQLiRuleSet",
+      "override_action": "none",
+      "priority": 540
+    },
+    {
+      "excluded_rules": [],
+      "name": "AWSManagedRulesLinuxRuleSet",
+      "override_action": "none",
+      "priority": 550
+    },
+    {
+      "excluded_rules": [],
+      "name": "AWSManagedRulesUnixRuleSet",
+      "override_action": "none",
+      "priority": 560
+    }
+  ]
+  tags = var.tags
+}
+
+resource "aws_wafv2_web_acl_logging_configuration" "waf_logs" {
+  log_destination_configs = [ "arn:${var.aws_partition}:firehose:${var.aws_region}:${var.aws_account_id}:deliverystream/aws-waf-logs-splunk" ]
+  resource_arn            = module.wafv2.web_acl_id
+
+#  logging_filter {
+#    default_behavior = "KEEP"
+#
+#    filter {
+#      behavior = "DROP"
+#
+#      condition {
+#        action_condition {
+#          action = "COUNT"
+#        }
+#      }
+#
+#      condition {
+#        label_name_condition {
+#          label_name = "awswaf:111122223333:rulegroup:testRules:LabelNameZ"
+#        }
+#      }
+#
+#      requirement = "MEETS_ALL"
+#    }
+#
+#    filter {
+#      behavior = "KEEP"
+#
+#      condition {
+#        action_condition {
+#          action = "ALLOW"
+#        }
+#      }
+#
+#      requirement = "MEETS_ANY"
+#    }
+#  }
+}