Archived
Consolidates nixos, docker, raspi, and debian-configuration into a single infrastructure-as-code repo. Includes: - ansible/: full inventory + proxmox-hardening, freeipa, and raspberrypi roles (converted from debian-configuration bash scripts) - terraform/: Proxmox VMs, Dynu DNS, Pi-hole (decommissioned stub), Docker container catalog — migrated from docker/infrastructure/terraform/ - stacks/docker/, stacks/raspi/, nixos/: placeholder READMEs pending git subtree population (see implementation plan) - docs/: internal MkDocs site with architecture, network topology, runbooks, and drift-detection guide; external sanitized site - scripts/: drift-detect.sh, docs-build.sh, install-hooks.sh, check-secrets.sh - CI: secret-scan (push/PR), drift-detect (daily), docs-build (on change) - Pi-hole removed throughout — DNS is FreeIPA, DHCP is router See docs/internal/implementation-plan.md for the phased rollout after pushing to Gitea. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UvNjoxTWEDkhXsd1Dq2ETP
244 lines
7.9 KiB
YAML
244 lines
7.9 KiB
YAML
---
|
|
# proxmox-hardening/tasks/main.yml
|
|
# Mirrors the bash scripts in debian-configuration/proxmox/scripts/.
|
|
# All tasks are idempotent — safe to re-run.
|
|
|
|
# ── APT repos ─────────────────────────────────────────────────────────────────
|
|
|
|
- name: Disable enterprise apt source (rename to .disabled)
|
|
ansible.builtin.find:
|
|
paths: /etc/apt/sources.list.d
|
|
patterns: "*.sources,*.list"
|
|
register: apt_sources
|
|
when: proxmox_switch_to_nosub_repo
|
|
tags: repos
|
|
|
|
- name: Disable enterprise apt sources
|
|
ansible.builtin.command:
|
|
cmd: "mv {{ item.path }} {{ item.path }}.disabled"
|
|
loop: "{{ apt_sources.files | default([]) }}"
|
|
when:
|
|
- proxmox_switch_to_nosub_repo
|
|
- item.path is not search('.disabled')
|
|
register: mv_result
|
|
changed_when: mv_result.rc == 0
|
|
failed_when: mv_result.rc not in [0, 1]
|
|
# Only disable files that actually reference enterprise.proxmox.com
|
|
# (checked by grep in the shell — using shell here for grep pipe)
|
|
tags: repos
|
|
|
|
- name: Write no-subscription apt source
|
|
ansible.builtin.template:
|
|
src: pve-no-subscription.sources.j2
|
|
dest: /etc/apt/sources.list.d/pve-no-subscription.sources
|
|
mode: "0644"
|
|
notify: apt update
|
|
when: proxmox_switch_to_nosub_repo
|
|
tags: repos
|
|
|
|
# ── SSH hardening ─────────────────────────────────────────────────────────────
|
|
|
|
- name: Ensure sshd_config.d directory exists
|
|
ansible.builtin.file:
|
|
path: /etc/ssh/sshd_config.d
|
|
state: directory
|
|
mode: "0755"
|
|
when: proxmox_harden_ssh
|
|
tags: ssh
|
|
|
|
- name: Apply SSH hardening drop-in
|
|
ansible.builtin.template:
|
|
src: sshd-hardening.conf.j2
|
|
dest: /etc/ssh/sshd_config.d/99-hardening.conf
|
|
mode: "0644"
|
|
validate: "sshd -t -f %s"
|
|
notify: reload sshd
|
|
when: proxmox_harden_ssh
|
|
tags: ssh
|
|
|
|
- name: Install fail2ban
|
|
ansible.builtin.apt:
|
|
name: fail2ban
|
|
state: present
|
|
update_cache: false
|
|
when: proxmox_harden_ssh
|
|
tags: ssh
|
|
|
|
- name: Write fail2ban sshd jail
|
|
ansible.builtin.template:
|
|
src: fail2ban-sshd.local.j2
|
|
dest: /etc/fail2ban/jail.d/sshd.local
|
|
mode: "0644"
|
|
notify: restart fail2ban
|
|
when: proxmox_harden_ssh
|
|
tags: ssh
|
|
|
|
- name: Enable and start fail2ban
|
|
ansible.builtin.systemd:
|
|
name: fail2ban
|
|
enabled: true
|
|
state: started
|
|
when: proxmox_harden_ssh
|
|
tags: ssh
|
|
|
|
# ── PVE datacenter firewall ───────────────────────────────────────────────────
|
|
|
|
- name: Ensure /etc/pve/firewall exists
|
|
ansible.builtin.file:
|
|
path: /etc/pve/firewall
|
|
state: directory
|
|
mode: "0750"
|
|
when: proxmox_configure_firewall and proxmox_mgmt_cidr is defined
|
|
tags: firewall
|
|
|
|
- name: Deploy datacenter firewall config
|
|
ansible.builtin.template:
|
|
src: cluster-fw.j2
|
|
dest: /etc/pve/firewall/cluster.fw
|
|
mode: "0640"
|
|
notify: restart pve-firewall
|
|
when: proxmox_configure_firewall and proxmox_mgmt_cidr is defined
|
|
tags: firewall
|
|
|
|
# ── Unattended upgrades ───────────────────────────────────────────────────────
|
|
|
|
- name: Install unattended-upgrades
|
|
ansible.builtin.apt:
|
|
name: unattended-upgrades
|
|
state: present
|
|
when: proxmox_configure_unattended_upgrades
|
|
tags: upgrades
|
|
|
|
- name: Write unattended-upgrades origins config
|
|
ansible.builtin.template:
|
|
src: unattended-upgrades.conf.j2
|
|
dest: /etc/apt/apt.conf.d/51pve-unattended-upgrades.conf
|
|
mode: "0644"
|
|
when: proxmox_configure_unattended_upgrades
|
|
tags: upgrades
|
|
|
|
- name: Write auto-upgrades config
|
|
ansible.builtin.copy:
|
|
content: |
|
|
// Managed by ansible proxmox-hardening role
|
|
APT::Periodic::Update-Package-Lists "1";
|
|
APT::Periodic::Unattended-Upgrade "1";
|
|
APT::Periodic::Download-Upgradeable-Packages "1";
|
|
APT::Periodic::AutocleanInterval "7";
|
|
dest: /etc/apt/apt.conf.d/20auto-upgrades
|
|
mode: "0644"
|
|
when: proxmox_configure_unattended_upgrades
|
|
tags: upgrades
|
|
|
|
- name: Enable unattended-upgrades service
|
|
ansible.builtin.systemd:
|
|
name: unattended-upgrades
|
|
enabled: true
|
|
state: started
|
|
when: proxmox_configure_unattended_upgrades
|
|
tags: upgrades
|
|
|
|
# ── PVE admin user ────────────────────────────────────────────────────────────
|
|
|
|
- name: Check if named admin user already exists
|
|
ansible.builtin.command:
|
|
cmd: "pveum user list --output-format json"
|
|
register: pve_users
|
|
changed_when: false
|
|
when: proxmox_admin_username is defined and proxmox_admin_username != ""
|
|
tags: users
|
|
|
|
- name: Create named PVE admin user (one-time — password printed, must be changed on first login)
|
|
ansible.builtin.command:
|
|
cmd: >
|
|
pveum user add {{ proxmox_admin_username }}@pve
|
|
--password {{ lookup('password', '/dev/null length=24 chars=ascii_letters,digits') }}
|
|
--comment "Named admin account, managed by ansible proxmox-hardening role"
|
|
when:
|
|
- proxmox_admin_username is defined
|
|
- proxmox_admin_username != ""
|
|
- pve_users.stdout is defined
|
|
- proxmox_admin_username + "@pve" not in pve_users.stdout
|
|
no_log: true
|
|
tags: users
|
|
|
|
- name: Grant Administrator role to PVE admin user
|
|
ansible.builtin.command:
|
|
cmd: "pveum acl modify / --users {{ proxmox_admin_username }}@pve --roles Administrator"
|
|
when:
|
|
- proxmox_admin_username is defined
|
|
- proxmox_admin_username != ""
|
|
- pve_users.stdout is defined
|
|
- proxmox_admin_username + "@pve" not in pve_users.stdout
|
|
tags: users
|
|
|
|
# ── IPA sudo ──────────────────────────────────────────────────────────────────
|
|
|
|
- name: Check IPA enrollment
|
|
ansible.builtin.stat:
|
|
path: /etc/ipa/default.conf
|
|
register: ipa_conf
|
|
when: proxmox_setup_ipa_sudo
|
|
tags: ipa
|
|
|
|
- name: Write admins NOPASSWD sudoers file
|
|
ansible.builtin.copy:
|
|
content: "%admins ALL=(root) NOPASSWD: ALL\n"
|
|
dest: /etc/sudoers.d/admins-nopasswd
|
|
mode: "0440"
|
|
validate: "visudo -cf %s"
|
|
when:
|
|
- proxmox_setup_ipa_sudo
|
|
- ipa_conf.stat.exists
|
|
tags: ipa
|
|
|
|
- name: Find pvesh/qm/pct paths for proxmox sudoers
|
|
ansible.builtin.command:
|
|
cmd: "which {{ item }}"
|
|
register: pve_tools
|
|
loop: [pvesh, qm, pct]
|
|
changed_when: false
|
|
failed_when: false
|
|
when:
|
|
- proxmox_setup_ipa_sudo
|
|
- ipa_conf.stat.exists
|
|
tags: ipa
|
|
|
|
- name: Write admins proxmox tools sudoers file
|
|
ansible.builtin.copy:
|
|
content: >
|
|
%admins ALL=(root) NOPASSWD:
|
|
{{ pve_tools.results | map(attribute='stdout') | select | join(', ') }}
|
|
dest: /etc/sudoers.d/admins-proxmox
|
|
mode: "0440"
|
|
validate: "visudo -cf %s"
|
|
when:
|
|
- proxmox_setup_ipa_sudo
|
|
- ipa_conf.stat.exists
|
|
- pve_tools.results | map(attribute='rc') | select('eq', 0) | list | length == 3
|
|
tags: ipa
|
|
|
|
# ── Subscription nag ──────────────────────────────────────────────────────────
|
|
|
|
- name: Check if nag patch is needed
|
|
ansible.builtin.shell:
|
|
cmd: >
|
|
grep -qF "data.status.toLowerCase() !== 'active'"
|
|
/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
|
|
register: nag_check
|
|
changed_when: false
|
|
failed_when: false
|
|
when: proxmox_disable_nag
|
|
tags: nag
|
|
|
|
- name: Patch subscription nag
|
|
ansible.builtin.replace:
|
|
path: /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
|
|
regexp: "data\\.status\\.toLowerCase\\(\\) !== 'active'"
|
|
replace: "false"
|
|
backup: true
|
|
when:
|
|
- proxmox_disable_nag
|
|
- nag_check.rc == 0
|
|
tags: nag
|