214 lines
9.7 KiB
Bash
Executable file
214 lines
9.7 KiB
Bash
Executable file
#!/usr/bin/env bash
|
||
set -Eeuo pipefail
|
||
# setup-vault-agent-mtls-client-config-v4.4.sh
|
||
# Datum: 2025-09-29
|
||
|
||
# ========================= FEATURE MANIFEST (mTLS v4.4) ======================
|
||
# 🔒 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
|
||
# ✅ Neu: BASE-Ordner via --home-base <ordner> (z. B. "vault-sidecar") wählbar.
|
||
#
|
||
# 🛠️ PKI / Client-Zert
|
||
# 6) Erzwingt/aktualisiert CLIENT-Rolle (client_flag=true, server_flag=false, EC P-256).
|
||
# 7) Stellt Client-Zert mit CN=agent-<app>-<suffix>.<env>.privsec.ch (ttl=720h) aus.
|
||
# ✅ Neu: <suffix> via --cn-suffix (Default: == <home-base>).
|
||
#
|
||
# 🔐 Vault auth/cert
|
||
# 8) Aktiviert auth/cert (idempotent) und legt Mapping an:
|
||
# - certificate = Issuing CA der Client-Rolle
|
||
# - allowed_common_names = CN
|
||
# - policies = pki-issue-<app>, token_ttl=24h
|
||
# ✅ Neu: Mapping-Name via --mapping-name (Default: <app>-<env>-<suffix>)
|
||
#
|
||
# 🔧 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),
|
||
# --mapping-name <name> (default: <app>-<env>-<suffix>)
|
||
#
|
||
# 🚦 Exit-Codes
|
||
# 13) 2=Args/Env unvollständig; 3=Linux-User fehlt; 6=PKI-Issue fehlgeschlagen.
|
||
# ============================================================================
|
||
#
|
||
# ================================ EXAMPLES ==================================
|
||
# --- Option B (umgekehrt): UNIX-User => ~/vault, Container => anderer Ordner ---
|
||
# • UNIX-User (Standard-Pfad ~/vault, Suffix=vault, Mapping=<app>-<env>-vault):
|
||
# sudo -E ./setup-vault-agent-mtls-client-config-v4.4.sh --app nctest --env test
|
||
# • Container/Sidecar (eigener Pfad ~/vault-sidecar, Suffix=sidecar, Mapping=nctest-test-sidecar):
|
||
# sudo -E ./setup-vault-agent-mtls-client-config-v4.4.sh --app nctest --env test --home-base vault-sidecar --cn-suffix sidecar --mapping-name nctest-test-sidecar
|
||
# • Weitere App (gleiches Muster):
|
||
# sudo -E ./setup-vault-agent-mtls-client-config-v4.4.sh --app apptest --env test
|
||
# sudo -E ./setup-vault-agent-mtls-client-config-v4.4.sh --app apptest --env test --home-base vault-sidecar --cn-suffix sidecar --mapping-name apptest-test-sidecar
|
||
# • PKI-Mount explizit:
|
||
# sudo -E ./setup-vault-agent-mtls-client-config-v4.4.sh --app nctest --pki-mount pki-test
|
||
# • Nur anderen BASE-Namen (Suffix wird automatisch == BASE):
|
||
# sudo -E ./setup-vault-agent-mtls-client-config-v4.4.sh --app nctest --home-base secrets
|
||
#
|
||
# Quick-Tests (nach dem Lauf):
|
||
# • CRT anzeigen: openssl x509 -in ~/<BASE>/mtls/agent.crt -noout -subject -issuer -dates
|
||
# • CA-Bundle Info: openssl x509 -in ~/<BASE>/ca/ca.pem -noout -subject -issuer -enddate || true
|
||
# • Mapping prüfen: vault read auth/cert/certs/<mapping-name>
|
||
# • Live-Check: 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=""; MAPPING_NAME=""
|
||
|
||
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;;
|
||
--mapping-name) MAPPING_NAME="$2"; shift 2;;
|
||
-h|--help) sed -n '1,260p' "$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}"
|
||
|
||
# ===== Suffix/Mapping Defaults =====
|
||
: "${CN_SUFFIX:=${HOME_BASE}}"
|
||
: "${MAPPING_NAME:=${APPN}-${ENV_NAME}-${CN_SUFFIX}}"
|
||
|
||
ROLE="agent-mtls-${APPN}-${CN_SUFFIX}"
|
||
CN_AGENT="agent-${APPN}-${CN_SUFFIX}.${ENV_NAME}.privsec.ch"
|
||
info "CN=${CN_AGENT} role=${PKI_MOUNT}/roles/${ROLE} mapping=auth/cert/certs/${MAPPING_NAME}"
|
||
|
||
# ===== 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="pki-issue-${APPN}" \
|
||
token_ttl="24h" >/dev/null
|
||
rm -f "$TMP"
|
||
ok "Mapping updated → auth/cert/certs/${MAPPING_NAME} (policies=pki-issue-${APPN})"
|
||
|
||
# ===== Hinweis, falls Policy noch nicht existiert =====
|
||
if ! 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.4)"
|
||
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 " 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}\""
|
||
|