vault-ops/infra/versions/distribute_ca_to_agents-v3.sh
2026-04-14 11:45:15 +07:00

193 lines
7 KiB
Bash
Executable file

#!/usr/bin/env bash
set -Eeuo pipefail
# distribute_ca_to_agents_v3.sh
#
# v3: Kann "intermediate" direkt aus Vault lesen (pki_mount/cert/ca),
# oder lokal "root" / "chain" vom Vault-Host kopieren.
#
# Beispiele:
# # Agents (Trust = INTERMEDIATE):
# sudo -E ./distribute_ca_to_agents_v3.sh \
# --env test --config ./config/apps.yaml \
# --which intermediate --users "nctest apptest"
#
# # Proxy einmalig mit Chain (I+R) befüllen:
# sudo -E ./distribute_ca_to_agents_v3.sh \
# --env test --config ./config/apps.yaml \
# --which chain --users proxytest \
# --dest "/home/proxytest/nginx/ca/current-ca-chain.pem"
#
# Optionen:
# --env <name> (z.B. test|prod)
# --config <file> (apps.yaml für vault_addr/pki_mount)
# --which intermediate|root|chain
# --src <pem> (überschreibt env/which für root/chain)
# --users "u1 u2 ..." (Space-separiert)
# --users-file <file> (ein User pro Zeile)
# --dest <PATH> (Ziel-Datei; Default: /home/<user>/vault/ca/ca.pem)
# --pki <mount> (übersteuert pki_mount aus config)
# --addr <URL> (übersteuert vault_addr aus config)
# --force (erzwingt Überschreiben)
#
# Erwartet für "intermediate":
# VAULT_TOKEN im Env (oder VAULT_ADMIN_TOKEN) + VAULT_ADDR (aus config/--addr)
#
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; }
need(){ command -v "$1" >/dev/null 2>&1 || { err "missing: $1"; exit 2; }; }
need sudo; need install; need getent
command -v openssl >/dev/null 2>&1 || warn "openssl not found → PEM-Check wird übersprungen"
command -v jq >/dev/null 2>&1 || warn "jq nicht gefunden → nur Datei-Mode verfügbar"
ENV_NAME=""; CFG=""; WHICH=""; SRC=""
USERS=""; USERS_FILE=""; DEST_OVERRIDE=""
PKI_MOUNT_OVERRIDE=""; ADDR_OVERRIDE=""
FORCE=0
while [[ $# -gt 0 ]]; do
case "$1" in
--env) ENV_NAME="$2"; shift 2;;
--config) CFG="$2"; shift 2;;
--which) WHICH="$2"; shift 2;;
--src) SRC="$2"; shift 2;;
--users) USERS="$2"; shift 2;;
--users-file) USERS_FILE="$2"; shift 2;;
--dest) DEST_OVERRIDE="$2"; shift 2;;
--pki) PKI_MOUNT_OVERRIDE="$2"; shift 2;;
--addr) ADDR_OVERRIDE="$2"; shift 2;;
--force) FORCE=1; shift;;
-h|--help) sed -n '1,200p' "$0"; exit 0;;
*) err "unknown arg: $1"; exit 2;;
esac
done
[[ -n "$WHICH" ]] || { err "--which intermediate|root|chain ist Pflicht"; exit 2; }
if [[ -z "$SRC" && -z "$ENV_NAME" ]]; then
err "entweder --src ODER --env angeben"; exit 2
fi
# ---- Users einsammeln ----
USERS_ARR=()
[[ -n "$USERS" ]] && read -r -a USERS_ARR <<<"$USERS"
if [[ -n "$USERS_FILE" ]]; then
[[ -r "$USERS_FILE" ]] || { err "users file not readable: $USERS_FILE"; exit 2; }
while IFS= read -r line; do
line="${line%%#*}"; line="$(echo "$line" | xargs || true)"
[[ -z "$line" ]] && continue
USERS_ARR+=("$line")
done < "$USERS_FILE"
fi
(( ${#USERS_ARR[@]} > 0 )) || { err "keine Nutzer angegeben (nutze --users oder --users-file)"; exit 2; }
# ---- Config laden (falls nötig) ----
CFGJSON=""
if [[ -n "$CFG" ]]; then
need python3; need jq
CFG_ABS="$(readlink -f "$CFG" 2>/dev/null || true)"
[[ -n "$CFG_ABS" && -r "$CFG_ABS" ]] || { err "config nicht lesbar: $CFG"; exit 2; }
CFGJSON="$(python3 - <<PY
import yaml, json, sys
print(json.dumps(yaml.safe_load(open("$CFG_ABS","r",encoding="utf-8"))))
PY
)"
fi
jqenv(){ echo "$CFGJSON" | jq -r ".environments.\"$ENV_NAME\"$1"; }
VAULT_ADDR="${ADDR_OVERRIDE:-}"
PKI_MOUNT="${PKI_MOUNT_OVERRIDE:-}"
if [[ -z "$SRC" ]]; then
case "$WHICH" in
root|chain)
SRC="/home/vault/tls-${ENV_NAME}/$([[ $WHICH == root ]] && echo root_ca.pem || echo ca_chain.pem)"
;;
intermediate)
[[ -n "$CFGJSON" ]] || { err "--config apps.yaml ist für 'intermediate' nötig"; exit 2; }
[[ -n "$VAULT_ADDR" ]] || VAULT_ADDR="$(jqenv '.vault_addr')"
[[ -n "$PKI_MOUNT" ]] || PKI_MOUNT="$(jqenv '.pki_mount')"
[[ -n "$VAULT_ADDR" && "$VAULT_ADDR" != "null" ]] || { err "vault_addr fehlt (config/--addr)"; exit 2; }
[[ -n "$PKI_MOUNT" && "$PKI_MOUNT" != "null" ]] || { err "pki_mount fehlt (config/--pki)"; exit 2; }
export VAULT_ADDR
if [[ -z "${VAULT_TOKEN:-}" && -n "${VAULT_ADMIN_TOKEN:-}" ]]; then
export VAULT_TOKEN="$VAULT_ADMIN_TOKEN"
fi
: "${VAULT_TOKEN:?VAULT_TOKEN oder VAULT_ADMIN_TOKEN im Env erforderlich für --which intermediate}"
need vault
TMP_SRC="$(mktemp)"
if vault read -format=json "${PKI_MOUNT}/cert/ca" | jq -r '.data.certificate' >"$TMP_SRC" && [[ -s "$TMP_SRC" ]]; then
SRC="$TMP_SRC"
else
err "konnte Intermediate aus Vault nicht lesen (${PKI_MOUNT}/cert/ca)"
exit 3
fi
;;
*) err "--which muss intermediate|root|chain sein"; exit 2;;
esac
fi
sudo test -r "$SRC" || { err "Quelle nicht lesbar (via sudo): $SRC"; exit 3; }
if command -v openssl >/dev/null 2>&1; then
if ! sudo openssl x509 -in "$SRC" -noout >/dev/null 2>&1; then
warn "openssl konnte keinen einzelnen Cert-Block parsen (bei Chain normal)."
fi
fi
info "Quelle: $SRC"
[[ -n "$VAULT_ADDR" ]] && info "Vault: $VAULT_ADDR"
[[ -n "$PKI_MOUNT" ]] && info "PKI: $PKI_MOUNT"
info "Modus: $WHICH"
echo
# ---- Kopieren pro User ----
COPIED=0; SKIPPED=0; MISSING=0; ERRORS=0
for u in "${USERS_ARR[@]}"; do
if ! id -u "$u" >/dev/null 2>&1; then
warn "user nicht gefunden: $u → skip"; ((MISSING++)); continue
fi
HOME_DIR="$(getent passwd "$u" | cut -d: -f6)"; [[ -n "$HOME_DIR" ]] || HOME_DIR="/home/$u"
# Default-Ziel
DEST="${DEST_OVERRIDE}"
if [[ -z "$DEST" ]]; then
DEST_DIR="${HOME_DIR}/vault/ca"
[[ "$WHICH" == "chain" ]] && DEST_FILE="ca-chain.pem" || DEST_FILE="ca.pem"
DEST="${DEST_DIR}/${DEST_FILE}"
fi
# Wenn mehrere Nutzer und ein absoluter identischer --dest → verhindern Clash
if (( ${#USERS_ARR[@]} > 1 )) && [[ -n "$DEST_OVERRIDE" && "$DEST_OVERRIDE" = /* ]]; then
err "--dest ist absolut und mehrere Nutzer angegeben → Pfad-Kollision. Bitte ohne --dest oder je User separat ausführen."
exit 2
fi
DEST_DIR="$(dirname "$DEST")"
sudo install -d -m 0755 -o "$u" -g "$u" "$DEST_DIR" >/dev/null
if [[ -f "$DEST" && $FORCE -eq 0 ]] && sudo cmp -s "$SRC" "$DEST"; then
ok "[$u] up-to-date → $DEST"; ((SKIPPED++)); continue
fi
if [[ -f "$DEST" && $FORCE -eq 0 ]]; then
ok "[$u] existiert → skip (nutze --force zum Überschreiben): $DEST"; ((SKIPPED++)); continue
fi
if sudo install -m 0644 -o "$u" -g "$u" "$SRC" "$DEST"; then
ok "[$u] geschrieben: $DEST"
((COPIED++))
else
err "[$u] schreiben fehlgeschlagen: $DEST"
((ERRORS++))
fi
done
echo
ok "Fertig. Copied=${COPIED}, Skipped=${SKIPPED}, MissingUsers=${MISSING}, Errors=${ERRORS}"