279 lines
8.8 KiB
Bash
Executable file
279 lines
8.8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
# ======================================================
|
||
# Vault Agent One-Shot Provisioner (all inline, no deps)
|
||
# ======================================================
|
||
# Usage: ./setup-vault-agent.sh <APP_NAME> <TARGET_USER>
|
||
# Example: ./setup-vault-agent.sh test test
|
||
#
|
||
# Creates:
|
||
# - Policy pki-issue-<app>
|
||
# - AppRole <app>-pki-issue
|
||
# - PKI Role nginx-<app> (allowed_domains=<app>.example.com)
|
||
# - Agent config + template + post-script in /home/<user>/.vault-agent-<app>
|
||
# - systemd user service vault-agent-<app>.service (fallback: system-wide)
|
||
# - Writes TLS files to /home/<user>/tls/<app>.key + <app>.fullchain.pem
|
||
|
||
# ============================
|
||
# === Konfiguration ==========
|
||
# ============================
|
||
export VAULT_ADDR="${VAULT_ADDR:-http://127.0.0.1:22300}"
|
||
# !!! Dein Admin/Root Token hier eintragen oder via ENV setzen:
|
||
export VAULT_ADMIN_TOKEN="${VAULT_ADMIN_TOKEN:-}"
|
||
APP_NAME="${1:-test}" # 1. Argument: App-Name
|
||
TARGET_USER="${2:-test}" # 2. Argument: System-User
|
||
|
||
if [[ -z "$VAULT_ADMIN_TOKEN" ]]; then
|
||
echo "[ERROR] VAULT_ADMIN_TOKEN ist leer. Setze es vor dem Start oder trage es oben ein."
|
||
exit 2
|
||
fi
|
||
# Nutze explizit das Admin-Token für alle Vault-Calls:
|
||
export VAULT_TOKEN="${VAULT_ADMIN_TOKEN}"
|
||
|
||
ROLE_NAME="${APP_NAME}-pki-issue"
|
||
POLICY_NAME="pki-issue-${APP_NAME}"
|
||
AGENT_DIR="/home/${TARGET_USER}/.vault-agent-${APP_NAME}"
|
||
SERVICE_FILE="/home/${TARGET_USER}/.config/systemd/user/vault-agent-${APP_NAME}.service"
|
||
|
||
# PKI Mount/Rolle/Domain
|
||
PKI_MOUNT="pki-test"
|
||
PKI_ROLE="nginx-${APP_NAME}"
|
||
DOMAIN="${APP_NAME}.example.com"
|
||
|
||
# ============================
|
||
# === Helper ================
|
||
# ============================
|
||
log() { echo -e "\033[1;32m[INFO]\033[0m $*"; }
|
||
warn(){ echo -e "\033[1;33m[WARN]\033[0m $*"; }
|
||
err() { echo -e "\033[1;31m[ERROR]\033[0m $*" >&2; }
|
||
|
||
# ============================
|
||
# === Dependency-Checks ======
|
||
# ============================
|
||
for bin in vault jq systemctl; do
|
||
if ! command -v "$bin" >/dev/null 2>&1; then
|
||
err "Missing dependency: $bin (bitte installieren)"
|
||
exit 2
|
||
fi
|
||
done
|
||
|
||
# ============================
|
||
# === 1. Policy =============
|
||
# ============================
|
||
log "Erstelle Vault-Policy: ${POLICY_NAME}"
|
||
tmp_policy="$(mktemp)"
|
||
cat >"$tmp_policy" <<EOF
|
||
path "${PKI_MOUNT}/issue/${PKI_ROLE}" {
|
||
capabilities = ["create", "update"]
|
||
}
|
||
path "auth/approle/role/${ROLE_NAME}/role-id" {
|
||
capabilities = ["read"]
|
||
}
|
||
path "auth/approle/role/${ROLE_NAME}/secret-id" {
|
||
capabilities = ["create", "update"]
|
||
}
|
||
path "auth/token/renew-self" {
|
||
capabilities = ["update"]
|
||
}
|
||
EOF
|
||
|
||
vault policy write "${POLICY_NAME}" "$tmp_policy"
|
||
rm -f "$tmp_policy"
|
||
|
||
# ============================
|
||
# === 2. AppRole ============
|
||
# ============================
|
||
log "Erstelle AppRole: ${ROLE_NAME}"
|
||
vault write "auth/approle/role/${ROLE_NAME}" \
|
||
policies="${POLICY_NAME}" \
|
||
secret_id_ttl=0 \
|
||
secret_id_num_uses=0 \
|
||
token_ttl=24h \
|
||
token_max_ttl=0 \
|
||
bind_secret_id=true
|
||
|
||
# ============================
|
||
# === 3. PKI Role ===========
|
||
# ============================
|
||
log "Erstelle/aktualisiere PKI-Role: ${PKI_ROLE}"
|
||
vault write "${PKI_MOUNT}/roles/${PKI_ROLE}" \
|
||
allowed_domains="${DOMAIN}" \
|
||
allow_subdomains=true \
|
||
allow_bare_domains=true \
|
||
max_ttl="720h"
|
||
|
||
# ============================
|
||
# === 4. Credentials ========
|
||
# ============================
|
||
ROLE_ID="$(vault read -field=role_id "auth/approle/role/${ROLE_NAME}/role-id")"
|
||
SECRET_ID="$(vault write -f -field=secret_id "auth/approle/role/${ROLE_NAME}/secret-id")"
|
||
log "RoleID: ${ROLE_ID}"
|
||
log "SecretID: ${SECRET_ID:0:6}********"
|
||
|
||
# Schreibe sicher ins App-Home
|
||
sudo -u "${TARGET_USER}" bash -c "
|
||
umask 077
|
||
mkdir -p '${AGENT_DIR}/bin'
|
||
printf '%s\n' '${ROLE_ID}' > '${AGENT_DIR}/role_id'
|
||
printf '%s\n' '${SECRET_ID}' > '${AGENT_DIR}/secret_id'
|
||
chmod 600 '${AGENT_DIR}/role_id' '${AGENT_DIR}/secret_id'
|
||
mkdir -p '/home/${TARGET_USER}/tls'
|
||
"
|
||
|
||
# ============================
|
||
# === 5. Vault-Agent Config =
|
||
# ============================
|
||
log "Erstelle Vault-Agent-Konfiguration"
|
||
sudo -u "${TARGET_USER}" tee "${AGENT_DIR}/vault-agent.hcl" >/dev/null <<EOF
|
||
pid_file = "${AGENT_DIR}/pidfile"
|
||
|
||
auto_auth {
|
||
method "approle" {
|
||
mount_path = "auth/approle"
|
||
config = {
|
||
role_id_file_path = "${AGENT_DIR}/role_id"
|
||
secret_id_file_path = "${AGENT_DIR}/secret_id"
|
||
}
|
||
}
|
||
|
||
sink "file" {
|
||
config = {
|
||
path = "${AGENT_DIR}/token"
|
||
}
|
||
}
|
||
}
|
||
|
||
# Template rendert JSON in .issue.json; Post-Skript splittet nach ~/tls/
|
||
template {
|
||
source = "${AGENT_DIR}/cert.tpl"
|
||
destination = "${AGENT_DIR}/.issue.json"
|
||
command = ["${AGENT_DIR}/bin/vault-agent-post.sh"]
|
||
}
|
||
EOF
|
||
|
||
# ============================
|
||
# === 6. Template (JSON) ====
|
||
# ============================
|
||
log "Erstelle Cert-Template"
|
||
# ttl=5m: schnelles Testen der Rotation; für Prod anpassen/entfernen
|
||
sudo -u "${TARGET_USER}" tee "${AGENT_DIR}/cert.tpl" >/dev/null <<EOF
|
||
{{ with secret "${PKI_MOUNT}/issue/${PKI_ROLE}" "common_name=${DOMAIN}" "ttl=5m" }}
|
||
{
|
||
"certificate": "{{ .Data.certificate }}",
|
||
"issuing_ca": "{{ .Data.issuing_ca }}",
|
||
"ca_chain": {{ toJSON .Data.ca_chain }},
|
||
"private_key": "{{ .Data.private_key }}"
|
||
}
|
||
{{ end }}
|
||
EOF
|
||
|
||
# ============================
|
||
# === 7. Post-Skript ========
|
||
# ============================
|
||
log "Erstelle Post-Skript (TLS Files + optional Nginx reload)"
|
||
sudo -u "${TARGET_USER}" tee "${AGENT_DIR}/bin/vault-agent-post.sh" >/dev/null <<'EOF'
|
||
#!/usr/bin/env bash
|
||
set -Eeuo pipefail
|
||
|
||
# APP_NAME aus HOME ableiten (default); bei Bedarf via ENV überschreiben
|
||
APP_NAME="${APP_NAME:-$(basename "$HOME")}"
|
||
AGENT_DIR="$HOME/.vault-agent-${APP_NAME}"
|
||
JSON="$AGENT_DIR/.issue.json"
|
||
OUTDIR="$HOME/tls"
|
||
|
||
echo "[post] Processing $JSON -> $OUTDIR/${APP_NAME}.*"
|
||
mkdir -p "$OUTDIR"
|
||
umask 077
|
||
tmp="$(mktemp -d "$OUTDIR/.staging.XXXX")"
|
||
|
||
# Private Key extrahieren
|
||
jq -r .private_key "$JSON" > "$tmp/${APP_NAME}.key"
|
||
|
||
# Certificate + CA Chain sauber zusammenbauen (array/string/fallback)
|
||
jq -r '
|
||
.certificate,
|
||
(if (.ca_chain|type=="array") then (.ca_chain|join("\n"))
|
||
else if (.ca_chain|type=="string") then .ca_chain
|
||
else .issuing_ca end end)
|
||
' "$JSON" > "$tmp/${APP_NAME}.fullchain.pem"
|
||
|
||
# Sicher installieren
|
||
install -m 600 "$tmp/${APP_NAME}.key" "$OUTDIR/${APP_NAME}.key"
|
||
install -m 644 "$tmp/${APP_NAME}.fullchain.pem" "$OUTDIR/${APP_NAME}.fullchain.pem"
|
||
rm -rf "$tmp"
|
||
|
||
echo "[post] wrote $OUTDIR/${APP_NAME}.key and ${APP_NAME}.fullchain.pem"
|
||
|
||
# Optional: Nginx reload (ohne sudo). Falls sudo nötig: sudoers-Regel setzen
|
||
if systemctl is-active --quiet nginx; then
|
||
systemctl reload nginx || true
|
||
echo "[post] nginx reloaded"
|
||
fi
|
||
EOF
|
||
sudo -u "${TARGET_USER}" chmod +x "${AGENT_DIR}/bin/vault-agent-post.sh"
|
||
|
||
# ============================
|
||
# === 8. Systemd Service ====
|
||
# ============================
|
||
log "Erstelle systemd Service-Datei (user)"
|
||
sudo -u "${TARGET_USER}" mkdir -p "/home/${TARGET_USER}/.config/systemd/user"
|
||
sudo -u "${TARGET_USER}" tee "${SERVICE_FILE}" >/dev/null <<EOF
|
||
[Unit]
|
||
Description=Vault Agent (${APP_NAME}) - issue & rotate TLS certs
|
||
Wants=network-online.target
|
||
After=network-online.target
|
||
|
||
[Service]
|
||
Type=simple
|
||
WorkingDirectory=${AGENT_DIR}
|
||
Environment=VAULT_ADDR=${VAULT_ADDR}
|
||
ExecStart=/usr/bin/vault agent -config=${AGENT_DIR}/vault-agent.hcl
|
||
Restart=on-failure
|
||
RestartSec=5s
|
||
|
||
[Install]
|
||
WantedBy=default.target
|
||
EOF
|
||
|
||
# ============================
|
||
# === 9. Start Service =======
|
||
# ============================
|
||
log "Aktiviere linger für ${TARGET_USER} (User-Services nach Logout)"
|
||
loginctl enable-linger "${TARGET_USER}" >/dev/null 2>&1 || true
|
||
|
||
log "Starte systemd --user Service"
|
||
UID="$(id -u "${TARGET_USER}")"
|
||
if sudo -u "${TARGET_USER}" XDG_RUNTIME_DIR="/run/user/${UID}" systemctl --user daemon-reload 2>/dev/null; then
|
||
sudo -u "${TARGET_USER}" XDG_RUNTIME_DIR="/run/user/${UID}" systemctl --user enable --now "vault-agent-${APP_NAME}.service"
|
||
sudo -u "${TARGET_USER}" XDG_RUNTIME_DIR="/run/user/${UID}" systemctl --user status "vault-agent-${APP_NAME}.service" --no-pager || true
|
||
else
|
||
warn "systemctl --user nicht verfügbar – weiche auf systemweiten Service aus"
|
||
SYSTEM_SERVICE="/etc/systemd/system/vault-agent-${APP_NAME}.service"
|
||
sudo tee "${SYSTEM_SERVICE}" >/dev/null <<EOF
|
||
[Unit]
|
||
Description=Vault Agent (${APP_NAME}) - system service
|
||
Wants=network-online.target
|
||
After=network-online.target
|
||
|
||
[Service]
|
||
User=${TARGET_USER}
|
||
WorkingDirectory=${AGENT_DIR}
|
||
Environment=VAULT_ADDR=${VAULT_ADDR}
|
||
ExecStart=/usr/bin/vault agent -config=${AGENT_DIR}/vault-agent.hcl
|
||
Restart=on-failure
|
||
RestartSec=5s
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
EOF
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl enable --now "vault-agent-${APP_NAME}.service"
|
||
sudo systemctl status "vault-agent-${APP_NAME}.service" --no-pager || true
|
||
fi
|
||
|
||
log "SUCCESS: Vault Agent für ${APP_NAME} eingerichtet!"
|
||
echo
|
||
echo "TLS files unter: /home/${TARGET_USER}/tls/${APP_NAME}.key / ${APP_NAME}.fullchain.pem"
|
||
echo "Agent-Config unter: ${AGENT_DIR}"
|
||
|