Skip to content

Add a CRS observation disable action with a persistent pre-change snapshot - #78

Merged
mangyan1 merged 2 commits into
developfrom
feat/crs-disable
Oct 3, 2026
Merged

mangyan1 merged 2 commits into
developfrom
feat/crs-disable

Conversation

@mangyan1

@mangyan1 mangyan1 commented Oct 3, 2026

Copy link
Copy Markdown
Owner

What

The CrowdSec → WAF page could turn CRS observation on but never off. This adds the disable action end to end, mirroring the enable path's transactional design:

  • Host daemon (installer v1.69): a DISABLE socket verb. It removes the observation policy file, the npmplus/crs-observe acquisition entry, and the NPMPLUS_CRS_MODE marker from the bouncer config, then config-tests (crowdsec -t), restarts CrowdSec, and waits for health. Any failure restores the enabled configuration and reports failed; a failed restore reports rollback-failed. A disable while idle is an accepted no-op, and the same 5-minute cooldown applies. The Hub rule package is left installed but inert — removing it would be Hub surgery with no behavioral gain.
  • Persistent pre-change snapshot: both ENABLE and DISABLE now copy the CrowdSec config tree and the bouncer file to /var/lib/npmplus/crs-control/snapshot/ before touching anything. It survives a successful run (last known-good instance) and a failed rollback (manual recovery), instead of the enable path's old temp-dir snapshot that vanished with the process. Uninstall removes it along with the helper's other state.
  • Backend: DELETE /api/crowdsec/crs — admin-only, audited before acting (action disable-requested), sharing the enable route's rate-limit bucket and reason mapping (busy/unsupported → 409, cooldown → 429). The shared request path in crs-control.js is factored out.
  • GUI: the single button morphs with the observation state (outline-danger "Disable CRS observation" when enabled), a confirm modal explains the restart and what stays enabled, and running-phase labels distinguish enabling from disabling.

Why

Requested after the enable button shipped: once observation was on there was no supported way back to the pre-CRS detection behavior short of hand-editing host files, and any future regression in the change path had no on-host fallback instance to recover from.

Verification

  • tests/crs-control.py: 16/16 in CI-replica docker — disable round trip restores the exact pre-observation acquisition and keeps a snapshot; failed disable restores the enabled state and keeps the cooldown; snapshot survives a failed recovery; idempotent no-op and unsupported-state refusals; socket verb pins.
  • Backend: full pnpm test (226 tests) in CI-replica docker, including the new crs-disable.test.js (own process so the shared rate bucket doesn't shift api.test.js's enable boundary) — 403/401/cross-origin guards, audit-before-act, 500 on audit failure without reaching the host, 429 at the bucket limit.
  • Frontend in CI-replica docker: biome, locale sort (jq, diff-clean), typecheck, 24 tests, vite build.
  • Smoke workflow gains a disable round-trip step after the enabled-state assertions: socket DISABLE → idle, marker/acquisition/policy gone, Hub rules still installed, bouncer key intact, snapshot present, CrowdSec healthy; ui-driver checks the disable button renders while enabled.
  • shellcheck clean on the installer.

…pshot

The WAF page could enable CRS observation but never turn it off. The
daemon gains a DISABLE verb that removes the observation policy,
acquisition entry, and bouncer marker, then restarts CrowdSec and
waits for health, restoring the configuration on failure. Both verbs
now write a snapshot of the CrowdSec configuration tree and bouncer
file under /var/lib/npmplus/crs-control/snapshot before touching
anything; it survives the run as the known-good instance for manual
recovery, and the uninstaller removes it with the helper's other
state.

The GUI button toggles with the observation state, the route is
admin-only and shares the control rate limit and audit trail, and the
Hub rule package is left installed but inert so disabling never
involves Hub surgery.
Only the defaults matrix runs the --enable-crs safe-update step that the
round trip depends on; in the alternate matrix observation was never
enabled, the daemon's accepted no-op left no snapshot to assert about,
and the step failed.
@mangyan1
mangyan1 merged commit b3aa6ed into develop Oct 3, 2026
19 checks passed
@mangyan1
mangyan1 deleted the feat/crs-disable branch October 3, 2026 21:11
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.

1 participant