vault-ops/archiv/setup-vault-agent5.sh
2025-09-22 10:27:22 +02:00

265 lines
9.4 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
# =========================
# Config you may tweak
# =========================
: "${VAULT_ADDR:=http://127.0.0.1:22300}" # where Vault API is reachable
: "${PKI_MOUNT:=pki-test}" # PKI mount path
: "${ISSUE_TTL:=720h}" # default cert TTL
: "${LOG_LEVEL:=info}" # vault agent -log-level
: "${VAULT_BIN:=/usr/bin/vault}" # vault binary
: "${SYSTEMD_BIN:=/usr/bin/systemctl}" # systemctl binary
# =========================
# Required admin token
# =========================
if [[ -z "${VAULT_ADMIN_TOKEN:-}" ]]; then
echo "[ERROR] VAULT_ADMIN_TOKEN missing. Run as: VAULT_ADMIN_TOKEN='hvs.XXXX' $0 <app> <user> <fqdn>" >&2
exit 2
fi
export VAULT_ADDR VAULT_TOKEN="${VAULT_ADMIN_TOKEN}"
# =========================
# Inputs
# =========================
APP_NAME="${1:?usage: $0 <app-name> <target-user> <fqdn>}"
TARGET_USER="${2:?usage: $0 <app-name> <target-user> <fqdn>}"
COMMON_NAME="${3:?usage: $0 <app-name> <target-user> <fqdn>}"
ROLE_NAME="${APP_NAME}-pki-issue"
POLICY_NAME="pki-issue-${APP_NAME}"
PKI_ROLE="nginx-${APP_NAME}"
HOME_DIR="/home/${TARGET_USER}"
AGENT_DIR="${HOME_DIR}/.vault-agent-${APP_NAME}"
TLS_DIR="${HOME_DIR}/tls"
USER_UNIT="${HOME_DIR}/.config/systemd/user/vault-agent-${APP_NAME}.service"
SYS_UNIT="/etc/systemd/system/vault-agent-${APP_NAME}.service"
_ts(){ date +"[%Y-%m-%d %H:%M:%S]"; }
log(){ echo "$(_ts) [INFO] $*"; }
warn(){ echo "$(_ts) [WARN] $*" >&2; }
err(){ echo "$(_ts) [ERROR] $*" >&2; }
log "Starting on host $(hostname) for APP=${APP_NAME}, USER=${TARGET_USER}, CN=${COMMON_NAME}"
log "VAULT_ADDR=${VAULT_ADDR}"
log "PKI_MOUNT=${PKI_MOUNT} PKI_ROLE=${PKI_ROLE}"
# Ensure user exists and has home
if ! id -u "${TARGET_USER}" >/dev/null 2>&1; then
err "User ${TARGET_USER} does not exist"; exit 3
fi
install -d -m 0755 -o "${TARGET_USER}" -g "${TARGET_USER}" "${HOME_DIR}"
# ======================================
# 1) Policy allowing issue + approle ops
# ======================================
log "Writing policy: ${POLICY_NAME}"
POLICY_FILE="$(mktemp)"
cat >"${POLICY_FILE}" <<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_BIN} policy write "${POLICY_NAME}" "${POLICY_FILE}" >/dev/null
rm -f "${POLICY_FILE}"
# ======================================
# 2) AppRole (unlimited secret-id)
# ======================================
log "Creating AppRole: ${ROLE_NAME}"
${VAULT_BIN} 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 >/dev/null
# ======================================
# 3) PKI role (CN/domain policy)
# ======================================
log "Upserting PKI role: ${PKI_ROLE}"
${VAULT_BIN} write "${PKI_MOUNT}/roles/${PKI_ROLE}" \
allowed_domains="int.privsec.ch" \
allow_subdomains=true \
allow_bare_domains=false \
allow_wildcard_certificates=false \
max_ttl="720h" >/dev/null || true
# ======================================
# 4) Fetch AppRole creds and place for user
# ======================================
ROLE_ID="$(${VAULT_BIN} read -field=role_id "auth/approle/role/${ROLE_NAME}/role-id")"
SECRET_ID="$(${VAULT_BIN} write -f -field=secret_id "auth/approle/role/${ROLE_NAME}/secret-id")"
log "RoleID: ${ROLE_ID}"
log "SecretID: ${SECRET_ID:0:6}********"
sudo -u "${TARGET_USER}" install -d -m 0700 "${AGENT_DIR}"
sudo -u "${TARGET_USER}" bash -c "
umask 077
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'
"
# ======================================
# 5) Agent config (pid, auto_auth, sink, template)
# ======================================
log "Writing agent config"
sudo -u "${TARGET_USER}" tee "${AGENT_DIR}/vault-agent.hcl" >/dev/null <<EOF
pid_file = "${AGENT_DIR}/pidfile"
auto_auth {
method "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 {
source = "${AGENT_DIR}/cert.tpl"
destination = "${AGENT_DIR}/.issue.json"
command = "${AGENT_DIR}/bin/vault-agent-post.sh"
error_on_missing_key = true
}
EOF
# ======================================
# 6) Template that emits JSON (easy for jq)
# ======================================
log "Writing certificate template"
sudo -u "${TARGET_USER}" tee "${AGENT_DIR}/cert.tpl" >/dev/null <<EOF
{{ with secret "${PKI_MOUNT}/issue/${PKI_ROLE}" "common_name=${COMMON_NAME}" "alt_names=${COMMON_NAME}" "ttl=${ISSUE_TTL}" }}
{
"certificate": {{ toJSON .Data.certificate }},
"issuing_ca": {{ toJSON .Data.issuing_ca }},
"ca_chain": {{ toJSON .Data.ca_chain }},
"private_key": {{ toJSON .Data.private_key }}
}
{{ end }}
EOF
# ======================================
# 7) Post-script: minimal schreibt key/fullchain nach ~/tls
# ======================================
log "Writing post-script (minimal)"
sudo -u "${TARGET_USER}" install -d -m 0755 "${AGENT_DIR}/bin" "${TLS_DIR}"
# Immer überschreiben (tee) super simpel.
sudo -u "${TARGET_USER}" tee "${AGENT_DIR}/bin/vault-agent-post.sh" >/dev/null <<'EOS'
#!/usr/bin/env bash
set -Eeuo pipefail
# Eingaben/Orte
JSON="${1:-./.issue.json}" # Agent rendert hierhin; WorkingDirectory ist AGENT_DIR
APP="${APP_NAME:-__APP__}" # wird vom Setup-Script ersetzt
OUT="${HOME}/tls"
echo "[post] Processing ${JSON} -> ${OUT}/${APP}.*"
# Temp-Ordner, sichere Rechte
umask 077
mkdir -p "${OUT}"
tmp="$(mktemp -d "${OUT}/.staging.XXXX")"
# Abbruch wenn jq fehlt
command -v jq >/dev/null || { echo "[post] ERROR: jq not found"; exit 1; }
# Key schreiben
jq -r '.private_key' "${JSON}" > "${tmp}/${APP}.key"
# Fullchain schreiben (cert + ca_chain (array|string) oder issuing_ca)
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}.fullchain.pem"
# Final installieren
install -m 600 "${tmp}/${APP}.key" "${OUT}/${APP}.key"
install -m 644 "${tmp}/${APP}.fullchain.pem" "${OUT}/${APP}.fullchain.pem"
rm -rf "${tmp}"
echo "[post] wrote ${OUT}/${APP}.key and ${APP}.fullchain.pem"
EOS
# APP-Placeholder auf echten Namen setzen und ausführbar machen
sudo -u "${TARGET_USER}" sed -i "s|__APP__|${APP_NAME}|g" "${AGENT_DIR}/bin/vault-agent-post.sh"
sudo -u "${TARGET_USER}" chmod +x "${AGENT_DIR}/bin/vault-agent-post.sh"
# Sofortige Ausführung beim nächsten Render erzwingen
sudo -u "${TARGET_USER}" touch "${AGENT_DIR}/cert.tpl"
# ======================================
# 8) user systemd unit (preferred)
# ======================================
log "Writing user unit"
sudo -u "${TARGET_USER}" install -d -m 0755 "${HOME_DIR}/.config/systemd/user"
sudo -u "${TARGET_USER}" tee "${USER_UNIT}" >/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=${VAULT_BIN} agent -log-level=${LOG_LEVEL} -config=${AGENT_DIR}/vault-agent.hcl
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=default.target
EOF
# ======================================
# 9) Start preferred USER unit (no fallback here)
# ======================================
log "Enable linger and start user@ for ${TARGET_USER}"
APP_UID="$(id -u "${TARGET_USER}")"
loginctl enable-linger "${TARGET_USER}" >/dev/null || true
# ensure the per-user instance exists (Debian/Ubuntu needs this sometimes)
${SYSTEMD_BIN} start "user@${APP_UID}.service" >/dev/null || true
XDG_RUNTIME_DIR="/run/user/${APP_UID}"
export XDG_RUNTIME_DIR
# Some distros require the runtime dir to exist when called from sudo/root
mkdir -p "${XDG_RUNTIME_DIR}" && chown "${TARGET_USER}:${TARGET_USER}" "${XDG_RUNTIME_DIR}" || true
log "Reloading user units and starting agent"
sudo -u "${TARGET_USER}" XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR}" ${SYSTEMD_BIN} --user daemon-reload
sudo -u "${TARGET_USER}" XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR}" ${SYSTEMD_BIN} --user enable --now "vault-agent-${APP_NAME}.service"
# ======================================
# 10) Show live status & verify outputs
# ======================================
log "User-unit status:"
sudo -u "${TARGET_USER}" XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR}" ${SYSTEMD_BIN} --user status "vault-agent-${APP_NAME}.service" --no-pager || true
log "Recent logs (last 30 lines):"
sudo -u "${TARGET_USER}" XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR}" journalctl --user -u "vault-agent-${APP_NAME}.service" -n 30 -o cat || true
log "Quick verification:"
CERT="${TLS_DIR}/${APP_NAME}.fullchain.pem"
KEY="${TLS_DIR}/${APP_NAME}.key"
if sudo -u "${TARGET_USER}" test -s "${CERT}"; then
SIZE="$(sudo -u "${TARGET_USER}" wc -c < "${CERT}" || echo 0)"
log "TLS fullchain present: ${CERT} (${SIZE} bytes)"
openssl x509 -in "${CERT}" -noout -subject -enddate | sed "s/^/[$(_ts)] /" || true
else
warn "TLS fullchain not found: ${CERT}"
fi
echo
log "SUCCESS: Vault Agent for ${APP_NAME} ready."
echo "[INFO] TLS: ${KEY} | ${CERT}"
echo "[INFO] Agent: ${AGENT_DIR} | Service: user-unit"