Setting up a Web Application Firewall (WAF) using Terraform
Tags: terraform, aws, devops, security
Published: 2023-10-02 14:51:36

**Web application firewalls (WAFs)** are an essential security layer for any web application. They protect your applications from common attacks, such as SQL injection, cross-site scripting, and denial-of-service attacks.

**Terraform** is an infrastructure as code (IaC) tool that can be used to automate the deployment and management of WAFs. This makes it easy to deploy consistent and secure WAFs across all of your environments.

In this blog post, we will show you how to set up a WAF using Terraform. We will use the AWS WAF v2 service as an example, but the same principles can be applied to other WAFs.

## **Prerequisites**

Before getting started, you will need to have the following prerequisites:
- A Terraform installation
- An AWS account with the necessary permissions to create and manage WAF resources


## **Creating a Terraform configuration**

The first step is to create a Terraform configuration file. This file will define the resources that you want to create, such as the WAF and its associated rules.

Here is a simple example of a Terraform configuration for an AWS WAF v2:

```
#######################################
# --- Resources ----
#######################################
resource "aws_wafv2_web_acl" "waf" {
  count = var.create ? 1 : 0
  name  = "${var.prefix}-${var.name}"
  scope = var.scope

  // default action
  default_action {
    allow {}
  }

  // AWSManagedRulesCommonRuleSet
  rule {
    name     = "AWSManagedRulesCommonRuleSet"
    priority = 10

    override_action {
      none {
      }
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesCommonRuleSet"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "AWSManagedRulesCommonRuleSetMetric"
      sampled_requests_enabled   = true
    }
  }

  // AWSManagedRulesKnownBadInputsRuleSet
  rule {
    name     = "AWSManagedRulesKnownBadInputsRuleSet"
    priority = 20

    override_action {
      none {
      }
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesKnownBadInputsRuleSet"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "AWSManagedRulesKnownBadInputsRuleSetMetric"
      sampled_requests_enabled   = true
    }
  }

  // AWSManagedRulesAmazonIpReputationList
  rule {
    name     = "AWSManagedRulesAmazonIpReputationList"
    priority = 30

    override_action {
      none {
      }
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesAmazonIpReputationList"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "AWSManagedRulesAmazonIpReputationListMetric"
      sampled_requests_enabled   = true
    }
  }

  // AWSManagedRulesAnonymousIpList
  rule {
    name     = "AWSManagedRulesAnonymousIpList"
    priority = 40

    override_action {
      none {
      }
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesAnonymousIpList"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "AWSManagedRulesAnonymousIpListMetric"
      sampled_requests_enabled   = true
    }
  }

  // AWSManagedRulesSQLiRuleSet
  rule {
    name     = "AWSManagedRulesSQLiRuleSet"
    priority = 50

    override_action {
      none {
      }
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesSQLiRuleSet"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "AWSManagedRulesSQLiRuleSetMetric"
      sampled_requests_enabled   = true
    }
  }

  // AWSManagedRulesLinuxRuleSet
  rule {
    name     = "AWSManagedRulesLinuxRuleSet"
    priority = 60

    override_action {
      none {
      }
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesLinuxRuleSet"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "AWSManagedRulesLinuxRuleSetMetric"
      sampled_requests_enabled   = true
    }
  }

  // AWSManagedRulesUnixRuleSet
  rule {
    name     = "AWSManagedRulesUnixRuleSet"
    priority = 70

    override_action {
      none {
      }
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesUnixRuleSet"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "AWSManagedRulesUnixRuleSetMetric"
      sampled_requests_enabled   = true
    }
  }

  tags = {
    Name = "${var.prefix}-${var.name}"
  }

  visibility_config {
    cloudwatch_metrics_enabled = true
    metric_name                = var.metric_name
    sampled_requests_enabled   = true
  }

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_wafv2_web_acl_logging_configuration" "api_server_waf_log" {
  count                   = var.create ? 1 : 0
  log_destination_configs = [aws_s3_bucket.logs.arn]
  resource_arn            = aws_wafv2_web_acl.waf[count.index].arn
}

resource "aws_s3_bucket" "logs" {
  bucket = "aws-waf-logs-${var.prefix}-${var.name}"

  force_destroy = true

  tags = {
    Name = "aws-waf-logs-${var.prefix}-${var.name}"
  }
}

resource "aws_s3_bucket_acl" "acl" {
  bucket     = aws_s3_bucket.logs.id
  acl        = "private"
  depends_on = [aws_s3_bucket_ownership_controls.acl_ownership]
}

resource "aws_s3_bucket_ownership_controls" "acl_ownership" {
  bucket = aws_s3_bucket.logs.id
  rule {
    object_ownership = "ObjectWriter"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "sse_configuration" {
  bucket = aws_s3_bucket.logs.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

resource "aws_s3_bucket_public_access_block" "public_access_block" {
  bucket                  = aws_s3_bucket.logs.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

#######################################
# --- Variables ----
#######################################
variable "create" {
  description = "Controls if waf should be created"
  type        = bool
}

variable "prefix" {
  description = "The prefix of the resource name"
  type        = string
}

variable "name" {
  description = "The name of the waf resource"
  type        = string
}

variable "scope" {
  description = "The scope of the waf"
  type        = string
}

variable "metric_name" {
  description = "The name of the metric"
  type        = string
}

#######################################
# --- Outputs ----
#######################################
output "waf_arn" {
  value       = length(aws_wafv2_web_acl.waf) > 0 ? aws_wafv2_web_acl.waf[0].arn : ""
  description = "The arn of waf"
}
```

## **Deploying the WAF**

Once you have created the Terraform configuration file, you can deploy the WAF by running the following command:

`terraform apply`

This will create the WAF and its associated resources in AWS.

## **Testing the WAF**

Once the WAF has been deployed, you should test it to make sure that it is working properly. You can do this by sending a test request to your web application and verifying that the WAF blocks the request.

You can also use the AWS WAF console to test the WAF. To do this, go to the Web ACLs page and click on the name of your web ACL. Then, click on the Test tab and enter a test request.

## **Conclusion**

In this blog post, we have shown you how to set up a WAF using Terraform. Terraform is a powerful tool that can be used to automate the deployment and management of WAFs. This makes it easy to deploy consistent and secure WAFs across all of your environments.