Add guest playbooks: ssh hardening, reboot check, auto updates, service check, guest agent, admin user

Claude-Session: https://claude.ai/code/session_01JEczyvRjfakkiDNkbZA7PR
This commit is contained in:
Harwinder Sekhon
2026-09-06 20:27:50 -04:00
parent 686080a493
commit ce7e2bc205
6 changed files with 724 additions and 0 deletions
+209
View File
@@ -0,0 +1,209 @@
---
# Hardens sshd on every reachable Ubuntu/Rocky guest and turns on a fail2ban
# sshd jail. Settings go into /etc/ssh/sshd_config.d/00-ansible.conf, which
# sorts first and therefore wins over anything in sshd_config or the
# cloud-init drop-in (sshd keeps the first value it sees for each option).
#
# Lockout guard: password authentication is only disabled on hosts where
# root already has at least one authorized key.
#
# Extra vars (optional):
# ssh_disable_password_auth - default true
# ssh_max_auth_tries - default 4
- name: Check Host Availability
hosts: ubuntu:rocky
gather_facts: false
ignore_unreachable: true
tasks:
- name: Ping hosts to check availability
ansible.builtin.ping:
register: ping_result
ignore_errors: true
ignore_unreachable: true
- name: Add reachable hosts to group
ansible.builtin.group_by:
key: reachable_hosts
when: ping_result is succeeded
- name: Report offline hosts
ansible.builtin.debug:
msg: "{{ inventory_hostname }} - OFFLINE"
when: ping_result is not succeeded
delegate_to: localhost
- name: SSH Hardening - Install fail2ban on Ubuntu
hosts: ubuntu:&reachable_hosts
become: true
gather_facts: false
tasks:
- name: Install fail2ban and nftables via apt
ansible.builtin.apt:
name:
- fail2ban
- nftables
state: present
update_cache: true
cache_valid_time: 3600
- name: SSH Hardening - Install fail2ban on Rocky Linux
hosts: rocky:&reachable_hosts
become: true
gather_facts: false
tasks:
- name: Ensure EPEL repo is enabled
ansible.builtin.dnf:
name: epel-release
state: present
- name: Install fail2ban and nftables via dnf
ansible.builtin.dnf:
name:
- fail2ban
- nftables
state: present
- name: SSH Hardening - Configure sshd and fail2ban
hosts: reachable_hosts
become: true
gather_facts: false
vars:
ssh_disable_password_auth: true
ssh_max_auth_tries: 4
sshd_dropin_path: /etc/ssh/sshd_config.d/00-ansible.conf
tasks:
- name: Count public keys in root's authorized_keys
ansible.builtin.command: grep -cE '^(ssh-|ecdsa-|sk-)' /root/.ssh/authorized_keys
register: key_count
failed_when: false
changed_when: false
- name: Decide whether password authentication can be disabled
ansible.builtin.set_fact:
has_keys: "{{ key_count.rc == 0 and (key_count.stdout | int) > 0 }}"
- name: Warn when root has no authorized keys
ansible.builtin.debug:
msg: "WARNING: no authorized keys for root on {{ inventory_hostname }} - password authentication stays enabled"
when: not has_keys
- name: Ensure sshd_config.d directory exists
ansible.builtin.file:
path: /etc/ssh/sshd_config.d
state: directory
owner: root
group: root
mode: '0755'
- name: Ensure sshd_config includes the drop-in directory
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^Include\s+/etc/ssh/sshd_config\.d/\*\.conf'
line: 'Include /etc/ssh/sshd_config.d/*.conf'
insertbefore: BOF
backup: true
register: sshd_include
- name: Write sshd hardening drop-in
ansible.builtin.copy:
dest: "{{ sshd_dropin_path }}"
content: |
# Managed by Ansible (playbooks/ssh_harden.yml) - do not edit by hand
PermitRootLogin prohibit-password
PubkeyAuthentication yes
PasswordAuthentication {{ 'no' if (has_keys and ssh_disable_password_auth | bool) else 'yes' }}
KbdInteractiveAuthentication no
MaxAuthTries {{ ssh_max_auth_tries }}
X11Forwarding no
owner: root
group: root
mode: '0600'
validate: /usr/sbin/sshd -t -f %s
register: sshd_dropin
- name: Record whether sshd needs a restart
ansible.builtin.set_fact:
sshd_changed: "{{ sshd_include is changed or sshd_dropin is changed }}"
- name: Configure fail2ban sshd jail
ansible.builtin.copy:
dest: /etc/fail2ban/jail.d/sshd.local
content: |
# Managed by Ansible (playbooks/ssh_harden.yml)
[sshd]
enabled = true
backend = systemd
banaction = nftables-multiport
maxretry = 5
findtime = 10m
bantime = 1h
owner: root
group: root
mode: '0644'
register: f2b_jail
- name: Ensure fail2ban is enabled and running
ansible.builtin.systemd:
name: fail2ban
enabled: true
state: "{{ 'restarted' if f2b_jail is changed else 'started' }}"
- name: SSH Hardening - Restart SSH on Ubuntu
hosts: ubuntu:&reachable_hosts
become: true
gather_facts: false
tasks:
- name: Restart ssh service
ansible.builtin.service:
name: ssh
state: restarted
when: sshd_changed | bool
- name: SSH Hardening - Restart SSH on Rocky Linux
hosts: rocky:&reachable_hosts
become: true
gather_facts: false
tasks:
- name: Restart sshd service
ansible.builtin.service:
name: sshd
state: restarted
when: sshd_changed | bool
- name: SSH Hardening - Verify access
hosts: reachable_hosts
become: true
gather_facts: false
tasks:
- name: Drop the cached SSH connection so the next task performs a fresh login
ansible.builtin.meta: reset_connection
- name: Confirm key-based login still works
ansible.builtin.ping:
- name: Read fail2ban sshd jail status
ansible.builtin.command: fail2ban-client status sshd
register: f2b_status
changed_when: false
failed_when: false
- name: Summary
ansible.builtin.debug:
msg: |
======================================================
HOST: {{ inventory_hostname }} ({{ ansible_host }})
======================================================
PASSWORD AUTH: {{ 'disabled' if (has_keys and ssh_disable_password_auth | default(true) | bool) else 'ENABLED' }}
ROOT KEYS: {{ key_count.stdout if key_count.rc == 0 else 0 }}
SSHD: {{ 'restarted' if sshd_changed | bool else 'unchanged' }}
FAIL2BAN: {{ 'sshd jail active' if f2b_status.rc == 0 else 'WARNING: jail not reporting (' ~ (f2b_status.stderr | default('') | trim) ~ ')' }}
======================================================