90 lines
4.1 KiB
Bash
Executable file
90 lines
4.1 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Provisions a dedicated, full-sudo login for an AI coding agent on this VPS.
|
|
# Idempotent — safe to re-run (e.g. to rotate the key or re-apply hardening
|
|
# after an OS upgrade). Run as root (or via an existing sudo-capable user):
|
|
#
|
|
# sudo bash /opt/thermograph/deploy/provision-agent-access.sh
|
|
#
|
|
# The agent's public key is embedded below (AGENT_PUBKEY) rather than passed
|
|
# as an argument, so this file alone is the whole bootstrap — copy the current
|
|
# one from the repo and run it; there's nothing else to fetch or fill in.
|
|
#
|
|
# What this does, and why it's a SEPARATE user rather than direct root login:
|
|
# - A distinct login name ("agent") gives a clean "who did this" audit trail
|
|
# that shared/root login destroys. It is still full-root-capable via
|
|
# passwordless sudo — this is a full-trust grant, just an attributable and
|
|
# independently revocable one.
|
|
# - Revocation is one line: delete AGENT_PUBKEY's line from
|
|
# /home/agent/.ssh/authorized_keys, or `deluser --remove-home agent` +
|
|
# `rm /etc/sudoers.d/agent`. Your own access is untouched either way.
|
|
#
|
|
# This script does NOT touch the app, Docker, Postgres, or Terraform-managed
|
|
# state — it only manages the "agent" login and sshd/auditd hardening.
|
|
set -euo pipefail
|
|
|
|
# --- fill this in when rotating the key; the current value is the one
|
|
# generated for this engagement --------------------------------------------
|
|
AGENT_PUBKEY="${AGENT_PUBKEY:-ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICUwrp/neW5UJVmsK9SRveghJ+ayX3r59qgkL9Zo0mqO claude-agent@thermograph-infra}"
|
|
|
|
AGENT_USER="${AGENT_USER:-agent}"
|
|
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
echo "Run as root (sudo bash $0)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> User: $AGENT_USER"
|
|
if ! id "$AGENT_USER" >/dev/null 2>&1; then
|
|
adduser --disabled-password --gecos "" "$AGENT_USER"
|
|
fi
|
|
|
|
echo "==> Passwordless sudo (full, by design — see header comment)"
|
|
echo "$AGENT_USER ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/"$AGENT_USER"
|
|
chmod 440 /etc/sudoers.d/"$AGENT_USER"
|
|
visudo -cf /etc/sudoers.d/"$AGENT_USER" # fail loudly on a syntax error rather than silently locking sudo
|
|
|
|
echo "==> Authorized key"
|
|
install -d -m 700 -o "$AGENT_USER" -g "$AGENT_USER" /home/"$AGENT_USER"/.ssh
|
|
# Replace wholesale rather than append, so re-running after a key rotation
|
|
# doesn't accumulate stale keys.
|
|
echo "$AGENT_PUBKEY" > /home/"$AGENT_USER"/.ssh/authorized_keys
|
|
chown "$AGENT_USER":"$AGENT_USER" /home/"$AGENT_USER"/.ssh/authorized_keys
|
|
chmod 600 /home/"$AGENT_USER"/.ssh/authorized_keys
|
|
|
|
echo "==> sshd hardening (fleet-wide, not just for this account)"
|
|
SSHD_DROPIN=/etc/ssh/sshd_config.d/99-agent-hardening.conf
|
|
cat > "$SSHD_DROPIN" <<'EOF'
|
|
# Managed by deploy/provision-agent-access.sh — key-only login, no passwords.
|
|
# Root itself still can't password in; PermitRootLogin prohibit-password
|
|
# leaves direct root key-login available for a human who already has a key
|
|
# there, without opening a password path for anyone.
|
|
PasswordAuthentication no
|
|
KbdInteractiveAuthentication no
|
|
PermitRootLogin prohibit-password
|
|
EOF
|
|
sshd -t # validate before reload; a bad config here can lock out the whole box
|
|
systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true
|
|
|
|
echo "==> auditd: log root-effective commands (survives shell history -c)"
|
|
if ! command -v auditctl >/dev/null 2>&1; then
|
|
apt-get update -y -q
|
|
apt-get install -y -q auditd audispd-plugins
|
|
fi
|
|
AUDIT_RULES=/etc/audit/rules.d/agent-root-cmds.rules
|
|
cat > "$AUDIT_RULES" <<'EOF'
|
|
# Every root-effective exec, tagged for `ausearch -k agentcmd`.
|
|
-a always,exit -F arch=b64 -S execve -F euid=0 -k agentcmd
|
|
-a always,exit -F arch=b32 -S execve -F euid=0 -k agentcmd
|
|
EOF
|
|
augenrules --load 2>/dev/null || true
|
|
systemctl enable --now auditd 2>/dev/null || true
|
|
|
|
echo
|
|
echo "Done. Verify from your own machine (not this box):"
|
|
echo " ssh -i <agent-private-key> $AGENT_USER@<this-host> sudo whoami # -> root"
|
|
echo " ssh -o PasswordAuthentication=no <any-user>@<this-host> # password path confirmed dead"
|
|
echo
|
|
echo "Revoke any time: delete the key line in"
|
|
echo " /home/$AGENT_USER/.ssh/authorized_keys"
|
|
echo "or remove the account entirely:"
|
|
echo " deluser --remove-home $AGENT_USER && rm -f /etc/sudoers.d/$AGENT_USER"
|