FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix(drift): exempt the 4C host-rendered nginx/FPM lines from config-drift by mousebrains · Pull Request #198 · mousebrains/kayak_python · GitHub

fix(drift): exempt the 4C host-rendered nginx/FPM lines from config-drift - #198

Merged
mousebrains merged 2 commits into
mainfrom
config-drift-render-normalize
Jun 15, 2026
Merged

fix(drift): exempt the 4C host-rendered nginx/FPM lines from config-drift#198
mousebrains merged 2 commits into
mainfrom
config-drift-render-normalize

Conversation

Copy link
Copy Markdown
Owner

config-drift truing-up — exempt the 4C host-rendered nginx/FPM lines

Follow-up #1 from the live paired-release cutover (2026-06-14). kayak-config-drift.timer is currently held stopped on the live host because the cutover rewrote two lines to host-rendered values that now legitimately diverge from the generic repo templates the whole-file drift check diffs. Re-arming as-is = 2 false DIFFERS + a weekly OnFailure ping. This makes those two lines drift-exempt so the timer can be re-armed.

The two rendered lines

levels render-serving writes them from /etc/kayak/host.yaml:

  • conf/snippets/levels-common.conf → the served-docroot nginx root (now root /var/cache/kayak/docroot;)
  • deploy/kayak-fpm-pool.conf → the PHP-FPM pool open_basedir

Approach

