waf.tf 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  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_rev3" # update name when updating
  33. scope = "REGIONAL"
  34. capacity = 60
  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. #
  76. # #rule_label {
  77. # # name = "xdr_custom:log4j"
  78. # #}
  79. #
  80. # visibility_config {
  81. # cloudwatch_metrics_enabled = true
  82. # metric_name = "Block_Log4j_exploit_20211210"
  83. # sampled_requests_enabled = true
  84. # }
  85. #
  86. # statement {
  87. # or_statement {
  88. # statement {
  89. # byte_match_statement {
  90. # field_to_match {
  91. # single_header {
  92. # name = "user-agent"
  93. # }
  94. # }
  95. # positional_constraint = "STARTS_WITH"
  96. # search_string = "$${jndi:" # ldap://"
  97. #
  98. # text_transformation {
  99. # priority = 1
  100. # type = "BASE64_DECODE"
  101. # }
  102. #
  103. # #text_transformation {
  104. # # priority = 3
  105. # # type = "HEX_DECODE"
  106. # #}
  107. #
  108. # text_transformation {
  109. # priority = 5
  110. # type = "LOWERCASE"
  111. # }
  112. # }
  113. # }
  114. #
  115. ## statement {
  116. ## byte_match_statement {
  117. ## field_to_match {
  118. ## method {}
  119. ## }
  120. ## positional_constraint = "STARTS_WITH"
  121. ## search_string = "$${jndi:" # ldap://"
  122. ##
  123. ## text_transformation {
  124. ## priority = 1
  125. ## type = "BASE64_DECODE"
  126. ## }
  127. ##
  128. ## text_transformation {
  129. ## priority = 3
  130. ## type = "HEX_DECODE"
  131. ## }
  132. ##
  133. ## text_transformation {
  134. ## priority = 5
  135. ## type = "LOWERCASE"
  136. ## }
  137. ## }
  138. ## }
  139. ##
  140. ## statement {
  141. ## byte_match_statement {
  142. ## field_to_match {
  143. ## query_string {}
  144. ## }
  145. ## positional_constraint = "CONTAINS"
  146. ## search_string = "$${jndi:" # ldap://"
  147. ##
  148. ## text_transformation {
  149. ## priority = 1
  150. ## type = "BASE64_DECODE"
  151. ## }
  152. ##
  153. ## #text_transformation {
  154. ## # priority = 3
  155. ## # type = "HEX_DECODE"
  156. ## #}
  157. ##
  158. ## text_transformation {
  159. ## priority = 5
  160. ## type = "LOWERCASE"
  161. ## }
  162. ## }
  163. ## }
  164. #
  165. # statement {
  166. # byte_match_statement {
  167. # field_to_match {
  168. # uri_path {}
  169. # }
  170. # positional_constraint = "CONTAINS"
  171. # search_string = "$${jndi:" # ldap://"
  172. #
  173. # text_transformation {
  174. # priority = 1
  175. # type = "BASE64_DECODE"
  176. # }
  177. #
  178. # #text_transformation {
  179. # # priority = 3
  180. # # type = "HEX_DECODE"
  181. # #}
  182. #
  183. # text_transformation {
  184. # priority = 5
  185. # type = "LOWERCASE"
  186. # }
  187. # }
  188. # }
  189. # }
  190. # }
  191. # }
  192. # Add additional custom rules here
  193. lifecycle {
  194. create_before_destroy = true
  195. }
  196. }
  197. module "wafv2" {
  198. source = "trussworks/wafv2/aws"
  199. version = "= 2.4.0"
  200. name = local.waf_name
  201. scope = "REGIONAL"
  202. alb_arn = var.resource_arn
  203. associate_alb = true
  204. default_action = "block" # Note: The final action is actually to 'allow', provided the host header is correct
  205. filtered_header_rule = {
  206. header_types = var.fqdns
  207. header_value = "host"
  208. priority = 900
  209. action = "allow"
  210. }
  211. # IP based rules are processed first.
  212. # If an IP is blocked, it is blocked no matter what.
  213. # If an IP is allowed, it is allowed only if it's not blocked, but no other WAF rules are processed.
  214. ip_sets_rule = [
  215. {
  216. name = "blocked_ips"
  217. action = "block"
  218. priority = 10
  219. ip_set_arn = aws_wafv2_ip_set.blocked.arn
  220. },
  221. {
  222. name = "allowed_ips"
  223. action = "allow"
  224. priority = 20
  225. ip_set_arn = aws_wafv2_ip_set.allowed.arn
  226. }
  227. ]
  228. # Custom Rules are defined above
  229. group_rules = [
  230. {
  231. name = aws_wafv2_rule_group.xdr_custom_rules.name
  232. arn = aws_wafv2_rule_group.xdr_custom_rules.arn
  233. priority = 100
  234. override_action = "none"
  235. excluded_rules = []
  236. }
  237. ]
  238. # 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
  239. # Because of zscalar, this is completely ineffective for us.
  240. #ip_rate_based_rule = {
  241. # name = "Rate_Limit"
  242. # priority = 200
  243. # limit = 3000 # 6000 requests per 5 minutes= 10 requests/second (sustained for 5 minutes)
  244. # action = "block"
  245. #}
  246. # AWS managed rulesets
  247. # Baseline was from trussworks/wafv2/aws, but copied here to be customized for our use and renumbered.
  248. managed_rules = [
  249. {
  250. "excluded_rules": var.excluded_rules_AWSManagedRulesCommonRuleSet,
  251. "name": "AWSManagedRulesCommonRuleSet",
  252. "override_action": "none",
  253. "priority": 510
  254. },
  255. {
  256. "excluded_rules": var.excluded_rules_AWSManagedRulesAmazonIpReputationList,
  257. "name": "AWSManagedRulesAmazonIpReputationList",
  258. "override_action": "none",
  259. "priority": 520
  260. },
  261. {
  262. "excluded_rules": var.excluded_rules_AWSManagedRulesKnownBadInputsRuleSet,
  263. "name": "AWSManagedRulesKnownBadInputsRuleSet",
  264. "override_action": "none",
  265. "priority": 530
  266. },
  267. {
  268. "excluded_rules": var.excluded_rules_AWSManagedRulesSQLiRuleSet,
  269. "name": "AWSManagedRulesSQLiRuleSet",
  270. "override_action": "none",
  271. "priority": 540
  272. },
  273. {
  274. "excluded_rules": var.excluded_rules_AWSManagedRulesLinuxRuleSet,
  275. "name": "AWSManagedRulesLinuxRuleSet",
  276. "override_action": "none",
  277. "priority": 550
  278. },
  279. {
  280. "excluded_rules": var.excluded_rules_AWSManagedRulesUnixRuleSet,
  281. "name": "AWSManagedRulesUnixRuleSet",
  282. "override_action": "none",
  283. "priority": 560
  284. }
  285. ]
  286. depends_on = [ aws_wafv2_rule_group.xdr_custom_rules ]
  287. tags = var.tags
  288. }
  289. resource "aws_wafv2_web_acl_logging_configuration" "waf_logs" {
  290. log_destination_configs = [ "arn:${var.aws_partition}:firehose:${var.aws_region}:${var.aws_account_id}:deliverystream/aws-waf-logs-splunk" ]
  291. resource_arn = module.wafv2.web_acl_id
  292. # logging_filter {
  293. # default_behavior = "KEEP"
  294. #
  295. # filter {
  296. # behavior = "DROP"
  297. #
  298. # condition {
  299. # action_condition {
  300. # action = "COUNT"
  301. # }
  302. # }
  303. #
  304. # condition {
  305. # label_name_condition {
  306. # label_name = "awswaf:111122223333:rulegroup:testRules:LabelNameZ"
  307. # }
  308. # }
  309. #
  310. # requirement = "MEETS_ALL"
  311. # }
  312. #
  313. # filter {
  314. # behavior = "KEEP"
  315. #
  316. # condition {
  317. # action_condition {
  318. # action = "ALLOW"
  319. # }
  320. # }
  321. #
  322. # requirement = "MEETS_ANY"
  323. # }
  324. # }
  325. }