Download the release bundle from Download Edge-Native Defender. New to Edge-Native Defender? Start with Edge-Native Defender overview.
At a glance
| Deployment | A Lua script and an engine module added to the HAProxy you already run, and a few lines in haproxy.cfg, added by one installer command. No extra service, no routing changes. |
| Added latency | About 1 ms at the median. No call to Cequence on the request path. |
| Cost | About 1.1 CPU cores per 1,000 requests a second and about 100 MB of memory, on the HAProxy you already run. Nothing from Cequence for the infrastructure. |
| If something fails | Your APIs stay up. If Cequence is unreachable, Defender keeps enforcing its last policies; if Defender cannot start, HAProxy forwards requests unchanged. |
| Upgrades | Run the new bundle's installer. Roll back by running the previous one. |
Deployment architecture
Defender runs inside HAProxy: a Lua script HAProxy loads, and an engine module it calls. Each request is decided on the HAProxy thread that already holds it, from a policy set kept in memory, so there is no extra hop and no network call on the request path. It is the same engine as Defender for Kong and Defender for nginx.
BEFORE
client ---> HAProxy ----------------------------------> your backends
+-----------------------------+
| your frontends and rules |
+-----------------------------+
AFTER
client ---> HAProxy ----------------------------------> your backends
+----------+ +-----------------------------+
| Defender | | your frontends and rules |
| decides | | unchanged |
+----------+ +-----------------------------+
|
+---> your Cequence tenant: policies in, analytics out,
in the background
What stays the same. Your frontends, backends, ACLs and routing rules. Defender's lines go at the top of each HTTP frontend, so it sees each request as the client sent it, before your own rules change it.
What Defender does with a request. A blocked request is answered by HAProxy and never reaches your backend. An allowed request is forwarded byte for byte, and your client receives the response byte for byte: Defender inspects a copy of each body, up to your tenant's capture settings, and never changes what it forwards. Responses are never held back for inspection.
What is sent to Cequence. For each request: its headers and URL, the response status and headers, and the inspected body copies, masked first with your tenant's Sensitive Data rules. Your traffic filters decide whether a request is reported at all. Until the rules have been fetched, nothing is sent unmasked.
Installation
Prerequisites
- HAProxy 2.8 or later built with Lua, which
haproxy -vvshows as+LUA, on Linux (amd64 or arm64) with glibc 2.28 or later. The officialhaproxyimages and Debian's own package qualify. Tested on HAProxy 3.0 (Lua 5.4) and 2.8 (Lua 5.3); the bundle carries a module for each. - Root access on the HAProxy host, for the installer.
- Outbound HTTPS from every HAProxy host to your Cequence tenant, directly (an outbound proxy is not supported on HAProxy).
- Your tenant's name, client ID and client secret.
- The
defender-for-haproxyrelease bundle. It holds the installer (bin/defenderctl-haproxy), the script (defender.lua), the modules for each architecture (lib/),defender.conf.example, aDockerfileandhaproxy.cfgfor trying it in Docker, and aREADME.mdwith the full reference.
1. Configure
From the unpacked bundle, on the HAProxy host:
cp defender.conf.example defender.conf
Set at least:
| Key | What it is |
CEQUENCE_TENANT |
Your tenant, for example acme.1
|
CEQUENCE_CLIENT_ID |
Your tenant's client ID |
HAPROXY_FRONTENDS |
The frontends to protect, comma-separated. Leave it unset to protect every frontend that is not mode tcp
|
HAPROXY_CONFIG (default /etc/haproxy/haproxy.cfg) and HAPROXY_SERVICE
(default haproxy) name your configuration and systemd unit if they differ.
The client secret is never written in this file:
export CEQUENCE_CLIENT_SECRET='<client secret>'
2. Check
sudo -E ./bin/defenderctl-haproxy preflight
It checks that your credentials sign in to your tenant, that your HAProxy is 2.8 or later with Lua, and lists the frontends Defender will go in.
3. Install
sudo -E ./bin/defenderctl-haproxy install
The installer:
- copies the engine to
/usr/local/lib/defender/anddefender.luanext to yourhaproxy.cfg; - writes Defender's settings, the client secret among them, to
/etc/defender/haproxy.conf, readable by root only; - adds Defender's lines to
haproxy.cfgbetween# BEGIN defenderand# END defendermarkers, inglobaland at the top of each frontend it protects, keeping the previous file ashaproxy.cfg.defender-backup; - checks the result with
haproxy -c, leavinghaproxy.cfguntouched if the check fails; - reloads HAProxy. A reload is graceful: requests in flight finish.
Within a few seconds Defender has fetched your policies and is enforcing them.
Why the installer adds insecure-fork-wanted. Defender applies policy
updates continuously, every few seconds while an attack is being mitigated, and
does that on one background thread of its own so HAProxy's threads go on
serving requests. HAProxy lets a running process start a thread only with
insecure-fork-wanted in global: HAProxy's standard setting for extensions
that need one, which its own external-check health checks need too. It does
not change how HAProxy handles traffic. Defender starts exactly one thread and
never creates processes or runs commands. Without the line Defender still
decides every request, but each HAProxy thread pauses while it applies each
update, and geo-match policies do not take effect.
Or try it with Docker
From the unpacked bundle, for a tenant named acme.1:
docker build -t haproxy-with-defender . docker run -d --name defender -p 8080:8080 \ -e CEQUENCE_ORIGIN=https://api.internal \ -e CEQUENCE_EDGE_URL=https://edge.acme.1.cequence.cloud \ -e CEQUENCE_AUTH_URL=https://auth.acme.1.cequence.cloud/auth/realms/cequence/protocol/openid-connect/token \ -e CEQUENCE_CLIENT_ID=<client id> \ -e CEQUENCE_CLIENT_SECRET=<client secret> \ haproxy-with-defender
Requests to port 8080 are inspected and forwarded to CEQUENCE_ORIGIN: a URL
(https:// is reached over TLS, its certificate verified), or host:port for
plain HTTP.
Your own configuration management
./bin/defenderctl-haproxy render prints the lines to add without changing
anything, for a haproxy.cfg you manage with your own tools:
global lua-load-per-thread /etc/haproxy/defender.lua /etc/defender/haproxy.conf insecure-fork-wanted httpclient.ssl.verify required httpclient.ssl.ca-file @system-ca tune.bufsize 32768 frontend web option http-buffer-request filter lua.defender http-request lua.defender_request
Keep http-request lua.defender_request before your frontend's own
http-request rules.
Settings
| Key in defender.conf | What it does |
CEQUENCE_NAMESPACE |
Names this deployment in your tenant (haproxy-<namespace>). Default prod
|
CEQUENCE_DIAGNOSTICS |
yes serves /__end/status and answers x-end-echo. Off by default |
CEQUENCE_ALTERNATE_HOST_HEADER |
The header a load balancer in front puts the real host in, when it rewrites Host
|
HAPROXY_CLIENT_IP_HEADER |
One header carrying the client address. Unset, Defender finds it as the managed Defender does: your tenant's true client IP headers (set in Cequence), else the left end of X-Forwarded-For, else the connecting address |
HAPROXY_TRUE_CLIENT_IP_CONFIG |
A list of client address headers in place of your tenant's, such as True-Client-IP,X-Forwarded-For:-1
|
Geo-match policies (country, ASN, organization) need nothing: Defender pulls the geo databases from Cequence, as the managed Defender does.
4. Check it before you go live
- Set
CEQUENCE_DIAGNOSTICS=yes, run the installer again, and request/__end/statusthrough HAProxy. Within a few secondspolicies_loadedshows your tenant's policy count. - Send a request that one of your policies blocks, and check it is blocked.
- Set
CEQUENCE_DIAGNOSTICS=noand run the installer again. The status page is readable by anyone who can reach the frontend.
./bin/defenderctl-haproxy status shows what is installed at any time.
Upgrades and rollback
Each release publishes a new defender-for-haproxy bundle. Unpack it, copy
your defender.conf into it, and run its installer:
sudo -E ./bin/defenderctl-haproxy upgrade
It replaces the module and the script and reloads HAProxy gracefully; requests in flight finish, and the new process decides with your policies within a few seconds. To roll back, run the previous bundle's installer the same way.
Performance and sizing
| Added latency, median | about 1 ms |
| Added latency, 95th percentile | about 1 ms at 200 requests a second, about 3 ms at 500 |
| Added CPU | about 1.1 cores per 1,000 requests a second |
| Added memory | about 100 MB per host |
Measured on HAProxy 3.0 with 8 threads on an 8 vCPU host, at 200 and 500
requests a second, with analytics and masking on and 2 KB JSON responses. More
policies, larger bodies or more Sensitive Data rules raise the cost, so
measure with your own traffic. Each HAProxy thread keeps its own copy of your
policies, so memory grows with nbthread.
The request body Defender reads is bounded by tune.bufsize, which the
installer sets to 32 KB; raise it if your tenant's request capture size is
above 16 KB.
Cost
Cequence charges nothing for the infrastructure. Defender runs on the HAProxy hosts you already operate and adds the CPU and memory above.
Availability
- If Cequence is slow or unreachable, your traffic is unaffected: no request waits on Cequence. Defender keeps enforcing the policies it last received.
- If Defender itself fails, HAProxy keeps serving: an engine that cannot be loaded or a missing setting is logged, and requests are forwarded unchanged.
-
If you need Defender off the path at once, run
sudo ./bin/defenderctl-haproxy uninstall, or delete the lines between the# BEGIN defenderand# END defendermarkers and reload HAProxy. - A fresh install blocks nothing until your tenant's first policy publish arrives.
Known limits
-
block,add-header,challengeandrate-limitare carried out. Challenge pages are served on your own hostname at/__cq/auth. A rate limit is counted per HAProxy process, shared by all of its threads. - A
redirect(honeytrap) policy forwards the request and logs that it did. - An outbound proxy to Cequence is not supported.
- HAProxy built with Lua linked statically cannot load the engine;
preflightsays so.
If something is wrong
Requests keep flowing, and HAProxy's log says why on lines starting with
defender:.
| Log message | What to do |
cannot load /usr/local/lib/defender/libdefender_haproxy.so |
The engine is missing, built for the other CPU architecture, or your HAProxy links Lua statically |
tenant connection refused |
The tenant settings are empty or wrong; run preflight
|
token request failed or data request failed
|
HAProxy cannot reach the host named. Check outbound HTTPS from the host and your credentials |
no worker thread (HAProxy forbids it without insecure-fork-wanted) |
insecure-fork-wanted is missing from global; run the installer again |
The bundle's README.md has the full reference.
Removing it
sudo ./bin/defenderctl-haproxy uninstall
It takes Defender's lines out of haproxy.cfg (checking the result first),
reloads HAProxy, and removes the module, the script and the settings file.