Proxmox VE and Proxmox Mail Gateway are Debian, so os_detect classified them as debian and harden-firewall.sh installed the iptables backend on top of pve-firewall. The conflict is not the obvious one. pve-firewall does NOT delete third-party rules: it restores with `iptables-restore -n` (--noflush), only ever flushes chains matching its own patterns (PVEFW-*, tapNiM-*, vethNiM-*, fwbrN-*, GROUP-*), appends `-A INPUT -j PVEFW-INPUT` only when that hook is missing, and never sets a built-in chain's policy. Our rules survive it. We are the ones doing damage: - `-P INPUT DROP` is ours alone, and PVEFW-HOST-IN RETURNs on accept rather than ACCEPTing (it still has to check tap rules), so traffic Proxmox explicitly allowed falls out of its chain onto our DROP -- we silently override the platform's own accepts. - netfilter-persistent restores at boot with a full iptables-restore (no --noflush), wiping PVE's hook along with everything else until the daemon re-appends it ~10s later. - a deny-by-default chain has to enumerate the whole platform to stay usable: 8006, 5405-5412/udp corosync, 60000-60050, 5900-5999, 3128, 22, 111/udp, plus Ceph when hyperconverged -- and `-i lo`, or pveproxy loses pvedaemon on :85. - under the nftables backend (PVE 8.2+) an nft DROP beats an iptables ACCEPT, so our rules would not even be authoritative. So don't manage a firewall there at all: - oslib: is_proxmox() -- matches hosts shipping pve-firewall (VE/PMG), not PBS. - harden-firewall.sh: a third backend, "pve", that deliberately does nothing. apply explains and exits 0, allow/deny refuse loudly rather than fake success for a rule they didn't add, list shows pve-firewall status. It overrides an explicit FW_BACKEND; FW_IGNORE_PVE=1 is the one escape hatch. - harden-ssh.sh / harden-jumphost.sh: skip the firewall and install the standalone INPUT -> sshguard boot hook instead. That jump is safe alongside pve-firewall -- inserted with -I, it sits ahead of the appended PVEFW-INPUT hook and keeps first look at NEW connections. Detection only helps hosts built from here on, so `disable` cleans up one that was hardened earlier: it detects leftovers (and says which signal fired), sets INPUT ACCEPT *before* flushing so it can't drop the SSH session it runs over, deletes /etc/firewall and the engine, disables boot restore and renames the saved rulesets aside rather than persisting the open state, re-adds the sshguard jump, and restarts pve-firewall. `apply` points at it when it spots leftovers. Documented plainly that skipped is NOT protected: Proxmox's firewall is off by default (cluster-wide enable defaults to 0, and the daemon tears its chains down every ~10s while it is), so these hosts have no host firewall until someone enables it -- and the node panel's "Firewall: Yes" is ignored while the datacenter one says No. Also: svc_disable + fw_restore_services/fw_saved_files in oslib (fw_enable_restore now derives from the former), and usage() prints the whole header block instead of a hardcoded line range. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
59 lines
2.6 KiB
YAML
59 lines
2.6 KiB
YAML
#cloud-config
|
|
#
|
|
# Generic base-host bootstrap -- Alpine, Debian, or Alma Linux.
|
|
#
|
|
# On first boot this:
|
|
# 1. Installs prerequisites (bash, git, curl) for whichever distro this is.
|
|
# 2. Clones this repo to /opt/automations.
|
|
# 3. Sets the hostname per the Network Domain Name Schema and installs the
|
|
# shared MOTD banner (scripts/setup-host.sh).
|
|
# 4. Seeds root's authorized_keys from globals/ (URL-preferred).
|
|
# 5. Applies SSH hardening: key-only auth, post-quantum KEX, sshguard
|
|
# (scripts/harden-ssh.sh).
|
|
#
|
|
# Fill in REPO_URL, HOST, and the other values, then paste as instance
|
|
# user-data. For a bastion host use jumphost.yml instead.
|
|
#
|
|
# NOTE: harden-ssh.sh prints a freshly generated root private key to stdout,
|
|
# which lands in the cloud provider's console/serial log. Either capture it
|
|
# from there, or rely on the keys seeded from globals/ and ignore it.
|
|
|
|
runcmd:
|
|
- |
|
|
set -e
|
|
# ===== config =====
|
|
REPO_URL=https://git.anomalous.dev/57_Wolve/automations.git
|
|
REPO_BRANCH=main
|
|
HOST=sto-1 # <svc>-<n>; FQDN becomes HOST.BASE_DOMAIN
|
|
BASE_DOMAIN=srvno.de
|
|
DATACENTER="Globally Everywhere"
|
|
SSH_PORT=22
|
|
ALLOWED_IP= # optional: whitelist your client IP in sshguard
|
|
ENABLE_FIREWALL=1 # deny-by-default host firewall (0 to skip;
|
|
# always skipped on Proxmox -- pve-firewall owns it)
|
|
OPEN_PORTS="" # extra inbound ports, e.g. "80/tcp 443/tcp"
|
|
# ==================
|
|
|
|
# Prerequisites (OS-agnostic).
|
|
if command -v apk >/dev/null 2>&1; then apk add --no-cache bash git curl
|
|
elif command -v apt-get >/dev/null 2>&1; then apt-get update -qq && apt-get install -y -qq bash git curl
|
|
elif command -v dnf >/dev/null 2>&1; then dnf install -y -q bash git curl
|
|
fi
|
|
|
|
git clone --depth 1 --branch "$REPO_BRANCH" "$REPO_URL" /opt/automations
|
|
cd /opt/automations
|
|
|
|
# Hostname + shared MOTD.
|
|
HOST="$HOST" BASE_DOMAIN="$BASE_DOMAIN" DATACENTER="$DATACENTER" bash scripts/setup-host.sh
|
|
|
|
# Seed root's authorized_keys from globals/ (SSH_KEYS_URL or authorized_keys).
|
|
. scripts/lib.sh && load_globals \
|
|
&& install -d -m 700 /root/.ssh \
|
|
&& resolve_ssh_keys >> /root/.ssh/authorized_keys || true
|
|
sort -u /root/.ssh/authorized_keys -o /root/.ssh/authorized_keys 2>/dev/null || true
|
|
|
|
# SSH hardening (key-only, PQ KEX, sshguard) + deny-by-default host firewall.
|
|
SSH_PORT="$SSH_PORT" ALLOWED_IP="$ALLOWED_IP" \
|
|
ENABLE_FIREWALL="$ENABLE_FIREWALL" OPEN_PORTS="$OPEN_PORTS" \
|
|
FORCE=1 bash scripts/harden-ssh.sh
|