Deploying Next-Gen WAF with Terraform

This tutorial guides you through deploying Fastly's Next-Gen Web Application Firewall (Next-Gen WAF) for web and API endpoint security using the Edge Deployment method, which allows you to add an edge security service onto our Edge Cloud Platform without needing to make any modifications to your own hosting environment.

You will use the following Terraform providers:

Prerequisites

Before deploying the Next-Gen WAF, ensure you have the following:

1. Configure Terraform providers

Ensure Terraform 0.13+ is configured with the required providers for Fastly:

providers.tf
Terraform HCL
terraform {
required_providers {
fastly = {
source = "fastly/fastly"
version = ">= 5.7.1"
}
sigsci = {
source = "signalsciences/sigsci"
version = ">= 3.0.0"
}
http = {
source = "hashicorp/http"
}
}
}

2. Define variables

Declare the necessary variables and resources for the Fastly Edge VCL configuration, Next-Gen WAF settings, and dynamic snippets. This includes specifying domain names, backend hostnames, and API keys.

variables.tf
Terraform HCL
variable "FASTLY_API_KEY" {
type = string
description = "This is API key for the Fastly VCL edge configuration."
}
variable "USER_VCL_SERVICE_DOMAIN_NAME" {
type = string
description = "Frontend domain for your service."
default = "ngwaf-tf-demo.global.ssl.fastly.net"
}
variable "USER_VCL_SERVICE_BACKEND_HOSTNAME" {
type = string
description = "hostname used for backend."
default = "http-me.glitch.me"
}
variables.tf
Terraform HCL
variable "NGWAF_CORP" {
type = string
description = "Corp name for NGWAF"
}
variable "NGWAF_SITE" {
type = string
description = "Site name for NGWAF"
}
variable "NGWAF_EMAIL" {
type = string
description = "Email address associated with the token for the NGWAF API."
}
variable "NGWAF_TOKEN" {
type = string
description = "Secret token for the NGWAF API."
sensitive = true
}

3. Set values for variables

The values for the declared variables must be available to the environment where Terraform is running following Hashicorp's guidance for managing variables.

4. Update the Fastly VCL service

Before linking the Next-Gen WAF edge deployment to the VCL service, you must add placeholders for dynamic snippets and a dictionary that will be used by the Next-Gen WAF integration. If the configurations are not added by Terraform, then Terraform will attempt to remove or replace the settings needed for the Next-Gen WAF Edge implementation. This behavior exists because the Next-Gen WAF Edge implementation uses the Terraform provider to make updates which impact the resources defined by the Fastly provider. Populating the dictionary and dynamic snippets with the desired values for the Next-Gen WAF edge deployment will be handled via the Terraform provider.

The dynamic snippets are simply commented VCL and act as a placeholder for the modifications that will occur via the Next-Gen WAF edge deployment. It is worth noting that both the dictionary and dynamic snippets are versionless. This means when modifications are made (even in subsequent service versions), those modifications will persist.

The priority of the snippets ngwaf_config_miss and ngwaf_config_pass are intentionally high, to avoid conflicts with any existing VCL logic.

