feat(crowdsec): recover the bouncer key when the config lost it

Every install before the mirror target was corrected registered the bouncer but
never recorded its key, and cscli cannot show an existing bouncer's key — so
those installs had no route back to the value except re-registering, which
invalidates the key Traefik is already using.

The EXISTS branch now reads the key back from /etc/crowdsec/traefik_bouncer.key
when the config has none. That file is deliberately left owned by the manager at
0600 by libreportal-crowdsec, so this layer can read it without another
privileged round trip.

Restructured so both branches share one mirror, gated on the value actually
differing — a healthy reinstall now writes nothing instead of rewriting the same
key each time.

Exercised all six paths against the shipped block: fresh generation writes the
key; registered-with-empty-config recovers it; in-sync writes nothing; missing
and empty key files each explain what to do rather than failing silently; a cscli
error is unchanged. The masking added alongside holds throughout — the log shows
"Updated CFG_CROWDSEC_TRAEFIK_LAPI_KEY" with no value.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
librelad 2026-08-19 00:03:45 +01:00
parent c3b7d6ae35
commit 27fea7aa17

View File

@ -174,29 +174,60 @@ installCrowdsecHost()
# -f guard always failed, and the key was never mirrored. The key
# is declared in crowdsec.config, so that is where it belongs.
# The helper handles cscli + tee + chown + chmod atomically.
local init_result
init_result=$(runCrowdsec bouncer-traefik-init 2>&1)
if [[ "$init_result" == "EXISTS" ]]; then
isNotice "Bouncer 'traefik-bouncer' already registered — leaving existing key file untouched at /etc/crowdsec/traefik_bouncer.key."
elif [[ "$init_result" == GENERATED:* ]]; then
local bouncer_key="${init_result#GENERATED:}"
checkSuccess "Traefik bouncer API key generated"
local cfg_file="${containers_dir}crowdsec/crowdsec.config"
local key_file="/etc/crowdsec/traefik_bouncer.key"
# Mirror the key into the live config file so it's visible /
# editable via the framework's config page like any other CFG_*
# setting. updateConfigOption rather than a hand-rolled sed: it
# escapes the value, routes the write through the user that owns
# the containers tree, and re-sources so the new key is live in
# this same run.
local cfg_file="${containers_dir}crowdsec/crowdsec.config"
# What the config currently holds, if anything. Quotes and whitespace
# stripped: updateConfigOption writes the value quoted.
local recorded_key=""
if [[ -f "$cfg_file" ]]; then
recorded_key=$(grep -m1 '^CFG_CROWDSEC_TRAEFIK_LAPI_KEY=' "$cfg_file" 2>/dev/null | cut -d= -f2-)
recorded_key="${recorded_key%%#*}"
recorded_key="${recorded_key//\"/}"
recorded_key="${recorded_key//[[:space:]]/}"
fi
local init_result bouncer_key=""
init_result=$(runCrowdsec bouncer-traefik-init 2>&1)
if [[ "$init_result" == "EXISTS" ]]; then
isNotice "Bouncer 'traefik-bouncer' already registered — leaving the existing key file untouched at $key_file."
# Self-heal. cscli cannot show an existing bouncer's key, so an
# install whose mirror never landed (every install before the write
# target was corrected) had no way back to the value short of
# re-registering the bouncer — which invalidates the key Traefik is
# already using. The helper leaves the key file owned by the manager
# at 0600 precisely so this layer can read it back.
if [[ -z "$recorded_key" ]]; then
if [[ -r "$key_file" ]]; then
bouncer_key=$(tr -d '\r\n' < "$key_file")
[[ -n "$bouncer_key" ]] \
&& isNotice "Config had no bouncer key — recovering it from $key_file." \
|| isNotice "$key_file is empty — cannot recover the bouncer key. Rotate it to get a new one."
else
isNotice "Config has no bouncer key and $key_file is not readable — rotate the bouncer to issue a new one."
fi
fi
elif [[ "$init_result" == GENERATED:* ]]; then
bouncer_key="${init_result#GENERATED:}"
checkSuccess "Traefik bouncer API key generated"
else
isNotice "Failed to generate bouncer key: $init_result"
isNotice "Traefik integration won't authenticate. Re-run installCrowdsecHost to retry."
fi
# Mirror the key into the live config file so it's visible / editable via
# the framework's config page like any other CFG_* setting.
# updateConfigOption rather than a hand-rolled sed: it escapes the value,
# routes the write through the user that owns the containers tree, and
# re-sources so the new key is live in this same run. Skipped when the
# config already agrees, so a normal reinstall writes nothing.
if [[ -n "$bouncer_key" && "$bouncer_key" != "$recorded_key" ]]; then
if [[ -f "$cfg_file" ]]; then
updateConfigOption "CFG_CROWDSEC_TRAEFIK_LAPI_KEY" "$bouncer_key" "$cfg_file"
else
isNotice "crowdsec.config not deployed yet — key applied on next install."
fi
else
isNotice "Failed to generate bouncer key: $init_result"
isNotice "Traefik integration won't authenticate. Re-run installCrowdsecHost to retry."
fi
((menu_number++))