waf.tf 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. # trussworks/wafv2/aws has a basic WAF with the AWS Managed Ruleset
  2. # See https://registry.terraform.io/modules/trussworks/wafv2/aws/latest
  3. #
  4. # Attempted to add some sane defaults so we can customize as needed
  5. #
  6. # IMPORTANT NOTES:
  7. # An 'Allow' action stops processing immediately. Avoid them!
  8. # Goals:
  9. # - US IPs only - https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-type-geo-match.html
  10. locals {
  11. waf_name = replace(var.fqdns[0], ".", "_")
  12. }
  13. resource "aws_wafv2_ip_set" "blocked" {
  14. name = "${local.waf_name}_blocked_ips"
  15. scope = "REGIONAL"
  16. ip_address_version = "IPV4"
  17. addresses = toset(concat(var.additional_blocked_ips, local.blocked_ips))
  18. lifecycle {
  19. create_before_destroy = true
  20. }
  21. }
  22. resource "aws_wafv2_ip_set" "allowed" {
  23. name = "${local.waf_name}_allowed_ips"
  24. scope = "REGIONAL"
  25. ip_address_version = "IPV4"
  26. addresses = var.allowed_ips
  27. lifecycle {
  28. create_before_destroy = true
  29. }
  30. }
  31. resource "aws_wafv2_rule_group" "xdr_custom_rules" {
  32. name = "${local.waf_name}_xdr_custom_rules_rev2" # update name when updating
  33. scope = "REGIONAL"
  34. capacity = 50
  35. # Note, there is visibilty config for the group and for the rule
  36. visibility_config {
  37. cloudwatch_metrics_enabled = true
  38. metric_name = "${local.waf_name}_xdr_custom_rules"
  39. #metric_name = "xdr_custom_rules"
  40. sampled_requests_enabled = true
  41. }
  42. rule {
  43. name = "Block_Nonpermitted_Countries"
  44. priority = 100
  45. action {
  46. block {}
  47. }
  48. statement {
  49. not_statement {
  50. statement {
  51. geo_match_statement {
  52. country_codes = [
  53. "US",
  54. "DE",
  55. ]
  56. }
  57. }
  58. }
  59. }
  60. visibility_config {
  61. cloudwatch_metrics_enabled = true
  62. metric_name = "Block_Nonpermitted_Countries"
  63. sampled_requests_enabled = true
  64. }
  65. # rule_label {
  66. # name = "xdr_custom:nonpermittedcountry"
  67. # }
  68. }
  69. rule {
  70. name = "Block_log4j_Exploit_20211210"
  71. action {
  72. block {}
  73. }
  74. priority = 110
  75. #rule_label {
  76. # name = "xdr_custom:log4j"
  77. #}
  78. visibility_config {
  79. cloudwatch_metrics_enabled = true
  80. metric_name = "Block_Log4j_exploit_20211210"
  81. sampled_requests_enabled = true
  82. }
  83. statement {
  84. or_statement {
  85. statement {
  86. byte_match_statement {
  87. field_to_match {
  88. single_header {
  89. name = "user-agent"
  90. }
  91. }
  92. positional_constraint = "STARTS_WITH"
  93. search_string = "$${jndi:ldap://"
  94. text_transformation {
  95. priority = 2
  96. type = "LOWERCASE"
  97. }
  98. }
  99. }
  100. statement {
  101. byte_match_statement {
  102. field_to_match {
  103. single_header {
  104. name = "user-agent"
  105. }
  106. }
  107. positional_constraint = "STARTS_WITH"
  108. search_string = "$${jndi:rmi:"
  109. text_transformation {
  110. priority = 2
  111. type = "LOWERCASE"
  112. }
  113. }
  114. }
  115. statement {
  116. byte_match_statement {
  117. field_to_match {
  118. single_header {
  119. name = "user-agent"
  120. }
  121. }
  122. positional_constraint = "STARTS_WITH"
  123. search_string = "$${jndi:dns:"
  124. text_transformation {
  125. priority = 2
  126. type = "LOWERCASE"
  127. }
  128. }
  129. }
  130. }
  131. }
  132. }
  133. # Add additional custom rules here
  134. lifecycle {
  135. create_before_destroy = true
  136. }
  137. }
  138. module "wafv2" {
  139. source = "trussworks/wafv2/aws"
  140. version = "= 2.4.0"
  141. name = local.waf_name
  142. scope = "REGIONAL"
  143. alb_arn = var.resource_arn
  144. associate_alb = true
  145. default_action = "block" # Note: The final action is actually to 'allow', provided the host header is correct
  146. filtered_header_rule = {
  147. header_types = var.fqdns
  148. header_value = "host"
  149. priority = 900
  150. action = "allow"
  151. }
  152. # IP based rules are processed first.
  153. # If an IP is blocked, it is blocked no matter what.
  154. # If an IP is allowed, it is allowed only if it's not blocked, but no other WAF rules are processed.
  155. ip_sets_rule = [
  156. {
  157. name = "blocked_ips"
  158. action = "block"
  159. priority = 10
  160. ip_set_arn = aws_wafv2_ip_set.blocked.arn
  161. },
  162. {
  163. name = "allowed_ips"
  164. action = "allow"
  165. priority = 20
  166. ip_set_arn = aws_wafv2_ip_set.allowed.arn
  167. }
  168. ]
  169. # Custom Rules are defined above
  170. group_rules = [
  171. {
  172. name = aws_wafv2_rule_group.xdr_custom_rules.name
  173. arn = aws_wafv2_rule_group.xdr_custom_rules.arn
  174. priority = 100
  175. override_action = "none"
  176. excluded_rules = []
  177. }
  178. ]
  179. # 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
  180. # Because of zscalar, this is completely ineffective for us.
  181. #ip_rate_based_rule = {
  182. # name = "Rate_Limit"
  183. # priority = 200
  184. # limit = 3000 # 6000 requests per 5 minutes= 10 requests/second (sustained for 5 minutes)
  185. # action = "block"
  186. #}
  187. # AWS managed rulesets
  188. # Baseline was from trussworks/wafv2/aws, but copied here to be customized for our use and renumbered.
  189. managed_rules = [
  190. {
  191. "excluded_rules": var.excluded_rules_AWSManagedRulesCommonRuleSet,
  192. "name": "AWSManagedRulesCommonRuleSet",
  193. "override_action": "none",
  194. "priority": 510
  195. },
  196. {
  197. "excluded_rules": var.excluded_rules_AWSManagedRulesAmazonIpReputationList,
  198. "name": "AWSManagedRulesAmazonIpReputationList",
  199. "override_action": "none",
  200. "priority": 520
  201. },
  202. {
  203. "excluded_rules": var.excluded_rules_AWSManagedRulesKnownBadInputsRuleSet,
  204. "name": "AWSManagedRulesKnownBadInputsRuleSet",
  205. "override_action": "none",
  206. "priority": 530
  207. },
  208. {
  209. "excluded_rules": var.excluded_rules_AWSManagedRulesSQLiRuleSet,
  210. "name": "AWSManagedRulesSQLiRuleSet",
  211. "override_action": "none",
  212. "priority": 540
  213. },
  214. {
  215. "excluded_rules": var.excluded_rules_AWSManagedRulesLinuxRuleSet,
  216. "name": "AWSManagedRulesLinuxRuleSet",
  217. "override_action": "none",
  218. "priority": 550
  219. },
  220. {
  221. "excluded_rules": var.excluded_rules_AWSManagedRulesUnixRuleSet,
  222. "name": "AWSManagedRulesUnixRuleSet",
  223. "override_action": "none",
  224. "priority": 560
  225. }
  226. ]
  227. depends_on = [ aws_wafv2_rule_group.xdr_custom_rules ]
  228. tags = var.tags
  229. }
  230. resource "aws_wafv2_web_acl_logging_configuration" "waf_logs" {
  231. log_destination_configs = [ "arn:${var.aws_partition}:firehose:${var.aws_region}:${var.aws_account_id}:deliverystream/aws-waf-logs-splunk" ]
  232. resource_arn = module.wafv2.web_acl_id
  233. # logging_filter {
  234. # default_behavior = "KEEP"
  235. #
  236. # filter {
  237. # behavior = "DROP"
  238. #
  239. # condition {
  240. # action_condition {
  241. # action = "COUNT"
  242. # }
  243. # }
  244. #
  245. # condition {
  246. # label_name_condition {
  247. # label_name = "awswaf:111122223333:rulegroup:testRules:LabelNameZ"
  248. # }
  249. # }
  250. #
  251. # requirement = "MEETS_ALL"
  252. # }
  253. #
  254. # filter {
  255. # behavior = "KEEP"
  256. #
  257. # condition {
  258. # action_condition {
  259. # action = "ALLOW"
  260. # }
  261. # }
  262. #
  263. # requirement = "MEETS_ANY"
  264. # }
  265. # }
  266. }