main.tf
Terraform HCL
resource "fastly_service_dictionary_items" "edge_security_dictionary_items" {
for_each = {
for d in fastly_service_vcl.frontend-vcl-service.dictionary : d.name => d if d.name == "Edge_Security"
}
service_id = fastly_service_vcl.frontend-vcl-service.id
dictionary_id = each.value.dictionary_id
items = {
Enabled : "100"
}
}
resource "fastly_service_dynamic_snippet_content" "ngwaf_config_init" {
for_each = {
for d in fastly_service_vcl.frontend-vcl-service.dynamicsnippet : d.name => d if d.name == "ngwaf_config_init"
}
service_id = fastly_service_vcl.frontend-vcl-service.id
snippet_id = each.value.snippet_id
content = "### Fastly managed ngwaf_config_init"
manage_snippets = false
}
resource "fastly_service_dynamic_snippet_content" "ngwaf_config_miss" {
for_each = {
for d in fastly_service_vcl.frontend-vcl-service.dynamicsnippet : d.name => d if d.name == "ngwaf_config_miss"
}
service_id = fastly_service_vcl.frontend-vcl-service.id
snippet_id = each.value.snippet_id
content = "### Fastly managed ngwaf_config_miss"
manage_snippets = false
}
resource "fastly_service_dynamic_snippet_content" "ngwaf_config_pass" {
for_each = {
for d in fastly_service_vcl.frontend-vcl-service.dynamicsnippet : d.name => d if d.name == "ngwaf_config_pass"
}
service_id = fastly_service_vcl.frontend-vcl-service.id
snippet_id = each.value.snippet_id
content = "### Fastly managed ngwaf_config_pass"
manage_snippets = false
}
resource "fastly_service_dynamic_snippet_content" "ngwaf_config_deliver" {
for_each = {
for d in fastly_service_vcl.frontend-vcl-service.dynamicsnippet : d.name => d if d.name == "ngwaf_config_deliver"
}
service_id = fastly_service_vcl.frontend-vcl-service.id
snippet_id = each.value.snippet_id
content = "### Fastly managed ngwaf_config_deliver"
manage_snippets = false
}

5. Integrate the Next-Gen WAF Edge deployment

Use the Signal Sciences provider to create the Next-Gen WAF Edge Service and link it to the Fastly VCL service. This step involves setting up an edge dictionary and updating dynamic snippets maintained by Fastly.

NOTE: The updated dynamic snippets will not be overwritten by subsequent terraform updates because of the manage_snippets = false setting in the dynamic snippet Terraform configuration. If manage_snippets is not set to false, then the dynamic snipets will be overwritten and traffic for your service will not be protected by the edge Next-Gen WAF deployment.

main.tf
Terraform HCL
provider "sigsci" {
corp = var.NGWAF_CORP
email = var.NGWAF_EMAIL
auth_token = var.NGWAF_TOKEN
fastly_api_key = var.FASTLY_API_KEY
}
resource "sigsci_edge_deployment" "ngwaf_edge_site_service" {
# https://registry.terraform.io/providers/signalsciences/sigsci/latest/docs/resources/edge_deployment
site_short_name = sigsci_site.ngwaf_edge_site.short_name
}
resource "sigsci_edge_deployment_service" "ngwaf_edge_service_link" {
# https://registry.terraform.io/providers/signalsciences/sigsci/latest/docs/resources/edge_deployment_service
site_short_name = sigsci_site.ngwaf_edge_site.short_name
fastly_sid = fastly_service_vcl.frontend-vcl-service.id
activate_version = true
percent_enabled = 100
depends_on = [
sigsci_edge_deployment.ngwaf_edge_site_service,
fastly_service_vcl.frontend-vcl-service,
fastly_service_dictionary_items.edge_security_dictionary_items,
fastly_service_dynamic_snippet_content.ngwaf_config_init,
fastly_service_dynamic_snippet_content.ngwaf_config_miss,
fastly_service_dynamic_snippet_content.ngwaf_config_pass,
fastly_service_dynamic_snippet_content.ngwaf_config_deliver,
]
}

6. Apply configuration

Apply the Terraform configuration using the following command.

terraform apply -parallelism=1

-parallelism=1 is required based on guidance from the Signal Sciences Terraform provider. In case of errors…

When the configuration is applied, the dynamic snippets will be added in a VCL version. The Signal Sciences provider will then do the following:

  1. Clone the existing active configuration.
  2. Populate the dynamic snippets and Dictionary with the values needed for the Next-Gen WAF integration.
  3. Activate the new version.

TIP: Your local Terraform state for the VCL service won't reflect this newly incremented version of the VCL service. Run terraform apply -parallelism=1 again in order to match the local state to the remote state. This terraform apply should not make any changes to the VCL service. After a successful run, you should see outputs like the following:

