Archived
Initial infrastructure mono-repo scaffold
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
This commit is contained in:
commit
f85c65870f
125 files changed
+8186
No files matched your search
@@ -0,0 +1,243 @@
|
||||
---
|
||||
# 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
|
||||
Reference in new issue
Block a user