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

261 lines
9.1 KiB
Bash
Executable file

#!/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:=5m}" # 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="${COMMON_NAME}" \
allow_subdomains=true \
allow_bare_domains=true \
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"
}
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}" "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: write key/fullchain to ~/tls
# ======================================
log "Writing post-script"
sudo -u "${TARGET_USER}" install -d -m 0755 "${AGENT_DIR}/bin" "${TLS_DIR}"
sudo -u "${TARGET_USER}" tee "${AGENT_DIR}/bin/vault-agent-post.sh" >/dev/null <<'EOS'
#!/usr/bin/env bash
set -Eeuo pipefail
JSON="${1:-./.issue.json}"
APP="${APP_NAME:-__APP__}"
OUT="${HOME}/tls"
# APP placeholder is replaced at install-time by the main script
if [[ "${APP}" == "__APP__" ]]; then
APP="$(basename "${PWD}")" || APP="app"
fi
echo "[post] Processing ${JSON} -> ${OUT}/${APP}.*"
tmp="$(mktemp -d "${OUT}/.staging.XXXX")"
umask 077
# Make sure jq exists
command -v jq >/dev/null || { echo "[post] ERROR: jq not found"; exit 1; }
# Private key
jq -r '.private_key' "${JSON}" > "${tmp}/${APP}.key"
# Full chain (certificate + CA chain)
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"
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
# replace placeholder with real app name
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"
# ======================================
# 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"