Outputs:
live_waf_love_output = <<EOT
#### Click the URL to go to the Fastly VCL service ####
https://cfg.fastly.com/LINKEDSERVICEID
#### Click the URL to go to the Fastly NGWAF service ####
https://dashboard.signalsciences.net/corps/my_corp_name/sites/my_site_name
#### Send a test request with curl. ####
curl -i "https://devhub-ngwaf-terraform-tutorial.global.ssl.fastly.net/anything/whydopirates?likeurls=theargs" -d foo=bar
#### Send a test as traversal with curl. ####
curl -i "https://devhub-ngwaf-terraform-tutorial.global.ssl.fastly.net/anything/myattackreq?i=../../../../etc/passwd" -d foo=bar
#### Send a test as XSS with curl. ####
curl -i "https://devhub-ngwaf-terraform-tutorial.global.ssl.fastly.net/anything/myattackreq?foo=%3Cscript%3E" -d foo=bar
#### Troubleshoot the logging configuration if necessary. ####
https://docs.fastly.com/en/guides/setting-up-remote-log-streaming#troubleshooting-common-logging-errors
curl https://api.fastly.com/service/OuEbVB7UoymNTqI9ghCpE5/logging_status -H fastly-key:$FASTLY_API_KEY
EOT
response_body = [
"LINKEDSERVICEID",
]

Deployment considerations

Different designs using Terraform will require different approaches. This the following sections contain a few considerations.

Deploying Next-Gen WAF edge to an a new VCL service

For new implemenations, you may simply use the the complete Terraform implementation. When running the command terraform apply -parallelism=1, the implicit dependencies and the depends_on blocks within the resources ensure that the resources are created in the correct order.

Deploying Next-Gen WAF edge to an existing VCL service

The above methodology can work for an existing VCL service as well. It's very important to follow the guidance for updating the Fastly VCL service which makes Terraform aware of the dynamic snippets and dictionary. This configuration must occur before using the sigsci_edge_deployment_service Terraform resource. Otherwise, the dynamic snippets will be overwritten or deleted by subsequent terraform apply runs. For more information on the manage_snippets false configuration, please see our Fastly Terraform provider documentation for the resource fastly_service_dynamic_snippet_content.

You may complete the Next-Gen WAF edge implementation without sending traffic to the WAF. This can be helpful to test or gradually ramp traffic to the WAF edge implementation. The Edge_Security Dictionary may also be set to the value 0 within the Terraform resource fastly_service_dictionary_items and via resource sigsci_edge_deployment_service using the percent_enabled field. Our Traffic Ramping documenation the percent_enabled field

Deploying Next-Gen WAF edge to an existing Next-Gen WAF site

If you already have a Next-Gen WAF site you wish to use with the Next-Gen WAF edge implementation, then you may remove the Terraform resource block sigsci_site:

main.tf
Terraform HCL
resource "sigsci_site" "ngwaf_edge_site" {
short_name = var.NGWAF_SITE
display_name = var.NGWAF_SITE
block_duration_seconds = 86400
agent_anon_mode = ""
agent_level = "log"
}

You must then update all Terraform resources that depend on the sigsci_site resource, by replacing all references to sigsci_site.ngwaf_edge_site.short_name with the value var.NGWAF_SITE.

Optional: Identifying Fastly Delivery services linked with Next-Gen WAF edge deployments

Terraform can provide helpful outputs when doing a deployment. The example below shows you how to identify different Delivery services linked to a Next-Gen WAF edge deployment.

main.tf
Terraform HCL
provider "http" {}
data "http" "linked_fastly_services" {
url = "https://dashboard.signalsciences.net/api/v0/corps/${var.NGWAF_CORP}/sites/${var.NGWAF_SITE}/edgeDeployment"
request_headers = {
x-api-user = var.NGWAF_EMAIL
x-api-token = var.NGWAF_TOKEN
Content-Type = "application/json"
}
}
output "response_body" {
value = [for item in jsondecode(data.http.linked_fastly_services.response_body)["ServicesAttached"] : item.id]
}

Full reference implementation

The fastly/ngwaf-terraform-edge-deploy repository closely follows the recommendations in this tutorial.