Scenario #4: Manage an F5 BIG-IP Advanced WAF Policy on different devices¶
The goal of this lab is to manage an F5 BIG-IP Advanced WAF Policy on multiple devices, including:
- Different standalone devices, serving the same applications.
- Different devices serving different purposes; for example, changes tested first in a BIG-IP QA/test environment before applying them into a production environment.
Pre-requisites¶
On the BIG-IP:
- BIG-IP version 16.1 or newer
- Advanced WAF Provisioned
- Credentials with REST API access
On Terraform:
- Using F5 BIG-IP provider version 1.15.0 or newer
- Using Hashicorp versions following Releases and versioning
Create a policy¶
Create 4 files:
variables.tf¶1 2 3 4
variable qa_bigip {} variable prod_bigip {} variable username {} variable password {}
inputs.auto.tfvars¶1 2 3 4
qa_bigip = "10.1.1.9:443" prod_bigip = "10.1.1.8:443" username = "admin" password = "whatIsYourBigIPPassword?"
main.tf¶1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
terraform { required_providers { bigip = { source = "F5Networks/bigip" version = "1.15" } } } provider "bigip" { alias = "qa" address = var.qa_bigip username = var.username password = var.password } provider "bigip" { alias = "prod" address = var.prod_bigip username = var.username password = var.password } data "http" "scenario4" { url = "https://raw.githubusercontent.com/fchmainy/awaf_tf_docs/main/0.Appendix/scenario4.json" request_headers = { Accept = "application/json" } } 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 } resource "bigip_waf_policy" "s4_prod" { provider = bigip.prod application_language = "utf-8" partition = "Common" name = "scenario4" template_name = "POLICY_TEMPLATE_FUNDAMENTAL" type = "security" policy_import_json = data.http.scenario4.body }
Note
The template name can be set to anything. When it is imported, the value is overwritten.
This example references an existing policy from a GitHub repository, but you can also create a policy from zero on both BIG-IPs.
Initialize, plan, and apply your new Terraform project.
foo@bar:~$ terraform init Initializing the backend... Initializing provider plugins... [...] Terraform has been successfully initialized! foo@bar:~$ terraform plan -out scenario4 > output_scenario4.1 foo@bar:~$ more output_scenario4.1 foo@bar:~$ terraform apply "scenario4"
For both BIG-IPs, verify that the two policies are present and consistent.
Simulate a WAF Policy workflow¶
A common workflow includes the following processes:
- Enforce attack signatures in the test environment.
- Verify that these changes do not break the application and identify potential false positives (FP).
- Finalize the changes in a test environment, before applying them to your production environment.
Enforce attack signatures on the test environment
In order to track attack signature changes, use a Terraform HCL map to add a signature list definition in the inputs.auto.tfvars file:
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 } }
Create a signatures.tf file with a map to all the attack signatures defined, previously:
variable "signatures" { type = map(object({ signature_id = number enabled = bool perform_staging = bool description = string })) } data "bigip_waf_signatures" "map_qa" { 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"] } data "bigip_waf_signatures" "map_prod" { provider = bigip.prod for_each = var.signatures signature_id = each.value["signature_id"] description = each.value["description"] enabled = each.value["enabled"] perform_staging = each.value["perform_staging"] }
This example defines two different maps: one for the test environment BIG-IP and one for the production environment BIG-IP, because the
bigip_waf_signatures
data source are linked to their BIG-IP for consistency. Unlike the parameters and URLs data sources, which are just “json payload generators”, the attack signature data sources must first read the existence of the signature IDs and their status on the BIG-IP, before applying a configuration change.Update the main.tf file:
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 ] } resource "bigip_waf_policy" "s4_prod" { provider = bigip.prod application_language = "utf-8" partition = "Common" name = "scenario4" template_name = "POLICY_TEMPLATE_FUNDAMENTAL" type = "security" policy_import_json = data.http.scenario4.body }
Plan and apply:
foo@bar:~$ terraform plan -out scenario4 > output_scenario4.2 foo@bar:~$ more output_scenario4.2 foo@bar:~$ terraform apply "scenario4"
Verify the changes
You can verify that the 5 attack signatures have been enabled and enforced on the scenario4 WAF Policy on the QA BIG-IP (first 5 lines in the attack signatures list of the Advanced WAF Policy).
In the application, identify that these last changes on the test device have introduced some false positives. Using the log events on the F5 BIG-IP Advanced WAF GUI, identify the following:
- The attack signature “200101558” should be disabled globally
- The attack signature “200003068” should be disabled for the “/U1” URL
- The attack signaure “200003067” should be enabled globally but disabled specifically for the parameter “P1”.
Finalize changes in the test environment
Do the following, to final changes before moving into a production environment:
inputs.auto.tfvars¶1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
signatures = { 200101559 = { signature_id = 200101559 description = "src http: (Header)" enabled = true perform_staging = false } 200101558 = { signature_id = 200101558 description = "src http: (Parameter)" enabled = false 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 } }
parameters.tf¶1 2 3 4 5 6 7 8
data "bigip_waf_entity_parameter" "P1" { name = "P1" type = "explicit" data_type = "alpha-numeric" perform_staging = true signature_overrides_disable = [200003067] //url = data.bigip_waf_entity_url.U1 }
urls.tf¶1 2 3 4 5 6
data "bigip_waf_entity_url" "U1" { name = "/U1" type = "explicit" perform_staging = false signature_overrides_disable = [200003068] }
Update the main.tf file:
main.tf¶1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
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 ] parameters = [data.bigip_waf_entity_parameter.P1.json] urls = [data.bigip_waf_entity_url.U1.json] } resource "bigip_waf_policy" "s4_prod" { provider = bigip.prod 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_prod: v.json ] parameters = [data.bigip_waf_entity_parameter.P1.json] urls = [data.bigip_waf_entity_url.U1.json] }
Plan and apply:
foo@bar:~$ terraform plan -out scenario4 > output_scenario4.3 foo@bar:~$ more output_scenario4.3 foo@bar:~$ terraform apply "scenario4"
What’s Next?