Files
KNELIAC/roles/security_2fa/tasks/main.yml
T
mrcharles 703053430c feat(security): add two-factor authentication role
Port Modules/Security/secharden-2fa.sh.

Configures TOTP two-factor authentication via Google Authenticator for:
- SSH (publickey + keyboard-interactive)
- Cockpit web interface (PAM with Google Authenticator)
- Webmin (if installed, TOTP provider enabled)

Includes:
- Backup of existing SSH, PAM, and Cockpit configs before changes
- PAM sshd and cockpit config deployment
- Per-user enrollment scaffolding (setup script + instructions for
  root and localuser)
- SSH service validation before restart

🤖 Generated with [Crush](https://github.com/charmassociates/crush)

Assisted-by: GLM-5 via Crush <crush@charm.land>
2026-07-30 12:16:32 -05:00

204 lines
6.0 KiB
YAML

---
# Two-factor authentication — port of Modules/Security/secharden-2fa.sh.
# Configures TOTP 2FA for SSH, Cockpit, and Webmin (if present).
# ---------------------------------------------------------------------------
# Backups (backup_configs)
# ---------------------------------------------------------------------------
- name: Ensure 2FA backup directory exists
ansible.builtin.file:
path: /root/backup/2fa
state: directory
owner: root
group: root
mode: "0700"
- name: Backup existing SSH config
ansible.builtin.copy:
src: /etc/ssh/sshd_config
dest: /root/backup/2fa/sshd_config.bak
remote_src: true
mode: "0600"
failed_when: false
- name: Backup existing PAM directory
ansible.builtin.copy:
src: /etc/pam.d
dest: /root/backup/2fa/pam.d.bak
remote_src: true
mode: "0700"
failed_when: false
- name: Backup existing Cockpit config (if present)
ansible.builtin.copy:
src: /etc/cockpit/cockpit.conf
dest: /root/backup/2fa/cockpit.conf.bak
remote_src: true
mode: "0600"
failed_when: false
# ---------------------------------------------------------------------------
# Packages (install_2fa_packages)
# ---------------------------------------------------------------------------
- name: Install 2FA packages
ansible.builtin.apt:
name:
- libpam-google-authenticator
- qrencode
state: present
update_cache: true
# ---------------------------------------------------------------------------
# SSH configuration (configure_ssh_2fa)
# ---------------------------------------------------------------------------
- name: Enable ChallengeResponseAuthentication
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^\s*ChallengeResponseAuthentication\s+'
line: 'ChallengeResponseAuthentication yes'
create: true
mode: "0600"
owner: root
group: root
notify: Validate and restart sshd (2fa)
- name: Enable KbdInteractiveAuthentication
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^\s*KbdInteractiveAuthentication\s+'
line: 'KbdInteractiveAuthentication yes'
create: true
mode: "0600"
owner: root
group: root
notify: Validate and restart sshd (2fa)
- name: Enable UsePAM
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^\s*UsePAM\s+'
line: 'UsePAM yes'
create: true
mode: "0600"
owner: root
group: root
notify: Validate and restart sshd (2fa)
- name: Require publickey + 2FA token
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^\s*AuthenticationMethods\s+'
line: 'AuthenticationMethods publickey,keyboard-interactive'
create: true
mode: "0600"
owner: root
group: root
notify: Validate and restart sshd (2fa)
# ---------------------------------------------------------------------------
# PAM for SSH (configure_pam_2fa)
# ---------------------------------------------------------------------------
- name: Deploy PAM sshd config with Google Authenticator
ansible.builtin.copy:
src: pam_sshd
dest: /etc/pam.d/sshd
owner: root
group: root
mode: "0644"
backup: true
# ---------------------------------------------------------------------------
# Cockpit 2FA (configure_cockpit_2fa)
# ---------------------------------------------------------------------------
- name: Ensure /etc/cockpit exists
ansible.builtin.file:
path: /etc/cockpit
state: directory
owner: root
group: root
mode: "0755"
- name: Deploy Cockpit config for 2FA
ansible.builtin.copy:
src: cockpit.conf
dest: /etc/cockpit/cockpit.conf
owner: root
group: root
mode: "0644"
backup: true
- name: Deploy PAM cockpit config with Google Authenticator
ansible.builtin.copy:
src: pam_cockpit
dest: /etc/pam.d/cockpit
owner: root
group: root
mode: "0644"
backup: true
# ---------------------------------------------------------------------------
# Webmin 2FA (configure_webmin_2fa) — only if webmin installed
# ---------------------------------------------------------------------------
- name: Check for Webmin installation # noqa var-naming[no-role-prefix]
ansible.builtin.stat:
path: /etc/webmin/miniserv.conf
register: _webmin_conf
- name: Configure Webmin 2FA (TOTP provider + enable)
when: _webmin_conf.stat.exists
block:
- name: Set twofactor_provider=totp
ansible.builtin.lineinfile:
path: /etc/webmin/miniserv.conf
regexp: '^twofactor_provider='
line: 'twofactor_provider=totp'
create: true
mode: "0644"
owner: root
group: root
notify: Restart webmin (2fa)
- name: Enable twofactor=1
ansible.builtin.lineinfile:
path: /etc/webmin/miniserv.conf
regexp: '^twofactor='
line: 'twofactor=1'
create: true
mode: "0644"
owner: root
group: root
notify: Restart webmin (2fa)
# ---------------------------------------------------------------------------
# Per-user enrollment scaffolding (setup_user_2fa)
# Only localuser and root are enrolled by default (matches legacy script).
# ---------------------------------------------------------------------------
- name: Users to enroll with 2FA # noqa var-naming[no-role-prefix]
ansible.builtin.set_fact:
_2fa_users: "{{ managed_users | selectattr('name', 'in', ['root', 'localuser']) | list }}"
- name: Detect existing 2FA target users
ansible.builtin.getent:
database: passwd
- name: Drop enrollment script for each 2FA user
ansible.builtin.copy:
src: setup-2fa.sh
dest: "/tmp/setup-2fa-{{ item.name }}.sh"
owner: root
group: root
mode: "0755"
loop: "{{ _2fa_users }}"
when: item.name in getent_passwd
- name: Drop instructions in each 2FA user's home
ansible.builtin.copy:
src: 2fa-setup-instructions.txt
dest: "{{ item.home }}/2fa-setup-instructions.txt"
owner: "{{ item.name }}"
group: "{{ item.name }}"
mode: "0644"
loop: "{{ _2fa_users }}"
when: item.name in getent_passwd
failed_when: false