Skip to main content

Protect a site with a WAF firewall and rulesets

You create a firewall and a reusable ruleset, bind the ruleset and enable the firewall on a group.

Note: Balancer is in beta. Endpoints, fields and behavior may change before general availability, and the feature must be enabled on your account before use. Contact your NetActuate account manager to request access.

Prerequisites​

  • Balancer access and a key in the BALANCER_API_KEY environment variable with permission for these operations.
  • The API host shown in the Balancer portal. Replace <your-api-host> in every example with that host.
  • A shell, curl and Python 3 for examples that build JSON from environment variables.
  • An HTTP group and a ruleset whose rules you have reviewed. Set WAF_RULESET_ID to that ruleset identifier.

Resource identifiers come from your API responses. Set each variable when its value is available, before the first step that uses it.

Environment variableValue
GROUP_IDReturned resource identifier, or your selected scope identifier.
HTTP_LB_IDReturned resource identifier, or your selected scope identifier.
WAF_FIREWALL_IDReturned resource identifier, or your selected scope identifier.
WAF_RULESET_IDReturned resource identifier, or your selected scope identifier.

For an identifier, enter the value returned for your resource and export it:

read -r -p 'GROUP_ID: ' GROUP_ID
export GROUP_ID

Steps​

  1. Create a firewall.

    curl --silent --show-error --include \
    --request POST \
    --url "https://<your-api-host>/waf-firewalls" \
    --header "x-api-key: $BALANCER_API_KEY" \
    --header "Content-Type: application/json" \
    --data-binary '{"name":"site_firewall","crsSensitivity":"medium"}'

    API reference: Create a new WAF firewall

    Expected: HTTP 200. The following shows the response fields to check; additional data depends on your saved resources. Resource identifiers are illustrative.

    {
    "code": 200,
    "result": "success",
    "data": {
    "wafFirewallId": 1
    }
    }

    Set WAF_FIREWALL_ID to the returned wafFirewallId.

  2. Create a ruleset if you do not have one.

    curl --silent --show-error --include \
    --request POST \
    --url "https://<your-api-host>/waf-rulesets" \
    --header "x-api-key: $BALANCER_API_KEY" \
    --header "Content-Type: application/json" \
    --data-binary '{"name":"site_rules"}'

    API reference: Create a new private WAF ruleset

    Expected: HTTP 200. The following shows the response fields to check; additional data depends on your saved resources. Resource identifiers are illustrative.

    {
    "code": 200,
    "result": "success",
    "data": {
    "wafRulesetId": 1
    }
    }

    Set WAF_RULESET_ID to the returned wafRulesetId. A new ruleset needs rules before it adds custom inspection.

  3. Read and review your rules.

    curl --silent --show-error --include \
    --request GET \
    --url "https://<your-api-host>/waf-rulesets/$WAF_RULESET_ID/rules" \
    --header "x-api-key: $BALANCER_API_KEY"

    API reference: Returns the ordered list of readable WAF rules inside one ruleset

    Expected: HTTP 200. The following shows the response fields to check; additional data depends on your saved resources. Resource identifiers are illustrative.

    {
    "code": 200,
    "result": "success"
    }

    TODO(verify): A complete custom WAF expression rule with its accepted action object.

  4. Bind the reviewed ruleset to the firewall.

    python3 - <<'PYBODY' | \
    curl --silent --show-error --include \
    --request POST \
    --url "https://<your-api-host>/waf-firewalls/$WAF_FIREWALL_ID/rulesets" \
    --header "x-api-key: $BALANCER_API_KEY" \
    --header "Content-Type: application/json" \
    --data-binary @-
    import json, os
    print(json.dumps({'priority': 10, 'wafRulesetId': int(os.environ['WAF_RULESET_ID'])}))
    PYBODY

    API reference: Create an ordered ruleset binding inside a WAF firewall

    Expected: HTTP 200. The following shows the response fields to check; additional data depends on your saved resources. Resource identifiers are illustrative.

    {
    "code": 200,
    "result": "success",
    "data": {
    "wafRulesetBindingId": 1
    }
    }
  5. Observe matches in shadow mode.

    python3 - <<'PYBODY' | \
    curl --silent --show-error --include \
    --request PATCH \
    --url "https://<your-api-host>/http-loadbalancers/$HTTP_LB_ID/groups/$GROUP_ID" \
    --header "x-api-key: $BALANCER_API_KEY" \
    --header "Content-Type: application/json" \
    --data-binary @-
    import json, os
    print(json.dumps({'defaultWafFirewallId': int(os.environ['WAF_FIREWALL_ID']), 'defaultWafMode': 'shadow'}))
    PYBODY

    API reference: Update an HTTP group configuration

    Expected: HTTP 200. The following shows the response fields to check; additional data depends on your saved resources. Resource identifiers are illustrative.

    {
    "code": 200,
    "result": "success",
    "data": {
    "groupId": 1
    }
    }
  6. After reviewing request observations, enable enforcement.

    curl --silent --show-error --include \
    --request PATCH \
    --url "https://<your-api-host>/http-loadbalancers/$HTTP_LB_ID/groups/$GROUP_ID" \
    --header "x-api-key: $BALANCER_API_KEY" \
    --header "Content-Type: application/json" \
    --data-binary '{"defaultWafMode":"enforce"}'

    API reference: Update an HTTP group configuration

    Expected: HTTP 200. The following shows the response fields to check; additional data depends on your saved resources. Resource identifiers are illustrative.

    {
    "code": 200,
    "result": "success",
    "data": {
    "groupId": 1
    }
    }
  7. Read the final assignment.

    curl --silent --show-error --include \
    --request GET \
    --url "https://<your-api-host>/http-loadbalancers/$HTTP_LB_ID/groups/$GROUP_ID" \
    --header "x-api-key: $BALANCER_API_KEY"

    API reference: Get HTTP group

    Expected: HTTP 200. The following shows the response fields to check; additional data depends on your saved resources. Resource identifiers are illustrative.

    {
    "code": 200,
    "result": "success",
    "data": {
    "groupId": 1
    }
    }

