feat(traefik): migrate keepalived password to manifest-driven Pattern C provisioning

Root cause of the 2026-09-12 VIP instability incident: traefik/traefik.env
was committed to git with a literal "***REDACTED***" placeholder as
KEEPALIVED_PASSWORD. Every git-guard resync/checkout restored that broken
value onto disk, diverging from keepalived-backup's stale-but-correct
in-memory value and causing a continuous VRRP auth failure + VIP flapping.

- Adds TRAEFIK_KEEPALIVED_PASSWORD (from_secret: traefik_keepalived_password)
  to provision-secrets environment block
- Replaces the traefik legacy case-entry (previously grouped with
  maintenance|media|unifi|... as "no Docker secrets, host .env only") with
  a call to deploy/provision-stack.py traefik, matching the ai) stack's
  manifest-driven pattern
- traefik/traefik.env is now rendered fresh from traefik/traefik.env.template
  on every provisioning run and never committed to git again

Diffed $${ occurrence count against previous version before committing,
per this file's own 2026-08-26 incident note.
This commit is contained in:
Bot
2026-09-11 22:40:36 -07:00
parent 201d8418ee
commit d9f151a1c3
+27 -1
View File
@@ -70,6 +70,16 @@ when:
# tolerating a failing stack-ps in verify (|| true) so a genuinely missing # tolerating a failing stack-ps in verify (|| true) so a genuinely missing
# stack produces the designed WARNING instead of killing the step. This # stack produces the designed WARNING instead of killing the step. This
# hazard was first flagged in July (PR #3, closed unmerged). # hazard was first flagged in July (PR #3, closed unmerged).
#
# 2026-09-12 MIGRATION: traefik moved from Pattern B (secrets hand-typed
# into the host-only traefik.env, no CI involvement at all) to the same
# manifest-driven provisioning as ai) below. Root cause of the migration:
# traefik.env had been accidentally committed to git with a literal
# "***REDACTED***" placeholder as KEEPALIVED_PASSWORD; every git-guard
# resync/checkout silently restored that broken value, which diverged from
# keepalived-backup's stale-but-correct in-memory value and produced a
# continuous VRRP auth failure ("received an invalid passwd!") and VIP
# instability. The manifest-driven path never commits the rendered file.
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
steps: steps:
@@ -216,6 +226,11 @@ steps:
from_secret: flowagent_azure_tenant_id from_secret: flowagent_azure_tenant_id
FLOWAGENT_AZURE_CLIENT_SECRET: FLOWAGENT_AZURE_CLIENT_SECRET:
from_secret: flowagent_azure_client_secret from_secret: flowagent_azure_client_secret
# ── traefik stack (manifest-driven — secrets/secrets-map.yaml +
# traefik/traefik.env.template + deploy/provision-stack.py).
# Migrated 2026-09-12; see header note above for root cause. ──
TRAEFIK_KEEPALIVED_PASSWORD:
from_secret: traefik_keepalived_password
commands: commands:
- apk add --no-cache openssh-client python3 py3-yaml - apk add --no-cache openssh-client python3 py3-yaml
- mkdir -p ~/.ssh - mkdir -p ~/.ssh
@@ -243,8 +258,19 @@ steps:
for STACK in $ALL_STACKS; do for STACK in $ALL_STACKS; do
echo "Provisioning: $STACK" echo "Provisioning: $STACK"
case "$STACK" in case "$STACK" in
maintenance|media|unifi|guacamole|security|auth|traefik|meshcentral|ddm) maintenance|media|unifi|guacamole|security|auth|meshcentral|ddm)
echo " No Docker secrets for $STACK — secrets in host .env";; echo " No Docker secrets for $STACK — secrets in host .env";;
traefik)
# MIGRATED (2026-09-12) to manifest-driven provisioning after
# discovering traefik.env was committed to git with a literal
# "***REDACTED***" placeholder as KEEPALIVED_PASSWORD — every
# git-guard resync/checkout restored the broken value, causing
# VRRP auth mismatch between keepalived-master/-backup and VIP
# instability. All logic lives in deploy/provision-stack.py;
# the authoritative key list lives in
# traefik/traefik.env.template; the mapping lives in
# secrets/secrets-map.yaml. This case is intentionally one line.
python3 deploy/provision-stack.py traefik;;
immich) immich)
ssh -o StrictHostKeyChecking=no root@$${SWARM_MANAGER_IP} "source /tmp/cs.sh ssh -o StrictHostKeyChecking=no root@$${SWARM_MANAGER_IP} "source /tmp/cs.sh
create_or_update_secret 'immich_db_password' '$${IMMICH_DB_PASSWORD}' create_or_update_secret 'immich_db_password' '$${IMMICH_DB_PASSWORD}'