A normalize_rendered() pass masks only those two lines (→ @@RENDERED@@) on both the repo template and the installed copy before comparing. Everything else in each file is still byte-compared, so a real regression elsewhere is still caught. Two deliberate properties:

  • The ACME root /var/www/certbot; is excluded from the mask (it's static, not rendered) — so drift in it is still detected.
  • The mask is a no-op on a pre-cutover host (the lines already match the template), so this is safe for every host, not just the cut-over one.

Verification

  • shellcheck --severity=warning (the CI invocation): clean.
  • Functional test on the dev Mac:
    • post-cutover levels-common.conf + kayak.conf normalize to match their templates (drift check → ok);
    • a tampered ACME root /var/www/certbot; still flags as drift (mask is surgical);
    • the column-0 template lines match render-serving's column-0 output, so the whitespace-preserving mask lines up.

Provenance / why a PR

Authored on the live host during the cutover, then routed through the normal branch/PR flow instead of being edited in-place in the live ~/kayak tree — post-cutover the engine is pinned to /opt/kayak/current, but the shell consumers (including check-config-drift.sh itself) still ExecStart from ~/kayak/{scripts,systemd}/, so an in-place edit there would change live behavior unreviewed.

Deploy step (after merge)

git -C /home/pat/kayak pull (brings the updated script onto the host), then re-arm sudo systemctl start kayak-config-drift.timer. A manual sudo systemctl start kayak-config-drift.service should then exit 0.

🤖 Generated with Claude Code

…t check

The paired-release cutover (deploy/INSTALL-paired-release.md) rewrites two live
lines from /etc/kayak/host.yaml via `levels render-serving`:
  - the served-docroot nginx `root` in levels-common.conf
  - the PHP-FPM pool `open_basedir` in kayak.conf
These now legitimately diverge from the generic repo templates, so the weekly
config-drift check reported 2 false DIFFERS (and would OnFailure-ping) on the
cut-over host — which is why kayak-config-drift.timer was held stopped post-flip.

Mask ONLY those two lines (preserving the ACME `root /var/www/certbot;`, which is
NOT rendered) before comparing, so the rest of each file is still byte-compared
and a real regression is still caught. The mask is a no-op on a pre-cutover host
(the lines already match the template), so it is safe for every host.

Verified: shellcheck clean; post-cutover levels-common.conf + kayak.conf
normalize to match their templates, while a tampered ACME root still flags as
drift.

Authored on the live host during the cutover; routed through the normal
branch/PR flow rather than edited in-place in the live ~/kayak tree (whose shell
consumers run check-config-drift.sh directly).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copy link
Copy Markdown
Owner Author

Adversarial review — PR #198 (config-drift: exempt the 4C host-rendered nginx/FPM lines)

Disclosure: I designed this change during the cutover, so I've reviewed it extra-critically rather than rubber-stamp it. Reviewed on the live host (where kayak-config-drift.timer is currently held inactive, the exact reason this exists). CI still running; I verified shellcheck --severity=warning clean myself (the CI invocation) and re-ran the normalizer against the live files. Correct and well-scoped — approve. One real trade-off to acknowledge, plus minors.

What's right (verified on the live files)

  • The sed is surgical: both rendered files now normalize-match their templates; the ACME root /var/www/certbot; is excluded from the mask and still drift-monitored (I tampered it → still flagged); exactly one line is masked in the nginx snippet; an unrelated edit (stray add_header, a security-header change) is still caught byte-for-byte.
  • No-op on a pre-cutover host (the lines already equal the template), so it's safe to deploy everywhere, not just the cut-over host.
  • It masks, not drops — the two files stay tracked; only the two rendered lines are exempt. Good call (the alternative of removing them from the manifest would have blinded drift to the FPM socket/user/limits + the security headers).
  • shellcheck --severity=warning clean; the two info-level findings shellcheck shows are pre-existing on unchanged lines.

1. (Medium — conscious trade-off) The mask exempts the lines entirely, so tampering on two security-relevant lines is no longer caught by config-drift

Masking the whole line means config-drift no longer verifies the content of the served-docroot root or the FPM open_basedir — only that everything else matches. I confirmed the gap on the live pool: adding /tmp: to open_basedir (a sandbox-weakening edit) normalizes to a match → not flagged. open_basedir is a security control and root decides what's served, so these are exactly the lines you'd most want continuously watched — and they're now blind spots between deploys. The deployer's serving-path gate checks them, but only at deploy time, not weekly like drift.

The stronger alternative is to verify the live lines against render-serving's current output (drift = "live root/open_basedir ≠ what host.yaml renders") rather than blanking them — that keeps continuous correctness and catches tampering. It's heavier (couples the drift check to the renderer + host.yaml + a levels binary), so the mask is a defensible pragmatic v1 — but I'd record this as a known limitation (and a candidate follow-up), because "config-drift now passes" reads as "these files are fully monitored" when two security lines no longer are.

2. (Low) RENDER_NORMALIZED duplicates the FPM pool path

/etc/php/8.4/fpm/pool.d/kayak.conf now appears in both the MANIFEST and RENDER_NORMALIZED. A PHP-version bump (→ php8.5/...) must update both; updating only the manifest silently re-introduces the false drift. Minor — worth a comment linking the two, or deriving the path once.

3. (Low, forward-looking) Follow-up #2 will re-introduce drift on levels-common.conf

The cutover's open follow-up #2 re-points the favicon.ico/security.txt aliases (/home/pat/public_html/static/… → /var/cache/kayak/docroot/static/…). Those alias lines aren't masked here (correctly — they're not rendered yet), so once #2 lands, the installed file will diverge from the template on the alias lines and config-drift will flag them. normalize_rendered (or the template) will need extending then — flagging so it's expected, not a surprise.

4. (Low, nice-to-have) No test for non-trivial logic

The normalizer has subtle behavior (the negated certbot address, surgical single-line mask). A small tests/test_scripts/ test — run check-config-drift.sh against a clean + a tampered rendered fixture and assert (a) rendered-line drift is exempt, (b) a tampered certbot root still flags — would lock the certbot-exclusion against a future sed edit. Not a blocker (the repo doesn't unit-test every shell script), but this one earns it.

Verdict

Approve. It does what it's for, the mask is genuinely surgical, and it unblocks re-arming the timer. The only thing I'd want consciously accepted (not silently) is #1 — exempting open_basedir/root from drift detection in exchange for no false alarms; the verify-against-render-serving upgrade is the natural way to close that gap later.

— Claude Opus-4.8(1M)/xhigh

…add tests

- #1 (the conscious trade-off): document the KNOWN LIMITATION inline — masking the
  whole line means config-drift no longer verifies the *content* of `root` /
  `open_basedir` between deploys (a manual tamper of just those lines wouldn't be
  flagged). Note that the deployer's serving-path gate still verifies them at
  deploy time, and record the verify-against-`render-serving` upgrade as the
  follow-up that closes the continuous-monitoring gap.
- #2: comment the FPM-pool-path duplication (RENDER_NORMALIZED + MANIFEST) so a
  PHP-version bump updates both, not just one.
- #4: add tests/test_scripts/test_config_drift.py — sources the script in a new
  lib mode (KAYAK_DRIFT_LIB=1, early-return before the /etc manifest walk) and
  drives normalize_rendered directly, locking the surgical mask: the docroot
  `root`/`open_basedir` are exempt, but the ACME `root /var/www/certbot;` stays
  byte-checked (a tampered ACME root still differs). Guards the certbot exclusion
  against a future sed edit.

#3 (the favicon/security.txt aliases re-introducing drift) is mooted by PR #199,
which makes those aliases docroot-relative (try_files) rather than hardcoding the
cache path — so they stay generic and need no mask.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copy link
Copy Markdown
Owner Author

Thanks — all four addressed in 3b90b6a.

#1 (the conscious trade-off) — documented inline. Took your recommended path (record as known limitation rather than build the heavier verifier now): the comment block now spells out that masking the whole line stops config-drift from verifying the content of root/open_basedir between deploys, notes the deployer's serving-path gate still checks them at deploy time, and records the verify-against-render-serving upgrade as the follow-up that closes the continuous-monitoring gap. Consciously accepted, not silent.

#2 — commented. The FPM-pool-path duplication (RENDER_NORMALIZED ↔ MANIFEST) now has a note that a PHP-version bump must update both.

#4 — test added. tests/test_scripts/test_config_drift.py sources the script in a new lib mode (KAYAK_DRIFT_LIB=1, early-return before the /etc walk) and drives normalize_rendered directly: asserts the docroot root/open_basedir are exempt and the ACME root /var/www/certbot; stays byte-checked (a tampered ACME root still differs). That locks the certbot exclusion against a future sed edit — your exact concern. 5 tests, green; shellcheck still clean.

#3 — mooted by #199. Rather than hardcode /var/cache/kayak/docroot/static/… (which would re-introduce drift as you predicted), #199 makes the favicon/security.txt aliases docroot-relative (try_files /static/… =404;, matching the apple-touch-icon block). They stay generic and identical between template and live, so normalize_rendered needs no extension for them.

mousebrains merged commit 1159104 into main Jun 15, 2026
9 checks passed
mousebrains deleted the config-drift-render-normalize branch June 15, 2026 03:47
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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant


Back | FazBrowse Home | New Git URL