mirror of
https://github.com/ghndrx/terraform-foundation.git
synced 2026-02-10 06:45:06 +00:00
feat: Terraform Foundation - AWS Landing Zone
Enterprise-grade multi-tenant AWS cloud foundation. Modules: - GitHub OIDC for keyless CI/CD authentication - IAM account settings and security baseline - AWS Config Rules for compliance - ABAC (Attribute-Based Access Control) - SCPs (Service Control Policies) Features: - Multi-account architecture - Cost optimization patterns - Security best practices - Comprehensive documentation Tech: Terraform, AWS Organizations, IAM Identity Center
This commit is contained in:
57
terraform/modules/waf-alb/README.md
Normal file
57
terraform/modules/waf-alb/README.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# waf-alb
|
||||
|
||||
WAF Module for ALB Protection
|
||||
|
||||
## Usage
|
||||
|
||||
```hcl
|
||||
module "waf_alb" {
|
||||
source = "../modules/waf-alb"
|
||||
|
||||
# Required variables
|
||||
name = ""
|
||||
|
||||
# Optional: see variables.tf for all options
|
||||
}
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
| Name | Version |
|
||||
|------|---------|
|
||||
| terraform | >= 1.5.0 |
|
||||
| aws | >= 5.0 |
|
||||
|
||||
## Inputs
|
||||
|
||||
| Name | Description | Type | Required |
|
||||
|------|-------------|------|----------|
|
||||
| name | Name for the WAF Web ACL | `string` | yes |
|
||||
| description | | `string` | no |
|
||||
| rate_limit | Requests per 5-minute period per IP | `number` | no |
|
||||
| rate_limit_action | | `string` | no |
|
||||
| blocked_countries | ISO 3166-1 alpha-2 country codes to block | `list(string)` | no |
|
||||
| allowed_countries | If set, ONLY these countries are allowed (overrides blocked) | `list(string)` | no |
|
||||
| ip_allowlist | CIDR blocks to always allow | `list(string)` | no |
|
||||
| ip_blocklist | CIDR blocks to always block | `list(string)` | no |
|
||||
| enable_aws_managed_rules | | `bool` | no |
|
||||
| enable_known_bad_inputs | | `bool` | no |
|
||||
| enable_sql_injection | | `bool` | no |
|
||||
| enable_linux_protection | | `bool` | no |
|
||||
| enable_php_protection | | `bool` | no |
|
||||
| enable_wordpress_protection | | `bool` | no |
|
||||
| enable_bot_control | Bot Control (additional cost ~$10/mo + $1/million requests) | `bool` | no |
|
||||
|
||||
*...and 3 more variables. See `variables.tf` for complete list.*
|
||||
|
||||
## Outputs
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| web_acl_arn | ARN of the WAF Web ACL - use this with ALB |
|
||||
| web_acl_id | |
|
||||
| web_acl_capacity | WCU capacity used (max 1500 for regional) |
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0 - See LICENSE for details.
|
||||
561
terraform/modules/waf-alb/main.tf
Normal file
561
terraform/modules/waf-alb/main.tf
Normal file
@@ -0,0 +1,561 @@
|
||||
################################################################################
|
||||
# WAF Module for ALB Protection
|
||||
#
|
||||
# Provides Web Application Firewall protection:
|
||||
# - AWS Managed Rules (OWASP, Known Bad Inputs, etc.)
|
||||
# - Rate limiting
|
||||
# - Geo-blocking (optional)
|
||||
# - IP allowlist/blocklist
|
||||
# - Logging to S3/CloudWatch
|
||||
#
|
||||
# Attach to ALB: set waf_web_acl_arn in your workload
|
||||
################################################################################
|
||||
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
description = "Name for the WAF Web ACL"
|
||||
}
|
||||
|
||||
variable "description" {
|
||||
type = string
|
||||
default = "WAF Web ACL for ALB protection"
|
||||
}
|
||||
|
||||
# Rate limiting
|
||||
variable "rate_limit" {
|
||||
type = number
|
||||
default = 2000
|
||||
description = "Requests per 5-minute period per IP"
|
||||
}
|
||||
|
||||
variable "rate_limit_action" {
|
||||
type = string
|
||||
default = "block"
|
||||
validation {
|
||||
condition = contains(["block", "count"], var.rate_limit_action)
|
||||
error_message = "Must be 'block' or 'count'"
|
||||
}
|
||||
}
|
||||
|
||||
# Geo restrictions
|
||||
variable "blocked_countries" {
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "ISO 3166-1 alpha-2 country codes to block"
|
||||
}
|
||||
|
||||
variable "allowed_countries" {
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "If set, ONLY these countries are allowed (overrides blocked)"
|
||||
}
|
||||
|
||||
# IP lists
|
||||
variable "ip_allowlist" {
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "CIDR blocks to always allow"
|
||||
}
|
||||
|
||||
variable "ip_blocklist" {
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "CIDR blocks to always block"
|
||||
}
|
||||
|
||||
# Managed rule settings
|
||||
variable "enable_aws_managed_rules" {
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "enable_known_bad_inputs" {
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "enable_sql_injection" {
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "enable_linux_protection" {
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "enable_php_protection" {
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "enable_wordpress_protection" {
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "enable_bot_control" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Bot Control (additional cost ~$10/mo + $1/million requests)"
|
||||
}
|
||||
|
||||
# Logging
|
||||
variable "enable_logging" {
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "log_destination_arn" {
|
||||
type = string
|
||||
default = ""
|
||||
description = "S3 bucket ARN, CloudWatch Log Group ARN, or Kinesis Firehose ARN"
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# IP Sets
|
||||
################################################################################
|
||||
|
||||
resource "aws_wafv2_ip_set" "allowlist" {
|
||||
count = length(var.ip_allowlist) > 0 ? 1 : 0
|
||||
name = "${var.name}-allowlist"
|
||||
description = "Allowed IP addresses"
|
||||
scope = "REGIONAL"
|
||||
ip_address_version = "IPV4"
|
||||
addresses = var.ip_allowlist
|
||||
|
||||
tags = merge(var.tags, { Name = "${var.name}-allowlist" })
|
||||
}
|
||||
|
||||
resource "aws_wafv2_ip_set" "blocklist" {
|
||||
count = length(var.ip_blocklist) > 0 ? 1 : 0
|
||||
name = "${var.name}-blocklist"
|
||||
description = "Blocked IP addresses"
|
||||
scope = "REGIONAL"
|
||||
ip_address_version = "IPV4"
|
||||
addresses = var.ip_blocklist
|
||||
|
||||
tags = merge(var.tags, { Name = "${var.name}-blocklist" })
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Web ACL
|
||||
################################################################################
|
||||
|
||||
resource "aws_wafv2_web_acl" "main" {
|
||||
name = var.name
|
||||
description = var.description
|
||||
scope = "REGIONAL"
|
||||
|
||||
default_action {
|
||||
allow {}
|
||||
}
|
||||
|
||||
# Rule 1: IP Allowlist (highest priority - allow first)
|
||||
dynamic "rule" {
|
||||
for_each = length(var.ip_allowlist) > 0 ? [1] : []
|
||||
content {
|
||||
name = "AllowlistedIPs"
|
||||
priority = 0
|
||||
|
||||
override_action {
|
||||
none {}
|
||||
}
|
||||
|
||||
statement {
|
||||
ip_set_reference_statement {
|
||||
arn = aws_wafv2_ip_set.allowlist[0].arn
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = "${var.name}-allowlist"
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Rule 2: IP Blocklist
|
||||
dynamic "rule" {
|
||||
for_each = length(var.ip_blocklist) > 0 ? [1] : []
|
||||
content {
|
||||
name = "BlocklistedIPs"
|
||||
priority = 1
|
||||
|
||||
action {
|
||||
block {}
|
||||
}
|
||||
|
||||
statement {
|
||||
ip_set_reference_statement {
|
||||
arn = aws_wafv2_ip_set.blocklist[0].arn
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = "${var.name}-blocklist"
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Rule 3: Geo blocking
|
||||
dynamic "rule" {
|
||||
for_each = length(var.blocked_countries) > 0 ? [1] : []
|
||||
content {
|
||||
name = "GeoBlock"
|
||||
priority = 2
|
||||
|
||||
action {
|
||||
block {}
|
||||
}
|
||||
|
||||
statement {
|
||||
geo_match_statement {
|
||||
country_codes = var.blocked_countries
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = "${var.name}-geoblock"
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Rule 4: Geo allow (only specific countries)
|
||||
dynamic "rule" {
|
||||
for_each = length(var.allowed_countries) > 0 ? [1] : []
|
||||
content {
|
||||
name = "GeoAllow"
|
||||
priority = 3
|
||||
|
||||
action {
|
||||
block {}
|
||||
}
|
||||
|
||||
statement {
|
||||
not_statement {
|
||||
statement {
|
||||
geo_match_statement {
|
||||
country_codes = var.allowed_countries
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = "${var.name}-geoallow"
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Rule 5: Rate limiting
|
||||
rule {
|
||||
name = "RateLimit"
|
||||
priority = 10
|
||||
|
||||
action {
|
||||
dynamic "block" {
|
||||
for_each = var.rate_limit_action == "block" ? [1] : []
|
||||
content {}
|
||||
}
|
||||
dynamic "count" {
|
||||
for_each = var.rate_limit_action == "count" ? [1] : []
|
||||
content {}
|
||||
}
|
||||
}
|
||||
|
||||
statement {
|
||||
rate_based_statement {
|
||||
limit = var.rate_limit
|
||||
aggregate_key_type = "IP"
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = "${var.name}-ratelimit"
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
# Rule 6: AWS Managed Rules - Common Rule Set
|
||||
dynamic "rule" {
|
||||
for_each = var.enable_aws_managed_rules ? [1] : []
|
||||
content {
|
||||
name = "AWSManagedRulesCommonRuleSet"
|
||||
priority = 20
|
||||
|
||||
override_action {
|
||||
none {}
|
||||
}
|
||||
|
||||
statement {
|
||||
managed_rule_group_statement {
|
||||
name = "AWSManagedRulesCommonRuleSet"
|
||||
vendor_name = "AWS"
|
||||
|
||||
# Exclude rules that may cause false positives
|
||||
rule_action_override {
|
||||
name = "SizeRestrictions_BODY"
|
||||
action_to_use {
|
||||
count {}
|
||||
}
|
||||
}
|
||||
|
||||
rule_action_override {
|
||||
name = "GenericRFI_BODY"
|
||||
action_to_use {
|
||||
count {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = "${var.name}-common"
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Rule 7: Known Bad Inputs
|
||||
dynamic "rule" {
|
||||
for_each = var.enable_known_bad_inputs ? [1] : []
|
||||
content {
|
||||
name = "AWSManagedRulesKnownBadInputsRuleSet"
|
||||
priority = 21
|
||||
|
||||
override_action {
|
||||
none {}
|
||||
}
|
||||
|
||||
statement {
|
||||
managed_rule_group_statement {
|
||||
name = "AWSManagedRulesKnownBadInputsRuleSet"
|
||||
vendor_name = "AWS"
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = "${var.name}-badinputs"
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Rule 8: SQL Injection
|
||||
dynamic "rule" {
|
||||
for_each = var.enable_sql_injection ? [1] : []
|
||||
content {
|
||||
name = "AWSManagedRulesSQLiRuleSet"
|
||||
priority = 22
|
||||
|
||||
override_action {
|
||||
none {}
|
||||
}
|
||||
|
||||
statement {
|
||||
managed_rule_group_statement {
|
||||
name = "AWSManagedRulesSQLiRuleSet"
|
||||
vendor_name = "AWS"
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = "${var.name}-sqli"
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Rule 9: Linux Protection
|
||||
dynamic "rule" {
|
||||
for_each = var.enable_linux_protection ? [1] : []
|
||||
content {
|
||||
name = "AWSManagedRulesLinuxRuleSet"
|
||||
priority = 23
|
||||
|
||||
override_action {
|
||||
none {}
|
||||
}
|
||||
|
||||
statement {
|
||||
managed_rule_group_statement {
|
||||
name = "AWSManagedRulesLinuxRuleSet"
|
||||
vendor_name = "AWS"
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = "${var.name}-linux"
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Rule 10: PHP Protection
|
||||
dynamic "rule" {
|
||||
for_each = var.enable_php_protection ? [1] : []
|
||||
content {
|
||||
name = "AWSManagedRulesPHPRuleSet"
|
||||
priority = 24
|
||||
|
||||
override_action {
|
||||
none {}
|
||||
}
|
||||
|
||||
statement {
|
||||
managed_rule_group_statement {
|
||||
name = "AWSManagedRulesPHPRuleSet"
|
||||
vendor_name = "AWS"
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = "${var.name}-php"
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Rule 11: WordPress Protection
|
||||
dynamic "rule" {
|
||||
for_each = var.enable_wordpress_protection ? [1] : []
|
||||
content {
|
||||
name = "AWSManagedRulesWordPressRuleSet"
|
||||
priority = 25
|
||||
|
||||
override_action {
|
||||
none {}
|
||||
}
|
||||
|
||||
statement {
|
||||
managed_rule_group_statement {
|
||||
name = "AWSManagedRulesWordPressRuleSet"
|
||||
vendor_name = "AWS"
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = "${var.name}-wordpress"
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Rule 12: Bot Control (costs extra)
|
||||
dynamic "rule" {
|
||||
for_each = var.enable_bot_control ? [1] : []
|
||||
content {
|
||||
name = "AWSManagedRulesBotControlRuleSet"
|
||||
priority = 30
|
||||
|
||||
override_action {
|
||||
none {}
|
||||
}
|
||||
|
||||
statement {
|
||||
managed_rule_group_statement {
|
||||
name = "AWSManagedRulesBotControlRuleSet"
|
||||
vendor_name = "AWS"
|
||||
|
||||
managed_rule_group_configs {
|
||||
aws_managed_rules_bot_control_rule_set {
|
||||
inspection_level = "COMMON"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = "${var.name}-botcontrol"
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = var.name
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
|
||||
tags = merge(var.tags, { Name = var.name })
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Logging
|
||||
################################################################################
|
||||
|
||||
resource "aws_wafv2_web_acl_logging_configuration" "main" {
|
||||
count = var.enable_logging && var.log_destination_arn != "" ? 1 : 0
|
||||
log_destination_configs = [var.log_destination_arn]
|
||||
resource_arn = aws_wafv2_web_acl.main.arn
|
||||
|
||||
logging_filter {
|
||||
default_behavior = "DROP"
|
||||
|
||||
filter {
|
||||
behavior = "KEEP"
|
||||
requirement = "MEETS_ANY"
|
||||
|
||||
condition {
|
||||
action_condition {
|
||||
action = "BLOCK"
|
||||
}
|
||||
}
|
||||
|
||||
condition {
|
||||
action_condition {
|
||||
action = "COUNT"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Outputs
|
||||
################################################################################
|
||||
|
||||
output "web_acl_arn" {
|
||||
value = aws_wafv2_web_acl.main.arn
|
||||
description = "ARN of the WAF Web ACL - use this with ALB"
|
||||
}
|
||||
|
||||
output "web_acl_id" {
|
||||
value = aws_wafv2_web_acl.main.id
|
||||
}
|
||||
|
||||
output "web_acl_capacity" {
|
||||
value = aws_wafv2_web_acl.main.capacity
|
||||
description = "WCU capacity used (max 1500 for regional)"
|
||||
}
|
||||
Reference in New Issue
Block a user