netscaler — Citrix NetScaler/ADC

Blocks remotely by binding IPs into a policy dataset on a NetScaler via the NITRO REST API — the equivalent of the fail2ban netscaler action, done with LWP::UserAgent rather than curl. The dataset does nothing by itself; responder (or similar) policies referencing it do the actual blocking.

[kur.adc]
backend = "netscaler"

[kur.adc.options]
host    = "netscaler.example.com"
user    = "nsroot"
pass    = "hunter2"
dataset = "banned_ips"

What it creates

One dataset value binding per banned IP:

PUT https://<host>/nitro/v1/config/policydataset_value_binding
{ "policydataset_value_binding": { "name": "<dataset>", "value": "1.2.3.4" } }

Unban is a DELETE on /nitro/v1/config/policydataset_value_binding/<dataset>?args=value:<ip>.

NetScaler-side setup — required before use

The policy dataset must already exist; init only verifies the API is reachable and auth works, it does not create the dataset. On the NetScaler:

add policy dataset banned_ips ipv4

(or type ipv6 / one dataset per family as your policies need), then wire it into enforcement, e.g. a responder policy dropping matching clients:

add responder policy kur_block "CLIENT.IP.SRC.EQUALS_ANY(\"banned_ips\")" DROP
bind lb vserver my_vserver -policyName kur_block -priority 10 -type REQUEST

Exactly how the dataset is consumed — responder policy, ACL expression, vserver or global binding — is your design; the kur only manages the membership.

A dedicated API user with just the permissions to manage dataset bindings is preferable to nsroot.

Requirements

Settings

Options

| option | default | what | |--------------|-------------------|-------------------------------------------------------------------| | host | (required) | hostname or IP of the appliance; /^[a-zA-Z0-9.\-\[\]:]+$/ | | user | (unset) | username for basic auth | | pass | (unset) | password for basic auth | | auth | (unset) | pre-base64ed user:pass, in place of user/pass (fail2ban ns_auth parity) | | dataset | <prefix>_<name> | the policy dataset holding the IPs; /^[a-zA-Z0-9_\-]+$/ | | scheme | https | http or https | | ssl_verify | 0 | verify the TLS certificate | | timeout | 30 | HTTP timeout in seconds |

host is required, as is either auth or both user and pass. Auth is sent as Authorization: Basic <base64> — built from user/pass at request time, or your auth value verbatim.

ssl_verify defaults off, matching the fail2ban action's curl -k (appliances commonly wear self-signed certificates). Off means verify_hostname => 0 — the connection is encrypted but the peer unauthenticated; anything that can MITM the path can harvest the credentials. Set ssl_verify = 1 if the appliance has a real certificate, and consider giving it one if not.

What each operation does

| operation | API traffic | |------------|--------------------------------------------------------------------------| | init | GET <scheme>://<host>/nitro/v1/config — verifies reachability and auth | | ban | the PUT policydataset_value_binding above | | unban | DELETE .../policydataset_value_binding/<dataset>?args=value:<ip> | | list | no API call — the kur's own ban book | | check | same probe as init | | flush | the DELETE per banned IP | | re_init | teardown (best effort), init, re-PUT every banned IP | | teardown | the DELETE per banned IP (ban book kept for re_init) |

HTTP-level failures die with the status and response body included — but NITRO errors that arrive with HTTP 200 are not parsed, so a misconfigured dataset name may only surface as a failed ban rather than at init.

self_heal and remote drift

check verifies the API answers and auth works — not that the dataset exists or that individual bindings survive. A binding removed on the appliance by hand stays removed until re_init; unlike the cloudflare backend, unbanning an already-missing binding is an error (the DELETE fails), which the kur logs and reports.

Gotchas