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

229 lines
10 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-mtls-client-config-v4.5.sh
# Datum: 2025-09-29
# ========================= FEATURE MANIFEST (mTLS v4.5) ======================
# 🔒 Zweck / Sicherheit
# 1) Bereitet STRICT mTLS-Login für den Vault-Agent vor (auth/cert).
# 2) Keine AppRole, kein Fallback dieses Script erzeugt NUR Client-mTLS & Mapping.
#
# 📦 Konfiguration / YAML
# 3) Liest ./config/apps.yaml:
# environments.<env>.pki_mount
# environments.<env>.app.user (Default-App-User)
# apps[].{name, user?} (per-App User-Override)
#
# 🧩 Pfade (kompatibel zu Agent v4.2+)
# 4) Schreibt Login-mTLS nach: ~/<BASE>/mtls/{agent.crt,agent.key} (BASE=default "vault")
# 5) Schreibt Vault-TLS-CA nach: ~/<BASE>/ca/ca.pem
# ✅ BASE via --home-base <ordner> wählbar (z. B. "vault-sidecar" oder "vault-pki").
#
# 🛠️ PKI / Client-Zert
# 6) Erzwingt/aktualisiert CLIENT-Rolle (client_flag=true, server_flag=false, EC P-256).
# 7) Stellt Client-Zert aus:
# a) Standard: CN=agent-<app>-<suffix>.<env>.privsec.ch (ttl=720h)
# • <suffix> via --cn-suffix (Default = <home-base>)
# b) Neu: EXAKTER CN via --cn "<wert>" (übersteuert a)
#
# 🔐 Vault auth/cert (Mapping)
# 8) Aktiviert auth/cert (idempotent) und legt Mapping an:
# - certificate = Issuing CA der Client-Rolle
# - allowed_common_names = CN
# - policies = <mapping-policy>
# ✅ Neu: --mapping-policy <policy> (Default: pki-issue-<app>)
# - name des Mappings via --mapping-name (Default: <app>-<env>-<suffix> bzw. frei wählbar)
#
# 🔧 Ownership / Permissions
# 9) KEY 0600, CRT 0644, CA 0644; Owner = Linux-User aus YAML.
#
# 🧪 Ausgabe / Checks
# 10) OpenSSL-Checks (subject/issuer/dates) + farbige Zusammenfassung.
# 11) Warnung, falls Policy pki-issue-<app> noch nicht existiert (Agent-Setup erzeugt sie i.d.R.).
#
# 🧰 CLI-Flags / Defaults
# 12) Flags: --env (default: test), --config (default: ./config/apps.yaml),
# --app (erforderlich), --pki-mount (optional Override),
# --home-base <ordner> (default: vault),
# --cn-suffix <suffix> (default: == home-base),
# --cn <exact-cn> (übersteuert cn-suffix),
# --mapping-name <name> (default: <app>-<env>-<suffix>),
# --mapping-policy <policy> (default: pki-issue-<app>)
#
# 🚦 Exit-Codes
# 13) 2=Args/Env unvollständig; 3=Linux-User fehlt; 6=PKI-Issue fehlgeschlagen.
# ============================================================================
# ================================ EXAMPLES ==================================
# --- PKI (UNIX-User, z. B. /home/nctest/vault = PKI) ---
# • Standard (BASE=~/vault, Policy=pki-issue-<app>, Mapping=<app>-<env>-vault):
# sudo -E ./setup-vault-agent-mtls-client-config-v4.5.sh --app nctest --env test
# • BASE benennen & Suffix anpassen:
# sudo -E ./setup-vault-agent-mtls-client-config-v4.5.sh --app nctest --env test --home-base vault-pki --cn-suffix pki --mapping-name nctest-test-pki
#
# --- KV (Sidecar, eigenes Verzeichnis, exakter CN & KV-Policy) ---
# • Mapping / Policy für SECRET-AGENT:
# sudo -E ./setup-vault-agent-mtls-client-config-v4.5.sh \
# --app nctest --env test --home-base vault-sidecar \
# --cn "agent-nctest.test.privsec.ch" \
# --mapping-name "agent-nctest" \
# --mapping-policy "secret-agent-nctest-policy"
#
# Quick-Tests nach Lauf:
# • CRT anzeigen: openssl x509 -in ~/<BASE>/mtls/agent.crt -noout -subject -issuer -dates
# • Mapping check: vault read auth/cert/certs/<mapping-name>
# • Live-Verify: vault-tls-check --addr 127.0.0.1:22300 --sni vault.test.privsec.ch \
# --cafile ~/<BASE>/ca/ca.pem --mtls-cert ~/<BASE>/mtls/agent.crt --mtls-key ~/<BASE>/mtls/agent.key
# ============================================================================
# ===== 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; }
# ===== Dependencies =====
need(){ command -v "$1" >/dev/null || { err "missing: $1"; exit 2; }; }
need vault; need jq; need python3; need install; need openssl
# ===== Defaults / Args =====
: "${DEFAULT_ENV:=test}"
: "${DEFAULT_CONFIG:=./config/apps.yaml}"
: "${DEFAULT_HOME_BASE:=vault}"
[[ -n "${VAULT_ADDR:-}" && -n "${VAULT_TOKEN:-}" ]] || { err "VAULT_ADDR/VAULT_TOKEN required"; exit 2; }
ENV_NAME="$DEFAULT_ENV"; CFG="$DEFAULT_CONFIG"; APPN=""
PKI_MOUNT_OVERRIDE=""; HOME_BASE="$DEFAULT_HOME_BASE"
CN_SUFFIX=""; CN_EXACT=""
MAPPING_NAME=""; MAPPING_POLICY=""
while [[ $# -gt 0 ]]; do
case "$1" in
--env) ENV_NAME="$2"; shift 2;;
--config) CFG="$2"; shift 2;;
--app) APPN="$2"; shift 2;;
--pki-mount) PKI_MOUNT_OVERRIDE="$2"; shift 2;;
--home-base) HOME_BASE="$2"; shift 2;;
--cn-suffix) CN_SUFFIX="$2"; shift 2;;
--cn) CN_EXACT="$2"; shift 2;;
--mapping-name) MAPPING_NAME="$2"; shift 2;;
--mapping-policy) MAPPING_POLICY="$2"; shift 2;;
-h|--help) sed -n '1,300p' "$0"; exit 0;;
*) err "unknown arg: $1"; exit 2;;
esac
done
[[ -n "$APPN" ]] || { err "--app required"; exit 2; }
[[ -n "$HOME_BASE" ]] || { err "--home-base must be non-empty"; exit 2; }
case "$HOME_BASE" in /*|*/*|*..*) err "--home-base must be a simple name under the user's home (no '/', no '..')"; exit 2;; esac
# ===== 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"; }
PKI_MOUNT="${PKI_MOUNT_OVERRIDE:-$(jqenv '.pki_mount')}"
APP_USER_DEF="$(jqenv '.app.user')"
APP_USER_APP="$(jqapp '.user')"
APP_USER="$APP_USER_DEF"; [[ "$APP_USER_APP" != "null" ]] && APP_USER="$APP_USER_APP"
[[ "$PKI_MOUNT" != "null" && -n "$PKI_MOUNT" ]] || { err "pki_mount missing (YAML/env or --pki-mount)"; exit 2; }
[[ -n "$APP_USER" && "$APP_USER" != "null" ]] || { err "app user missing (YAML)"; exit 2; }
id -u "$APP_USER" >/dev/null 2>&1 || { err "Linux-User $APP_USER fehlt"; exit 3; }
# ===== Pfade (BASE-Override) =====
HOME_DIR="/home/${APP_USER}"
BASE_DIR="${HOME_DIR}/${HOME_BASE}"
MTLS_DIR="${BASE_DIR}/mtls"
CA_DIR="${BASE_DIR}/ca"
CRT="${MTLS_DIR}/agent.crt"
KEY="${MTLS_DIR}/agent.key"
CA_FILE="${CA_DIR}/ca.pem"
sudo install -d -m 0755 -o "$APP_USER" -g "$APP_USER" "$BASE_DIR" "$CA_DIR" "$MTLS_DIR"
info "BASE=${BASE_DIR} MTLS=${MTLS_DIR} CA=${CA_DIR}"
# ===== CN / Mapping Defaults =====
: "${CN_SUFFIX:=${HOME_BASE}}"
if [[ -n "$CN_EXACT" ]]; then
CN_AGENT="$CN_EXACT"
else
CN_AGENT="agent-${APPN}-${CN_SUFFIX}.${ENV_NAME}.privsec.ch"
fi
: "${MAPPING_NAME:=${APPN}-${ENV_NAME}-${CN_SUFFIX}}"
: "${MAPPING_POLICY:=pki-issue-${APPN}}"
ROLE="agent-mtls-${APPN}-${CN_SUFFIX}"
info "CN=${CN_AGENT} role=${PKI_MOUNT}/roles/${ROLE} mapping=auth/cert/certs/${MAPPING_NAME} policy=${MAPPING_POLICY}"
# ===== PKI-Clientrolle & Zertifikat =====
info "Upsert PKI client role…"
vault write "${PKI_MOUNT}/roles/${ROLE}" \
allowed_domains="${CN_AGENT}" \
allow_bare_domains=true \
allow_subdomains=false \
server_flag=false client_flag=true \
key_type="ec" key_bits=256 max_ttl="720h" >/dev/null || true
info "Issue client certificate…"
ISSUE_JSON="$(vault write -format=json "${PKI_MOUNT}/issue/${ROLE}" common_name="${CN_AGENT}" ttl=720h)" \
|| { err "PKI issue failed (role=${ROLE})"; exit 6; }
# Dateien schreiben (Owner, Rechte)
echo "$ISSUE_JSON" | jq -r '.data.private_key' | sudo install -m 0600 -o "$APP_USER" -g "$APP_USER" /dev/stdin "$KEY"
echo "$ISSUE_JSON" | jq -r '.data.certificate' | sudo install -m 0644 -o "$APP_USER" -g "$APP_USER" /dev/stdin "$CRT"
echo "$ISSUE_JSON" | jq -r '.data.issuing_ca' | sudo install -m 0644 -o "$APP_USER" -g "$APP_USER" /dev/stdin "$CA_FILE" || true
ok "mTLS material written → KEY=${KEY} CRT=${CRT} CA=${CA_FILE}"
# ===== auth/cert aktivieren & Mapping setzen =====
info "Enable auth/cert & upsert mapping…"
vault auth enable cert >/dev/null 2>&1 || true
TMP="$(mktemp)"; jq -r '.data.issuing_ca' <<<"$ISSUE_JSON" > "$TMP"
vault write "auth/cert/certs/${MAPPING_NAME}" \
certificate=@"$TMP" \
allowed_common_names="${CN_AGENT}" \
policies="${MAPPING_POLICY}" \
token_ttl="24h" >/dev/null
rm -f "$TMP"
ok "Mapping updated → auth/cert/certs/${MAPPING_NAME} (policies=${MAPPING_POLICY})"
# ===== Hinweis, falls Standard-Policy noch nicht existiert =====
if [[ "$MAPPING_POLICY" == "pki-issue-${APPN}" ]] && ! vault policy read "pki-issue-${APPN}" >/dev/null 2>&1; then
warn "Policy pki-issue-${APPN} existiert noch nicht das Agent-Setup v4.2+ erzeugt sie i.d.R. automatisch."
fi
# ===== Zusammenfassung / Checks =====
echo
ok "Certificate summary:"
openssl x509 -in "$CRT" -noout -subject -issuer -dates | sed 's/^/ 🔎 /'
if [[ -s "$CA_FILE" ]]; then
info "CA bundle peek:"
( set +e; openssl x509 -in "$CA_FILE" -noout -subject -issuer -enddate 2>/dev/null | sed 's/^/ 📜 /'; true )
fi
echo
ok "SUCCESS (v4.5)"
echo " User: ${APP_USER}"
echo " BASE: ${BASE_DIR}"
echo " CN: ${CN_AGENT}"
echo " Role: ${PKI_MOUNT}/roles/${ROLE}"
echo " Mapping: auth/cert/certs/${MAPPING_NAME}"
echo " Policy: ${MAPPING_POLICY}"
echo " Files: ${KEY} (0600), ${CRT} (0644), ${CA_FILE} (0644)"
echo
info "Next:"
echo " • Mount BASE for container if needed: -v ${BASE_DIR}:${BASE_DIR}:ro"
echo " • Test login via mTLS (example):"
echo " VAULT_ADDR=\"https://127.0.0.1:22300\" VAULT_CACERT=\"${CA_FILE}\" \\"
echo " vault login -method=cert -client-cert \"${CRT}\" -client-key \"${KEY}\""