Skip to content

feat(waf): CrowdSec WAF and IP banning for DHIS2 - #113

Open
0xafrogeek wants to merge 6 commits into
mainfrom
feat/crowdsec-waf
Open

0xafrogeek wants to merge 6 commits into
mainfrom
feat/crowdsec-waf

Conversation

@0xafrogeek

@0xafrogeek 0xafrogeek commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

New crowdsec role: CrowdSec Security Engine with AppSec (WAF) and the OpenResty bouncer, colocated on the [web] host. Off by default; crowdsec_enabled=true under [web:vars].

  • IP banning from the proxy access log (scanners, probing, traversal, brute force) plus the community blocklist (Central API, opt-out with crowdsec_online_api_enabled=false).
  • AppSec in-band blocking of known-CVE and generic attacks; fails open; bodies over 10 MiB pass through so DHIS2 imports keep working.
  • Managed allowlist (crowdsec_allowlist) for shared egress IPs, optional captcha remediation, Prometheus metrics exposed only to the [monitoring] host, systemd memory ceiling.
  • Switch-off paths: bouncer only, or the whole engine (role-managed hosts only), both idempotent.
  • The run asserts the bouncer actually pulls decisions after each deploy, so a silently non-enforcing bouncer fails the playbook.

Requires proxy=openresty and ansible_connection=lxd; the run fails in pre-flight otherwise. Operator guide: docs/CrowdSec-WAF.md.

Testing

  • molecule test -s crowdsec (new scenario: converge, idempotence, verify incl. AppSec block, manual ban enforced through the bouncer, allowlist, metrics binding, conf mode, memory limit; side effect: bouncer off, engine off, rerun no-op). Green on Ubuntu 24.04 (CI amd64 and local arm64) and 22.04 (local arm64). The CI step is blocking.
  • The pre-existing molecule/default scenario fails in prepare (instance unreachable) on main as well; its step stays non-blocking and is out of scope here.
  • Lab deployment on an existing OpenResty server: upgrade path, idempotent rerun, real public-IP request (client IP preserved, /.env 403), 0 AppSec false positives on login app and API traffic, allowlist, metrics exposure on/off, both switch-off paths.
  • ansible-lint --profile moderate: no findings in the new role or scenario.

Follow-ups (separate PRs)

  • OpenResty log rotation (access.log, error.log, perf.log are not rotated today).
  • unattended-upgrades origins for the CrowdSec and OpenResty repositories.
  • CrowdSec Grafana dashboard and alert rules.
  • Fix the default Molecule scenario (unreachable instance in prepare) and make its step blocking too.

0xafrogeek and others added 5 commits October 3, 2026 00:01
documentation/ holds session handoffs, research notes and review
harnesses that stay on the contributor's machine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opt-in with crowdsec_enabled on [web] hosts (LXD only). Installs the
Security Engine and the OpenResty bouncer, with AppSec on by default
(virtual-patching and generic rules). Request bodies over the AppSec
limit are inspected partially, so DHIS2 imports up to 100M still pass.

Also adds crowdsec_allowlist, an optional Prometheus scrape job, and
removal when the bouncer or CrowdSec is switched off.
…ne memory

- Set the bouncer conffile to 0600; the package ships it world-readable
  with the LAPI API key inside. Only the OpenResty master reads it.
- After the final reload, wait for a stream-mode pull newer than the
  check start and fail with a diagnosis otherwise. A bouncer that never
  pulls (edited lua conf, stale key, wrong API_URL) used to let every
  request through silently.
- Add crowdsec_memory_max (default 1G) as a systemd drop-in, removed on
  engine removal; daemon-reload on restart.
- State what AppSec inspects over the body limit: form bodies up to the
  limit, JSON bodies not at all; URI, query and headers always.
- Molecule verify: enforce a manual ban through the bouncer, check the
  conf mode and the memory ceiling.
@0xafrogeek 0xafrogeek changed the title feat(crowdsec): CrowdSec WAF and IP banning for OpenResty feat(waf): CrowdSec WAF and IP banning for OpenResty Oct 5, 2026
…o blocking

The engine runs as root, so its acquisition, AppSec and systemd drop-in
files need no world-read bit. The apt keyring stays readable for apt.
The crowdsec Molecule step has a green run on GitHub; drop the
non-blocking fallback.
@sonarqubecloud

sonarqubecloud Bot commented Oct 5, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
2 New issues
2 New Vulnerabilities (required ≤ 0)
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Comment thread deploy/roles/crowdsec/tasks/install-engine.yml Dismissed
Comment thread deploy/roles/crowdsec/tasks/install-engine.yml Dismissed
@0xafrogeek 0xafrogeek changed the title feat(waf): CrowdSec WAF and IP banning for OpenResty feat(waf): CrowdSec WAF and IP banning for DHIS2 Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants