Repository navigation
fix(waf): detect stale seed config and report version skew (#312, #316) - #385
Merged
Merged
Conversation
A tag-only upgrade of the release kit runs new images against the old bind-mounted haproxy/ files. The old coraza.cfg sends app=str(default), so every request is inspected by the default Coraza application instead of its vhost's policy_<id>: traffic is still filtered, but with the wrong policy and no error anywhere. Stamp haproxy.cfg and coraza.cfg with `# guard-proxy-config-version: N` and bake the expected version into the backend. The seed coraza.cfg is checked at startup, in GET /health (degraded), in GET /ready (fails, so the release kit backend healthcheck fails) and in POST /config/apply (409), naming the stale file. Absent outside a release kit, so local runs are unaffected. Also stop hardcoding the CRS version: the template now takes it from one constant that a test pins to the configs/coraza/crs submodule, and GET /runtime/status reports app_version, the configured image tag and the CRS version, warning when the tag and the backend version disagree. Closes #312 Closes #316
Reject CORS_ORIGINS='*' at startup since credentialed requests are always enabled. Return a generic /ready error detail and log the runtime config path server-side. Document global /logs access and the X-Forwarded-For trust dependency on HAProxy.
…rsion-checks # Conflicts: # CHANGELOG.md
main added the seed-config readiness check in main.py while #384 made the runtime-config failure detail generic and logged the path instead. Both changes are kept: the git conflict was purely textual in the `runtime_config` error branch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #312
Closes #316
Stacked on
fix/321-hardening. This branch already contains the #321 merge commit, so the diff below is only #312/#316. Retarget the base tomainonce #321 merges; no rebase should be needed.What changed
#312 — a tag-only upgrade is now loud instead of silently wrong
The release kit bind-mounts
haproxy/coraza.cfginto HAProxy and the backend. Bumping onlyGUARD_PROXY_IMAGE_TAGtherefore runs a new image against a pre-beta.5 file that sendsapp=str(default): every request is inspected by thedefaultCoraza application instead of its vhost'spolicy_<id>, with no error anywhere (verified in the issue with real stacks).configs/haproxy/,release/haproxy/, both files) carry# guard-proxy-config-version: 1. The generatedhaproxy.cfgtemplate emits the same stamp fromEXPECTED_CONFIG_VERSION, so the seed file still equals the rendered reference modulo whitespace.check_seed_config()reads the mountedcoraza.cfgatsettings.seed_coraza_config_path(default/usr/local/etc/haproxy/coraza.cfg) and compares the stamp. A missing file is skipped, so local runs andpytestare unaffected; a stale one is a failure.ERRORlog naming the file;GET /health— 200 withstatus: degradedand the stale path indetail;GET /ready— theseed_configcheck fails, so the release kit's backend healthcheck fails; the body stays generic per Lower-severity hardening: write-time validation, log isolation, CORS, /ready detail #321's rule and the path goes to the server log;POST /config/apply—409naming the file, before anything is written or reloaded;GET /runtime/status—active_config.degradedpluswarningnaming the file (rendered by the existing admin-UI alert).#316 — the CRS version comes from one place
app/constants/versions.pyholdsCRS_VERSION;crs-setup.conf.j2takesver:'OWASP_CRS/{{ crs_version }}'and the derivedtx.crs_setup_versioninstead of hardcoded4.25.0/4250. A test asserts the constant matches theconfigs/coraza/crssubmodule'scrs-setup.conf.exampleheader, so bumping the submodule alone now fails a test.configs/coraza/crs-setup.confstays the checked reference — the existing baseline test keeps it in sync.GET /runtime/statusgainsversion_agreement:app_version,configured_image_tag(fromGUARD_PROXY_IMAGE_TAG, mappedv0.1.0-beta.10→0.1.0b10),crs_version, and awarningwhen the tag and the backend's own version disagree.APP_VERSIONmoved toapp/version.py(the status service needs it; importing it fromapp.mainwould be circular).Fail-closed consequence
A stale stamp makes
/readyfail, sofrontend,haproxy,corazaandlog-shipper(alldepends_on: backend: service_healthy) do not start until thehaproxy/files from the same release are in place. That is the intended "refuses to report healthy" behaviour from #312; the backend log names the file, and the admin UI surfaces it on an already-running stack. Documented inrelease/README.md("Upgrading", "A tag-only upgrade is refused") anddocs/architecture.md.Tests
cd src/backend && uv run ruff check app/ tests/ && uv run mypy app/ && uv run pytest -qNew coverage: stale/older/newer/missing-stamp detection and all four shipped configs passing;
/healthdegraded vs healthy;/ready503 with a generic body, no path in the body, path incaplog;/config/apply409 and 200;/runtime/statusversion_agreementand stale-seed warning; tag→PEP 440 mapping and skew warning; rendered CRS metadata usingCRS_VERSIONand matching the submodule.Skipped