vault-ops/infra/archiv/setup-vault-agent-app-config-v4.7.sh
2025-10-06 07:25:33 +02:00

427 lines
18 KiB
Bash
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
set -Eeuo pipefail
# setup-vault-agent-app-config-v4.7.sh
# Datum: 2025-09-30
# ========================= FEATURE MANIFEST (v4.7) =========================
# 🔒 Authentisierung / Sicherheit
# 1) Strict mTLS-Login (DEFAULT): Wenn --auth cert gesetzt (oder Default) und
# ~/vault/mtls/agent.{crt,key} fehlt → HARTE ABBRUCH (Exit 5). Kein Fallback.
# 2) Optional AppRole NUR bei --auth approle (erzeugt/liest role_id & secret_id).
# 3) Agent-Token wird sicher in Datei-Sink (0400) geschrieben.
# 4) Robuste CN-Prüfung (STRICT, Default aktiv): CN aus Client-Zert wird
# korrekt extrahiert und gegen Regex geprüft; bei Mismatch Abbruch.
#
# 📦 Konfiguration / YAML
# 5) Liest ./config/apps.yaml:
# environments.{vault_addr, kv_mount, pki_mount,
# proxy{user,listen_port,chain_path,reload},
# app{user,sidecar_host_port,sidecar_container}}
# apps[].{name, user?, internal_cn, external_host_{test,prod}, issue_ttl,
# kv_subpath?, seed?, sidecar_container?, proxy_user?, proxy_chain_path?, proxy_reload?}
#
# 🛠️ PKI / Policies
# 6) PKI-Role Upsert (EC P-256, max_ttl=720h), allowed_domains aus CN & externem Host.
# 7) Policies minimal: pki-issue-<app> (issue, renew-self), pki-bootstrap-<app> (AppRole bootstrap).
# 8) Environment-spezifische Policy-Erweiterung:
# • test: pki-issue-<app> erlaubt zusätzlich token/lookup-self (Debugging)
# • prod: nur token/renew-self (Minimal-Prinzip)
#
# 🧠 Agent / Templates / Pfade
# 9) Generiert Agent-HCL + Template (cert.tpl); rendert .issue.json.
# 10) Post-Hook (vault-agent-post-leaf.sh) schreibt <HOME>/tls/<app>.{key,fullchain.pem}.
# 11) Verzeichnisstruktur:
# ~/.vault-agent-<app>/{vault-agent.hcl,cert.tpl,.issue.json,token,bin/,pidfile}
# ~/tls/, ~/vault/ca/ca.pem, ~/vault/mtls/{agent.crt,agent.key}
#
# 🔁 Reload / Sidecar / Proxy
# 12) Übergibt an Post-Hook via Env:
# PROXY_RELOAD, PROXY_CHAIN_PATH, PROXY_USER, PROXY_LISTEN_PORT,
# SIDECAR_HOST_PORT, SIDECAR_CONTAINER, RELOAD_TLS_LABEL.
#
# 🔑 KV-Seed (einmalig)
# 13) Falls kv_subpath + seed vorhanden und Secret fehlt → einmaliges vault kv put (kein Overwrite).
# 14) Flag --with-kv-seed:
# • Immer prüfen und loggen, ob Secret existiert
# • Nur wenn Secret fehlt UND Flag gesetzt → einmaliges kv put
# • Niemals überschreiben
#
# ⚙️ Systemd (user)
# 15) Erstellt ~/.config/systemd/user/vault-agent-<app>.service, aktiviert (--user), Linger an.
#
# 🌐 TLS zu Vault
# 16) Nutzt ca_cert=~/vault/ca/ca.pem + tls_server_name (Override via --tls-server-name).
# Optional client_cert/client_key gesetzt, wenn Dateien existieren.
#
# 🧩 CLI-Flags / Defaults
# 17) Flags: --env, --config, --app, --auth (cert|approle), --pki-role,
# --tls-server-name, --reload-label, --with-kv-seed.
#
# 🧪 Quick Check
# 18) Prüft am Ende auf vorhandenes Leaf, zeigt subject/issuer/enddate; sonst journalctl Hint.
#
# 🆕 Operational Add-ons (v4.7)
# 19) Erzwingt IMMER einen Restart des user-services nach Deploy (damit neue HCL greift).
# 20) Zeigt IMMER den systemd-Status des Agents (kurz, ohne Pager).
# 21) Tails IMMER die letzten Journal-Logs des Agents (Standard: 80 Zeilen, ohne Pager).
# ===========================================================================
# Exit-Codes: 2=Args/Config; 3=User fehlt; 4=Post-Hook fehlt; 5=mTLS gefordert/prüfen fehlgeschlagen
# ===== Pretty logs =====
if [[ -t 1 ]]; then B=$'\e[1m'; R=$'\e[0m'; BL=$'\e[34m'; G=$'\e[32m'; Y=$'\e[33m'; E=$'\e[31m'; else B= R= BL= G= Y= E=; fi
ts(){ date +"%Y-%m-%d %H:%M:%S"; }
info(){ echo -e "🟦 ${BL}${B}[$(ts)]${R} $*"; }
ok(){ echo -e "🟩 ${G}${B}[$(ts)]${R} $*"; }
warn(){ echo -e "🟨 ${Y}${B}[$(ts)]${R} $*"; }
err(){ echo -e "🟥 ${E}${B}[$(ts)]${R} $*" >&2; }
# ===== Defaults / Args =====
: "${LOG_LEVEL:=info}"
: "${DEFAULT_ENV:=test}"
: "${DEFAULT_CONFIG:=./config/apps.yaml}"
: "${TLS_SERVER_NAME_DEFAULT:=vault.test.privsec.ch}"
: "${RELOAD_LABEL_DEFAULT:=tls=true}"
: "${STRICT_CN_CHECK:=1}" # Standard = strikt prüfen
: "${JOURNAL_TAIL_LINES:=80}" # NEU v4.7: wie viele Logzeilen am Ende tailen
[[ -n "${VAULT_ADMIN_TOKEN:-}" ]] || { err "VAULT_ADMIN_TOKEN fehlt (exportieren!)"; exit 2; }
ENV_NAME="$DEFAULT_ENV"; CFG="$DEFAULT_CONFIG"; APPN=""
AUTH_METHOD="cert" # cert|approle
PKI_ROLE_OVERRIDE=""; TLS_SNI_OVERRIDE=""; RELOAD_LABEL_OVERRIDE=""
WITH_KV_SEED=0
while [[ $# -gt 0 ]]; do
case "$1" in
--env) ENV_NAME="$2"; shift 2;;
--config) CFG="$2"; shift 2;;
--app) APPN="$2"; shift 2;;
--auth) AUTH_METHOD="$2"; shift 2;;
--pki-role) PKI_ROLE_OVERRIDE="$2"; shift 2;;
--tls-server-name) TLS_SNI_OVERRIDE="$2"; shift 2;;
--reload-label) RELOAD_LABEL_OVERRIDE="$2"; shift 2;;
--with-kv-seed) WITH_KV_SEED=1; shift;;
-h|--help) sed -n '1,260p' "$0"; exit 0;;
*) err "unknown arg: $1"; exit 2;;
esac
done
[[ -n "$APPN" ]] || { err "--app ist erforderlich"; exit 2; }
case "$AUTH_METHOD" in cert|approle) ;; * ) err "--auth muss cert|approle sein"; exit 2;; esac
# ===== Needs =====
need(){ command -v "$1" >/dev/null || { err "missing: $1"; exit 2; }; }
need vault; need jq; need python3; need openssl; need install; need systemctl
# ===== YAML laden =====
CFG_ABS="$(readlink -f -- "$CFG")" || { err "cannot resolve $CFG"; exit 2; }
CFGJSON="$(python3 - "$CFG_ABS" <<'PY'
import yaml, json, sys, io
p = sys.argv[1]
with io.open(p, "r", encoding="utf-8") as f:
print(json.dumps(yaml.safe_load(f)))
PY
)" || { err "YAML parse failed: $CFG_ABS"; exit 2; }
jqenv(){ echo "$CFGJSON" | jq -r ".environments.\"$ENV_NAME\"$1"; }
jqapp(){ echo "$CFGJSON" | jq -r ".apps[] | select(.name==\"$APPN\")$1"; }
VAULT_ADDR="$(jqenv '.vault_addr')"
PKI_MOUNT="$(jqenv '.pki_mount')"
KV_MOUNT="$(jqenv '.kv_mount')"
# Env-level defaults
APP_USER_DEF="$(jqenv '.app.user')"
SIDECAR_HOST_PORT_DEF="$(jqenv '.app.sidecar_host_port')"
SIDECAR_CONTAINER_DEF="$(jqenv '.app.sidecar_container')"
PROXY_USER_DEF="$(jqenv '.proxy.user')"
PROXY_LISTEN_PORT_DEF="$(jqenv '.proxy.listen_port')"
PROXY_CHAIN_PATH_DEF="$(jqenv '.proxy.chain_path')"
PROXY_RELOAD_DEF="$(jqenv '.proxy.reload')"
# App-level fields & overrides
APP_USER_APP="$(jqapp '.user')"
CN="$(jqapp '.internal_cn')"
TTL="$(jqapp '.issue_ttl')"
EXT_TEST="$(jqapp '.external_host_test')"; [[ "$EXT_TEST" == "null" ]] && EXT_TEST=""
EXT_PROD="$(jqapp '.external_host_prod')"; [[ "$EXT_PROD" == "null" ]] && EXT_PROD=""
KV_SUBPATH="$(jqapp '.kv_subpath')"
SEED_JSON="$(jqapp '.seed')"
SIDECAR_CONTAINER_APP="$(jqapp '.sidecar_container')"
PROXY_USER_APP="$(jqapp '.proxy_user')"
PROXY_CHAIN_PATH_APP="$(jqapp '.proxy_chain_path')"
PROXY_RELOAD_APP="$(jqapp '.proxy_reload')"
APP_USER="$APP_USER_DEF"; [[ "$APP_USER_APP" != "null" ]] && APP_USER="$APP_USER_APP"
SIDECAR_HOST_PORT="$SIDECAR_HOST_PORT_DEF"; [[ "$SIDECAR_HOST_PORT" == "null" ]] && SIDECAR_HOST_PORT=""
SIDECAR_CONTAINER="$SIDECAR_CONTAINER_DEF"; [[ "$SIDECAR_CONTAINER_APP" != "null" ]] && SIDECAR_CONTAINER="$SIDECAR_CONTAINER_APP"; [[ "$SIDECAR_CONTAINER" == "null" ]] && SIDECAR_CONTAINER=""
PROXY_USER="$PROXY_USER_DEF"; [[ "$PROXY_USER_APP" != "null" ]] && PROXY_USER="$PROXY_USER_APP"
PROXY_LISTEN_PORT="$PROXY_LISTEN_PORT_DEF"; [[ "$PROXY_LISTEN_PORT" == "null" ]] && PROXY_LISTEN_PORT=""
PROXY_CHAIN_PATH="$PROXY_CHAIN_PATH_DEF"; [[ "$PROXY_CHAIN_PATH_APP" != "null" ]] && PROXY_CHAIN_PATH="$PROXY_CHAIN_PATH_APP"
PROXY_RELOAD="$PROXY_RELOAD_DEF"; [[ "$PROXY_RELOAD_APP" != "null" ]] && PROXY_RELOAD="$PROXY_RELOAD_APP"
[[ "$VAULT_ADDR" != "null" && "$PKI_MOUNT" != "null" && "$CN" != "null" && "$TTL" != "null" && -n "$APP_USER" ]] \
|| { err "incomplete config: vault_addr/pki_mount/app.user/internal_cn/issue_ttl"; exit 2; }
export VAULT_ADDR VAULT_TOKEN="${VAULT_ADMIN_TOKEN}"
# ===== Helpers =====
base_domain(){ local h="${1:-}"; [[ "$h" == *.* ]] && printf '%s\n' "${h#*.}" || printf '\n'; }
uniq_csv(){ tr ',' '\n' | awk 'NF' | sort -u | paste -sd, -; }
derive_allowed_domains(){ printf '%s\n%s\n' "$(base_domain "$1")" "$(base_domain "$2")" | uniq_csv; }
extract_cn(){
# Liefert exakt den CN (ohne "CN="), oder leer bei Fehler
openssl x509 -in "$1" -noout -subject -nameopt RFC2253 \
| sed -n 's/^subject=//; s/.*CN=\([^,]*\).*/\1/p'
}
ensure_pki_role(){
local mount="$1" role="$2" doms="$3" max="${4:-720h}"
info "upsert PKI role ${role} (allowed_domains=${doms})"
vault write "${mount}/roles/${role}" \
allowed_domains="${doms}" allow_subdomains=true allow_bare_domains=true \
allow_wildcard_certificates=false server_flag=true client_flag=false \
key_type="ec" key_bits=256 max_ttl="${max}" >/dev/null || true
}
# ===== User/Dirs/Paths =====
id -u "${APP_USER}" >/dev/null 2>&1 || { err "Linux-User ${APP_USER} fehlt"; exit 3; }
HOME_DIR="/home/${APP_USER}"
AGENT_DIR="${HOME_DIR}/.vault-agent-${APPN}"
TLS_DIR="${HOME_DIR}/tls"
CA_DIR="${HOME_DIR}/vault/ca"
MTLS_DIR="${HOME_DIR}/vault/mtls"
CA_FILE="${CA_DIR}/ca.pem"
RID="${AGENT_DIR}/role_id"
SID="${AGENT_DIR}/secret_id"
TOKEN_FILE="${AGENT_DIR}/token"
POST_LOCAL="${AGENT_DIR}/bin/vault-agent-post-leaf.sh"
POST_SRC="$(cd -- "$(dirname -- "$0")" && pwd)/scripts/vault-agent-post-leaf.sh"
UNIT="${HOME_DIR}/.config/systemd/user/vault-agent-${APPN}.service"
sudo install -d -m 0700 -o "${APP_USER}" -g "${APP_USER}" "${AGENT_DIR}" "${TLS_DIR}"
sudo install -d -m 0755 -o "${APP_USER}" -g "${APP_USER}" "${AGENT_DIR}/bin"
sudo install -d -m 0755 -o "${APP_USER}" -g "${APP_USER}" "${CA_DIR}" >/dev/null 2>&1 || true
sudo install -d -m 0755 -o "${APP_USER}" -g "${APP_USER}" "${MTLS_DIR}" >/dev/null 2>&1 || true
# ===== PKI Role & Policies =====
PKI_ROLE="${PKI_ROLE_OVERRIDE:-nginx-${APPN}}"
EXT_HOST="$EXT_TEST"; [[ "$ENV_NAME" == "prod" ]] && EXT_HOST="$EXT_PROD"
ALLOWED="$(derive_allowed_domains "$CN" "$EXT_HOST")"
ensure_pki_role "$PKI_MOUNT" "$PKI_ROLE" "$ALLOWED" "720h"
POLICY_BOOT="pki-bootstrap-${APPN}"
POLICY_RUN="pki-issue-${APPN}"
TMP="$(mktemp)"
cat >"$TMP" <<EOF
path "${PKI_MOUNT}/issue/${PKI_ROLE}" { capabilities=["create","update"] }
path "auth/token/renew-self" { capabilities=["update"] }
EOF
# lookup-self nur in test-Env anhängen (keine Änderung bestehender Logs)
if [[ "$ENV_NAME" == "test" ]]; then
echo 'path "auth/token/lookup-self" { capabilities=["read"] }' >>"$TMP"
fi
vault policy write "${POLICY_RUN}" "$TMP" >/dev/null
cat >"$TMP" <<EOF
path "auth/approle/role/${APPN}-pki-issue/role-id" { capabilities=["read"] }
path "auth/approle/role/${APPN}-pki-issue/secret-id" { capabilities=["create","update"] }
EOF
vault policy write "${POLICY_BOOT}" "$TMP" >/dev/null
rm -f "$TMP"
# ===== Auth-Setup (Strict mTLS; AppRole nur explizit) =====
ROLE_NAME="${APPN}-pki-issue"
if [[ "$AUTH_METHOD" == "approle" ]]; then
info "Auth=AppRole (explizit via --auth approle)"
vault auth enable approle >/dev/null 2>&1 || true
vault write "auth/approle/role/${ROLE_NAME}" \
policies="${POLICY_RUN}" bind_secret_id=true \
token_type=service token_period=24h secret_id_ttl=0 secret_id_num_uses=0 >/dev/null
RID_VAL="$(vault read -field=role_id "auth/approle/role/${ROLE_NAME}/role-id")"
SID_VAL="$(vault write -f -field=secret_id "auth/approle/role/${ROLE_NAME}/secret-id")"
sudo bash -c "umask 077; printf '%s' '${RID_VAL}' > '${RID}'; chown ${APP_USER}:${APP_USER} '${RID}'; chmod 600 '${RID}'"
sudo bash -c "umask 077; printf '%s' '${SID_VAL}' > '${SID}'; chown ${APP_USER}:${APP_USER} '${SID}'; chmod 600 '${SID}'"
fi
# ===== CA-Trust prüfen =====
if ! sudo -u "${APP_USER}" test -s "${CA_FILE}"; then
warn "CA fehlt: ${CA_FILE} → HTTPS Verify ggü. Vault kann scheitern."
fi
# ===== mTLS Material vorhanden? + CN strikt prüfen =====
if [[ "$AUTH_METHOD" == "cert" ]]; then
if ! ( sudo -u "${APP_USER}" test -s "${MTLS_DIR}/agent.crt" && sudo -u "${APP_USER}" test -s "${MTLS_DIR}/agent.key" ); then
err "Auth=cert gefordert, aber mTLS fehlt: ${MTLS_DIR}/agent.{crt,key}"
exit 5
fi
CRT_CN="$(extract_cn "${MTLS_DIR}/agent.crt" || true)"
if [[ -z "$CRT_CN" ]]; then
err "Konnte CN aus ${MTLS_DIR}/agent.crt nicht lesen"
exit 5
fi
EXPECTED_RE="^agent-${APPN}(-[a-z0-9]+)?\.${ENV_NAME}\.privsec\.ch$"
if [[ "${STRICT_CN_CHECK}" == "1" ]]; then
if ! [[ "$CRT_CN" =~ $EXPECTED_RE ]]; then
err "mTLS client CN '${CRT_CN}' passt NICHT zu ${EXPECTED_RE} → Abbruch (STRICT_CN_CHECK=1)."
exit 5
else
ok "mTLS client CN '${CRT_CN}' passt zu ${EXPECTED_RE}"
fi
else
if ! [[ "$CRT_CN" =~ $EXPECTED_RE} ]]; then
warn "mTLS client CN '${CRT_CN}' passt nicht zu ${EXPECTED_RE}; weiter (tolerant, STRICT_CN_CHECK=0)."
else
info "mTLS client CN '${CRT_CN}' passt zu ${EXPECTED_RE}"
fi
fi
info "Auth=cert (mTLS) strikt, kein Fallback"
fi
# ===== KV-Seed (einmalig, kontrolliert per Flag) =====
seed_kv_once(){
local mount="$1" sub="$2" seed_json="$3"
[[ -z "$mount" || "$mount" == "null" || -z "$sub" || "$sub" == "null" ]] && return 0
if vault kv get -format=json "${mount}/${sub}" >/dev/null 2>&1; then
info "KV ${mount}/${sub} existiert Seed wird NICHT überschrieben"
return 0
fi
if [[ "$WITH_KV_SEED" -eq 1 && "$seed_json" != "null" ]]; then
info "KV-Seed → ${mount}/${sub} (nur einmalig)"
mapfile -t kvargs < <(jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' <<<"$seed_json")
printf " keys: %s\n" "$(jq -r 'keys|join(", ")' <<<"$seed_json")"
vault kv put "${mount}/${sub}" "${kvargs[@]}" >/dev/null
else
warn "KV ${mount}/${sub} fehlt kein Seed (Flag --with-kv-seed nicht gesetzt)"
fi
}
seed_kv_once "$KV_MOUNT" "$KV_SUBPATH" "$SEED_JSON"
# ===== HCL + Template (Strict mTLS / optional AppRole) =====
TLS_SNI="${TLS_SNI_OVERRIDE:-$TLS_SERVER_NAME_DEFAULT}"
PARAMS="\"common_name=${CN}\" \"ttl=${TTL}\""; [[ -n "$EXT_HOST" ]] && PARAMS="$PARAMS \"alt_names=${EXT_HOST}\""
AUTO_AUTH_BLOCK=""
if [[ "$AUTH_METHOD" == "cert" ]]; then
AUTO_AUTH_BLOCK=$(cat <<'EOF'
auto_auth {
method "cert" { }
sink "file" { config = { path = "__TOKEN_FILE__", mode = 0400 } }
}
EOF
)
elif [[ "$AUTH_METHOD" == "approle" ]]; then
AUTO_AUTH_BLOCK=$(cat <<'EOF'
auto_auth {
method "approle" {
config = {
role_id_file_path = "__RID__"
secret_id_file_path = "__SID__"
remove_secret_id_file_after_reading = false
}
}
sink "file" { config = { path = "__TOKEN_FILE__", mode = 0400 } }
}
EOF
)
fi
TLS_EXTRA=$'\n tls_server_name = "'"${TLS_SNI}"$'"\n'
# Optional mTLS zum Vault-Server selbst, falls vorhanden:
if sudo -u "${APP_USER}" test -s "${MTLS_DIR}/agent.crt" && sudo -u "${APP_USER}" test -s "${MTLS_DIR}/agent.key"; then
TLS_EXTRA+=$' client_cert = "'"${MTLS_DIR}/agent.crt"\"$'\n'
TLS_EXTRA+=$' client_key = "'"${MTLS_DIR}/agent.key"\"$'\n'
fi
HCL_CONTENT=$(cat <<HCL
pid_file = "${AGENT_DIR}/pidfile"
vault {
address = "${VAULT_ADDR}"
ca_cert = "${CA_FILE}"${TLS_EXTRA}
}
__AUTO_AUTH__
template {
source = "${AGENT_DIR}/cert.tpl"
destination = "${AGENT_DIR}/.issue.json"
command = "OUTDIR='${TLS_DIR}' RELOAD_TLS_LABEL='${RELOAD_LABEL_OVERRIDE:-$RELOAD_LABEL_DEFAULT}' PROXY_RELOAD='${PROXY_RELOAD}' PROXY_CHAIN_PATH='${PROXY_CHAIN_PATH}' PROXY_USER='${PROXY_USER}' PROXY_LISTEN_PORT='${PROXY_LISTEN_PORT}' SIDECAR_HOST_PORT='${SIDECAR_HOST_PORT}' SIDECAR_CONTAINER='${SIDECAR_CONTAINER}' APP_NAME='${APPN}' ${POST_LOCAL}"
}
HCL
)
HCL_CONTENT="${HCL_CONTENT/__AUTO_AUTH__/${AUTO_AUTH_BLOCK}}"
HCL_CONTENT="${HCL_CONTENT/__TOKEN_FILE__/${TOKEN_FILE}}"
HCL_CONTENT="${HCL_CONTENT/__RID__/${RID}}"
HCL_CONTENT="${HCL_CONTENT/__SID__/${SID}}"
sudo -u "${APP_USER}" tee "${AGENT_DIR}/vault-agent.hcl" >/dev/null <<<"$HCL_CONTENT"
sudo -u "${APP_USER}" tee "${AGENT_DIR}/cert.tpl" >/dev/null <<TPL
{{ with secret "${PKI_MOUNT}/issue/${PKI_ROLE}" ${PARAMS} }}
{ "certificate": {{ toJSON .Data.certificate }},
"issuing_ca": {{ toJSON .Data.issuing_ca }},
"ca_chain": {{ toJSON .Data.ca_chain }},
"private_key": {{ toJSON .Data.private_key }} }
{{ end }}
TPL
# ===== Post-Hook bereitstellen =====
if [[ -r "$POST_SRC" ]]; then
sudo /usr/bin/install -m 0755 -o "${APP_USER}" -g "${APP_USER}" -D "$POST_SRC" "${POST_LOCAL}"
ok "post-hook installiert: ${POST_LOCAL}"
else
err "Post-Hook fehlt: $POST_SRC (benötigt, um Key/Fullchain zu schreiben & Reload auszuführen)"; exit 4
fi
# ===== systemd (user) =====
sudo -u "${APP_USER}" install -d -m 0755 "${HOME_DIR}/.config/systemd/user"
sudo -u "${APP_USER}" tee "${UNIT}" >/dev/null <<UNIT
[Unit]
Description=Vault Agent (${APPN}) - leaf issuance & rotation (v4.7)
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
WorkingDirectory=${AGENT_DIR}
Environment=VAULT_ADDR=${VAULT_ADDR}
ExecStart=/usr/bin/vault agent -log-level=${LOG_LEVEL} -config=${AGENT_DIR}/vault-agent.hcl
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=default.target
UNIT
APP_UID="$(id -u "${APP_USER}")"
loginctl enable-linger "${APP_USER}" >/dev/null || true
systemctl start "user@${APP_UID}.service" >/dev/null || true
XDG_RUNTIME_DIR="/run/user/${APP_UID}"; mkdir -p "$XDG_RUNTIME_DIR"; chown "${APP_USER}:${APP_USER}" "$XDG_RUNTIME_DIR" || true
sudo -u "${APP_USER}" XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR}" systemctl --user daemon-reload
sudo -u "${APP_USER}" XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR}" systemctl --user enable --now "vault-agent-${APPN}.service"
# ===== NEU v4.7: HARDCODED RESTART + STATUS + LOG-TAIL (zusätzlich, ohne bestehende Logs zu ändern) =====
info "service restart (erzwinge Neustart des user-services)"
sudo -u "${APP_USER}" XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR}" systemctl --user restart "vault-agent-${APPN}.service"
info "service status (kurz, ohne Pager)"
sudo -u "${APP_USER}" XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR}" systemctl --user status "vault-agent-${APPN}.service" --no-pager -l | sed 's/^/ 📄 /'
# ===== Quick check (UNVERÄNDERT) =====
CERT="${TLS_DIR}/${APPN}.fullchain.pem"
if sudo -u "${APP_USER}" test -s "${CERT}"; then
ok "Leaf vorhanden: ${CERT}"
openssl x509 -in "${CERT}" -noout -subject -issuer -enddate | sed 's/^/ 🔎 /'
else
warn "Noch kein Zertifikat: ${CERT} (kommt i.d.R. kurz darauf). Logs:"
sudo -u "${APP_USER}" XDG_RUNTIME_DIR="/run/user/$(id -u "${APP_USER}")" journalctl --user -u "vault-agent-${APPN}.service" -n 60 --no-pager || true
fi
# ===== NEU v4.7: IMMER Logs tailen (zusätzlich zur Quick-Check-Branch oben) =====
info "journal tail (letzte ${JOURNAL_TAIL_LINES} Zeilen, ohne Pager)"
sudo -u "${APP_USER}" XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR}" journalctl --user -u "vault-agent-${APPN}.service" -n "${JOURNAL_TAIL_LINES}" --no-pager | sed 's/^/ 🪵 /'
ok "SUCCESS v4.7 → app=${APPN} env=${ENV_NAME} role=${PKI_ROLE} auth=${AUTH_METHOD} kv_mount=${KV_MOUNT} allowed_domains=${ALLOWED}"