#!/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_USER@ sudo whoami # -> root" echo " ssh -o PasswordAuthentication=no @ # 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"