Manage F5 BIG-IP Advanced WAF Policies with Terraform (Part 4 - Policy Lifecycle management)
The goal of this article is to present how to best manage your F5 BIG-IP Advanced WAF policy entities and attack signatures.
Table of Content
Introduction
Once a policy is created, imported or migrated you’re not done. The F5 BIG-IP Advanced WAF Policy lives with the Application, should evolve with it and adapt to its potential attack surface changes.
Many changes can be done on the F5 BIG-IP Advanced WAF Policy
- adding a parameter with its attribute
- adding a URL
- adding a FileType
- adding Attack Signatures
Adding a parameter
here is the link to the terraform data source documentation.
I always recommend splitting resources into easy to find and easy to manage files. Here, we will put all the parameters attributes into a dedicated “parameters.tf” file.
Create a parameters.tf file:
data "bigip_waf_entity_parameter" "P1" {
name = "Parameter1"
type = "explicit"
data_type = "alpha-numeric"
perform_staging = true
signature_overrides_disable = [200001494, 200001472]
}
This P1 parameter is an alphanumeric explicit parameter positioned at the global level with two attack signatures disabled on it.
To apply it to the F5 BIG-IP Advanced WAF Policy, you only have to reference it in the “parameters” list of the bigip_waf_policy resource.
resource "bigip_waf_policy" "this" {
[...]
parameters = [data.bigip_waf_entity_parameter.P1.json]
}
Because each entity is decoupled from the F5 BIG-IP Advanced WAF Policy and explicitly referenced into it makes it a very easy way to add, roll-back, remove or update parameters definitions in your F5 BIG-IP Advanced WAF Policy.
That’s also a very handy way to declare a parameter, attach it to a testing F5 BIG-IP Advanced WAF Policy before moving it to production or attach it to multiple F5 BIG-IP Advanced WAF Policies.
Now, once you have a large number of parameters, it could become difficult to maintain a long list of “data.bigip_waf_entity_parameter.*.json » references.
So, if you want to make it easier to read and manage, you can create a “parameters.auto.tfvars” with the following content:
Parameters = {
P1 = {
name = "Parameter1"
type = "explicit"
data_type = "alpha-numeric"
perform_staging = true
}
P2 = {
name = "Parameter2"
type = "wildcard"
data_type = "alpha-numeric"
perform_staging = false
signature_overrides_disable = [200001494, 200001472]
}
P3 = {
name = "Parameter3"
type = "explicit"
data_type = "alpha-numeric"
is_header = true
sensitive_parameter = true
perform_staging = true
}
}
Your parameters.tf file should look like:
variable "parameters" {
type = map(object({
name = string
type = string
data_type = string
is_header = bool
sensitive_parameter = bool
perform_staging = bool
signature_overrides_disable = list(number)
description = (optional(string))
}))
}
data "bigip_waf_entity_parameter" "map_params" {
for_each = var.parameters
name = each.value["name"]
type = each.value["type"]
data_type = each.value["data_type"]
is_header = each.value["is_header"]
sensitive_parameter = each.value["sensitive_parameter"]
perform_staging = each.value["perform_staging"]
signature_overrides_disable = each.value["signature_override_disable"]
description = each.value["description"]
}
And your F5 BIG-IP Advanced WAF Policy terraform resource is :
resource "bigip_waf_policy" "s4_qa" {
…
parameters = [for k,v in data.bigip_waf_entity_parameter.map_params: v.json]
…
}
And it does not change regardless of the number of parameters listed in your parameters.auto.tfvars.
Adding an URL
here is the link to the terraform data source documentation.
Same as with the parameters, we will use a dedicated "urls.tf" file.
Create a urls.tf file:
data "bigip_waf_entity_url" "url1" {
name = "/url1"
description = "managed by terraform"
type = "explicit"
protocol = "HTTP"
perform_staging = true
signature_overrides_disable = [12345678, 87654321]
method_overrides {
allow = false
method = "BCOPY"
}
method_overrides {
allow = true
method = "BDELETE"
}
}
This is an explicit HTTP url with attack signatures exceptions (disabling 123445678 and 87654321) and with overriding on BCPOY and BDELETE methods.
To apply it to the WAF Policy, you only have to reference it in the “urls” list of the bigip_waf_policy resource.
resource "bigip_waf_policy" "this" {
[...]
urls = [data.bigip_waf_entity_url.url1.json]
[...]
}
Same way we did with the parameters, we can ease the insertion of the urls in the F5 BIG-IP Advanced WAF Policy by creating an automatic input file for the URLs:
urls = {
url1 = {
name = "/url1"
type = "explicit"
protocol = "HTTPS"
description = "login page"
perform_staging = true
}
url2 = {
name = "url2"
type = "wildcard"
description = "logout page"
perform_staging = false
protocol = HTTPS
method = "*"
signature_overrides_disable = [200001494, 200001472]
}
url3 = {
name = "/url3"
type = "explicit"
description = "this is URL3"
is_header = true
protocol = HTTPS
perform_staging = false
signature_overrides_disable = [12345678, 87654321]
method_overrides {
allow = false
method = "DELETE"
}
}
}
Your urls.tf file should look like:
data "bigip_waf_entity_url" "map_urls" {
for_each = var.parameters
name = each.value["name"]
description = each.value["description"]
type = each.value["type"]
protocol = each.value["protocol"]
method = each.value["method"]
perform_staging = each.value["perform_staging"]
signature_overrides_disable = each.value["signature_override_disable"]
method_overrides = each.value["method_overrides"]
}
And your F5 BIG-IP Advanced WAF Policy terraform resource is :
resource "bigip_waf_policy" "this" {
…
urls = [for k,v in data.bigip_waf_entity_url.map_urls: v.json]
…
}
And it does not change regardless of the number of parameters listed in your parameters.auto.tfvars.
Adding attack signatures
signatures.auto.tfvars
signatures = {
200101559 = {
signature_id = 200101559
description = "src http: (Header)"
enabled = true
perform_staging = false
}
200101558 = {
signature_id = 200101558
description = "src http: (Parameter)"
enabled = true
perform_staging = false
}
200003067 = {
signature_id = 200003067
description = "\"/..namedfork/data\" execution attempt (Headers)"
enabled = true
perform_staging = false
}
200003066 = {
signature_id = 200003066
description = "\"/..namedfork/data\" execution attempt (Parameters)"
enabled = true
perform_staging = false
}
200003068 = {
signature_id = 200003068
description = "\"/..namedfork/data\" execution attempt (URI)"
enabled = true
perform_staging = false
}
}
Now, we should create the terraform maps for the signatures.
please note that, unlike the "parameters" and "urls" data sources, the "signatures" data sources are "connected", which means that we need here to specify the terraform provider (the BIG-IP address and the credentials). This is because, the terraform needs to read the BIG-IP configuration, to check the attack signature presence and to collect the documentation from it.
variable "signatures" {
type = map(object({
signature_id = number
enable = bool
perform_staging = bool
description = string
}))
}
data "bigip_waf_signatures" "map_signatures" {
provider = bigip.qa
for_each = var.signatures
signature_id = each.value["signature_id"]
description = each.value["description"]
enabled = each.value["enabled"]
perform_staging = each.value["perform_staging"]
}
finally, we need to update the bigip_waf_policy terraform resource:
resource "bigip_waf_policy" "s4_qa" {
provider = bigip.qa
application_language = "utf-8"
partition = "Common"
name = "scenario4"
template_name = "POLICY_TEMPLATE_FUNDAMENTAL"
type = "security"
policy_import_json = data.http.scenario4.body
signatures = [ for k,v in data.bigip_waf_signatures.map_qa: v.json ]
}
Demo Video