Files
2026-07-15 21:22:15 -07:00

106 lines
3.5 KiB
YAML

---
# One-job playbook: connect as root, create the automation user that every
# later run uses — key-only auth, passwordless sudo — verify it actually
# works, then lock sshd down to keys. Run once per fresh VPS:
#
# ansible-playbook bootstrap.yml
#
# It prompts for the root SSH password (needs sshpass on the control
# node). If root already authenticates with your key, just press Enter.
#
# Afterwards set `ansible_user: ansible` in inventory.yml and never
# connect as root again. Overridable with -e:
# bootstrap_user=ansible
# bootstrap_ssh_public_key_file=~/.ssh/id_ed25519.pub
# bootstrap_harden_ssh=true
#
# Safe by construction: play 2 logs in AS the new user and proves key +
# sudo work; sshd is only hardened in play 3, which never runs if play 2
# fails. Play 3 disables root SSH login entirely — from then on the only
# way in is the automation user's key (console access via the RackNerd
# panel remains the break-glass path).
- name: Create the automation user (connects as root)
hosts: vpnhub
gather_facts: false
vars_prompt:
- name: bootstrap_root_password
prompt: "Root SSH password (Enter if root already uses your key)"
private: true
default: ""
vars:
ansible_user: root # play var — outranks inventory on purpose
ansible_password: "{{ bootstrap_root_password }}"
bootstrap_user: ansible
bootstrap_ssh_public_key_file: "~/.ssh/togo-ansible.pub"
tasks:
- name: Create the user (no password hash set, account password locked)
ansible.builtin.user:
name: "{{ bootstrap_user }}"
shell: /bin/bash
create_home: true
password_lock: true
- name: Install the SSH public key
ansible.posix.authorized_key:
user: "{{ bootstrap_user }}"
key: "{{ lookup('file', bootstrap_ssh_public_key_file) }}"
- name: Grant passwordless sudo
ansible.builtin.copy:
dest: "/etc/sudoers.d/{{ bootstrap_user }}"
content: "{{ bootstrap_user }} ALL=(ALL) NOPASSWD:ALL\n"
owner: root
group: root
mode: "0440"
validate: /usr/sbin/visudo -cf %s
- name: Prove the new user works before touching sshd
hosts: vpnhub
gather_facts: false
become: true
vars:
bootstrap_user: ansible
ansible_user: "{{ bootstrap_user }}"
tasks:
- name: Key login and passwordless sudo both work
ansible.builtin.command: whoami
register: bootstrap_whoami
changed_when: false
failed_when: bootstrap_whoami.stdout != "root"
- name: Lock sshd down to key-only auth, no root login
hosts: vpnhub
gather_facts: false
become: true
vars:
bootstrap_user: ansible
ansible_user: "{{ bootstrap_user }}"
bootstrap_harden_ssh: true
handlers:
- name: restart sshd
ansible.builtin.service:
name: ssh
state: restarted
tasks:
- name: Key-only sshd drop-in
# Named 01-* on purpose: sshd honors the FIRST value it reads per
# keyword, and cloud-init images ship 50-cloud-init.conf that may
# set PasswordAuthentication yes — we must sort before it.
ansible.builtin.copy:
dest: /etc/ssh/sshd_config.d/01-vpnhub.conf
content: |
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
owner: root
group: root
mode: "0644"
when: bootstrap_harden_ssh | bool
notify: restart sshd
- name: Validate full sshd config before the restart fires
ansible.builtin.command: /usr/sbin/sshd -t
when: bootstrap_harden_ssh | bool
changed_when: false