Getting started with WAF Policy automation on BIG-IP AWAF
The continued rise of cyber attacks is demanding more from security teams within organizations. Zero-day exploits that affected the likes of Log4j and Spring4Shell have left operation teams scrambling to ensure their WAF policies are up-to-date and capable of mitigating attacks targeting those exploits. To be better prepared for future incidents, one avenue is to automate the process of deploying and updating WAF policies, both time-consuming processes for organizations with a large number of applications and WAF appliances.
With the introduction of F5 OneWAF, organizations have additional justification to invest in automating WAF policy configuration. OneWAF's aim to have a unified WAF policy definition across the F5 WAF portfolio helps ensure much of the automation efforts can be reused as the WAF delivery mechanism evolves along with your applications, be it through BIG-IP Advanced WAF (AWAF), F5 NGINX App Protect WAF or F5 Distributed Cloud.
In this article, I will provide some technical guidance on how to get started with automating WAF policy configurations on BIG-IP AWAF.
The Tools
I will be discussing the use of Ansible to automate the WAF policy configurations, as F5 has Ansible collections f5_modules and f5_bigip to provide abstraction to the underlying REST APIs for configuring BIG-IPs. Ansible also supports the Jinja2 templating language which can be used to dynamically generate the content for every WAF policy.
Starting from BIG-IP AWAF v15, WAF or Application Security policies can be defined declaratively in JSON format, which is much more human-readable compared to the XML format used in prior versions. That said, the rest of the article should also be applicable to the XML definition of the WAF policies.
Lastly, from BIG-IP v16 onwards, any WAF policy configured can be exported in JSON format from BIG-IP AWAF Web GUI, should you need a base policy to work with:
Identifying Common Patterns
- Security can look complex to the uninitiated, so the key to successful adoption of security practices is to simplify the implementation.
- It is important to have some governance in place to ensure compliance across the organization, but also flexible enough to handle edge cases.
In the context of securing the applications with WAF policies, the points above can be represented in the form of WAF policy templates that define much of the required core rule sets, and support customizations for additional rules and behaviours as required.
For organizations already using BIG-IP AWAFs, the existing WAF policies on the BIG-IP AWAFs likely have some common configurations, due to company policies dictating core rule sets. The common configurations can be used as the basis of the WAF policy templates.
For those configuring their first WAF policy, the policy below (credit to Shain Singh) is a good starting point to protect applications from bots and targeted attacks (using Threat Campaign), without generating too many false positives as the broader attack signatures (All Signatures signature set) have been disabled:
{
"policy": {
"name": "api_gateway_policy",
"template": {
"name": "POLICY_TEMPLATE_NGINX_BASE"
},
"applicationLanguage": "utf-8",
"enforcementMode": "blocking",
"blocking-settings": {
"violations": [{
"name": "VIOL_THREAT_CAMPAIGN",
"alarm": true,
"block": true
}
]
},
"signature-sets": [{
"name": "All Signatures",
"block": false,
"alarm": false
}
],
"bot-defense": {
"settings": {
"isEnabled": true
},
"mitigations": {
"classes": [{
"name": "trusted-bot",
"action": "alarm"
}, {
"name": "untrusted-bot",
"action": "block"
}, {
"name": "malicious-bot",
"action": "block"
}
]
}
}
}
}
Building a WAF Policy Template
Whilst the F5 WAF policy schema supports a large number of protection mechanisms, we should aim to reduce the number of variables to ensure the WAF policies are manageable. The sample policy above can be broken down to three sections:
- Threat Campaign
- All attack signatures
- Bot mitigation
Not every application requires all three protection mechanisms, nor does each mechanism need to behave in the same way for every application. With that in mind, here is an example of a template written in Jinja2:
{
"policy": {
"name": "{{ policy.name }}",
"template": {
"name": "POLICY_TEMPLATE_FUNDAMENTAL"
},
"general": {
"trustXff": "true"
},
{% if policy.threat_campaign %}
"blocking-settings": {
"violations": [{
"name": "VIOL_THREAT_CAMPAIGN",
"alarm": true,
"block": true
}
]
},
{% endif %}
"signature-sets": [{
"name": "All Signatures",
"block": {{ 'block' in policy.all_sigs_actions }},
"alarm": {{ 'alarm' in policy.all_sigs_actions }}
}
],
"bot-defense": {
"settings": {
"isEnabled": true
},
"mitigations": {
"classes": [{
"name": "trusted-bot",
"action": "alarm"
}, {
"name": "untrusted-bot",
"action": "{% if policy.allow_untrusted_bots %}alarm{% else %}block{% endif %}"
}, {
"name": "malicious-bot",
"action": "block"
}
]
}
},
"applicationLanguage": "utf-8",
"enforcementMode": "blocking"
}
}
The protection mechanisms are configurable on a per-policy level with the following Ansible variables:
- policy.all_sigs_actions
- policy.threat_campaign
- policy.allow_untrusted_bots
Below is an example of an Ansible host variable in the inventory, defining two WAF policies with different values for the Ansible variables, to meet different security requirements:
# inventory/bigip-01.yaml
app_sec_policies:
- name: shopping_site_policy
all_sigs_actions: ["alarm", "block"]
threat_campaign: true
allow_untrusted_bots: false
- name: info_api_policy
all_sigs_actions: ["alarm"]
threat_campaign: false
allow_untrusted_bots: true
- shopping_site_policy for a user facing site for customers to make purchases.
The policy has policy.threat_campaign enabled and policy.all_sigs_actions is set to alarm and block, as there may be concerns about running an unpatched web shop application leading to financial losses. policy.allow_untrusted_bots is disabled to protect against the bot attacks like Sneaker Bots preventing real users from making purchases. - info_api_policy for a non-business critical, read only API, made to be accessible to anyone and does not deal with any sensitive information.
This API is likely to be consumed by automation or bots (policy.allow_untrusted_bots enabled), and as this is not a critical API, may not require the targeted protection offered by F5 Threat Campaign (policy.threat_campaign disabled). policy.all_sigs_actions is only set to alarm, not block, to ensure API is always accessible to most consumers. Vulnerabilities highlighted from the security logs can be reviewed and patched in a non-urgent manner.
Applying a WAF Policy
With the WAF policy templates and inventory defined for every BIG-IP AWAF, the WAF policies can be generated and applied on BIG-IP AWAFs using the bigip_asm_policy_import module in the Ansible playbook:
# Ansible Task looping through app_sec_policies variable, with loop_var="policy"
- name: Create policy JSON file on AWAF
copy:
dest: "/tmp/{{ policy.name }}.json"
content: "{{ policy_template }}"
delegate_to: localhost
- name: Import policy from JSON file
bigip_asm_policy_import:
name: "{{ policy.name }}"
force: true
source: "/tmp/{{ policy.name }}.json"
provider:
server: bigip01.local.net
user: admin
password: secret
delegate_to: localhost
Once the policies have been created on the BIG-IP AWAF, they can then be attached to Virtual Servers. Whenever changes need to be made on the WAF policies, the Ansible playbook can be re-run, and the declarative nature of the API will ensure the configuration on BIG-IP AWAF is updated accordingly.
Closing
I hope the article has given you some confidence to take your first steps in introducing automation to securing your application with WAF policies and enabling your organization to react to new threats more effectively.
More importantly, remember to involve all stakeholders along your journey to garner support for when the time comes to roll out the solution, and that your work is aligned with the overarching strategy to stand the test of time.
The "provider" is no longer an option as F5 wants us to use "connection: httpapi" that has predefined variables.
Can I use refence a local file on the Ansible station and push it with bigip_asm_policy_import? I seem to get some errors as I think that the inline: option does not support json files but just XML and this limits the options .
tasks:
- name: Import ASM policy inline
bigip_asm_policy_import:
name: foo-policy4
inline: "{{ lookup('file','asm_policy.json') }}"