Verify​

Send a controlled request that matches one reviewed rule and a normal request. Compare rule observations with the selected mode and action. A match alone does not establish a block. Custom inspection runs before managed inspection.

Use your configured site domain in this traffic check:

curl --silent --show-error --include "https://example.com/"

Expected: the site behavior described above. The application response body and status depend on your origin and selected policies.

An API mutation saves desired configuration first. Readback and a live traffic check answer different questions. No fixed propagation time is established.

Troubleshooting​

SymptomCauseFix
Rule matches but traffic continuesShadow mode, rule action or a bypass permits itCheck the effective assignment, mode, action and allow policies.
Request returns 502Security configuration or body handling failsRead validation state and inspect body capture settings.
Custom rule does not appearBinding or effective route differsCheck ruleset binding, priority and route overrides.

Limits and defaults​

SettingLimit or default
Firewall sensitivityDefault medium; low, medium or high
Paranoia level1 to 4
Ruleset binding priorityRequired; 1 to 100000000
Expression textMaximum 8192 bytes
Body inspection limit1024 to 1048576 bytes
Body capture timeout50 to 5000 ms

API status codes​

StatusMeaning
200Request succeeds; inspect returned data.
400Invalid arguments or configuration.
401Authentication or authorization denied.
404Resource or parent is unavailable.
409Resource state conflicts with the operation.
412Inspect the returned error details.
500The request encounters a server error.
  • DELETE /waf-firewalls/{wafFirewallId}
  • DELETE /waf-firewalls/{wafFirewallId}/dearm-rules/{wafDearmRuleId}
  • DELETE /waf-firewalls/{wafFirewallId}/rulesets/{wafRulesetBindingId}
  • DELETE /waf-rulesets/{wafRulesetId}
  • DELETE /waf-rulesets/{wafRulesetId}/rules/{wafRuleId}
  • GET /http-loadbalancers/{httpLbId}/groups/{groupId}
  • GET /waf-firewalls
  • GET /waf-firewalls/{wafFirewallId}
  • GET /waf-firewalls/{wafFirewallId}/dearm-rules
  • GET /waf-firewalls/{wafFirewallId}/dearm-rules/{wafDearmRuleId}
  • GET /waf-firewalls/{wafFirewallId}/rulesets
  • GET /waf-firewalls/{wafFirewallId}/rulesets/{wafRulesetBindingId}
  • GET /waf-rulesets
  • GET /waf-rulesets/{wafRulesetId}
  • GET /waf-rulesets/{wafRulesetId}/rules
  • GET /waf-rulesets/{wafRulesetId}/rules/{wafRuleId}
  • PATCH /http-loadbalancers/{httpLbId}/groups/{groupId}
  • PATCH /waf-firewalls/{wafFirewallId}
  • PATCH /waf-firewalls/{wafFirewallId}/dearm-rules/{wafDearmRuleId}
  • PATCH /waf-firewalls/{wafFirewallId}/rulesets/{wafRulesetBindingId}
  • PATCH /waf-rulesets/{wafRulesetId}
  • PATCH /waf-rulesets/{wafRulesetId}/rules/{wafRuleId}
  • POST /http-loadbalancers/{httpLbId}/groups/{groupId}/routes/{routeId}/waf/statistics
  • POST /http-loadbalancers/{httpLbId}/groups/{groupId}/waf/statistics
  • POST /http-loadbalancers/{httpLbId}/waf/statistics
  • POST /waf-firewalls
  • POST /waf-firewalls/{wafFirewallId}/dearm-rules
  • POST /waf-firewalls/{wafFirewallId}/rulesets
  • POST /waf-rulesets
  • POST /waf-rulesets/{wafRulesetId}/rules

API reference​

Need Help?​

Contact your account manager or use the Balancer portal.