Proxmox & Host OS Hardening — Technical Playbook - For Brandon and Z

 Proxmox & Host OS Hardening — Technical Playbook

Scope: Proxmox VE hosts (Debian-based), generic Linux servers (Debian/Ubuntu & RHEL/CentOS families) used with Proxmox (management, jump hosts, backup servers), and Windows Server (2016/2019/2022/2025+). This playbook is designed to be prescriptive for enterprise deployments: installation order, configuration, and scripts for immediate application. Always test in staging before production.

Goals:

  • Harden host OSes to a CIS-like baseline
  • Isolate and protect management plane
  • Enable logging, monitoring, and immutable audit trails
  • Ensure patching, encryption, and least-privilege access

Table of contents

  1. Assumptions & prerequisites
  2. High-level deployment order
  3. Debian/Proxmox host hardening — step-by-step + scripts
  4. Generic Linux (Debian/Ubuntu) guest/service hardening
  5. RHEL/CentOS family hardening notes & scripts
  6. Windows Server hardening — PowerShell scripts + steps
  7. Testing, verification & auditing commands
  8. Rollout guidance and automation notes
  9. Appendix: sample configs (ssh, sysctl, auditd, rsyslog, pve-firewall)

1) Assumptions & prerequisites

  • You have a staging environment identical to production (3-node cluster for Proxmox recommended).
  • All changes are applied by a CI/CD or runbook and tested on one node before cluster-wide.
  • You have vendor firmware baseline applied (UEFI/Secure Boot enabled where supported, virtualization features on, IOMMU if needed).
  • Proxmox installed from official ISO (current stable). These steps assume Debian 12/Bookworm base used by Proxmox VE (adjust package manager commands for other Debian family versions).
  • Centralized logging (SIEM) endpoint reachable and certificate pinned where possible.

2) High-level deployment order

  1. Apply vendor firmware & BIOS baseline.
  2. Install Proxmox VE on nodes (standard ISO). Verify network/corosync NIC mapping.
  3. Harden base OS (run Debian hardening script below) on each node.
  4. Configure networking (separate corosync/storage/public/mgmt networks).
  5. Install & configure Ceph or ZFS as required.
  6. Configure Proxmox-specific hardening (pve-firewall, 2FA, RBAC, API tokens policies).
  7. Configure backup server (Proxmox Backup Server) and enable encryption with centralized KMS.
  8. Configure logging/monitoring (ship logs to SIEM, install Prometheus exporters/Grafana).
  9. Run verification and compliance scans (CIS/SCAP or OpenSCAP where supported).

3) Debian/Proxmox host hardening — step-by-step + scripts

Notes: Run these scripts as root or via sudo. Test in staging. They are designed for Debian-based Proxmox nodes.

3.1 Quick checklist before running scripts

  • Take a full backup / snapshot of current node.
  • Ensure console access or IPMI/DRAC available (in case of connection lockout).
  • Run first on single nodal maintenance window.

3.2 Debian hardening script (proxmox-debian-harden.sh)

#!/usr/bin/env bash

# proxmox-debian-harden.sh

# Basic host hardening for Debian/Proxmox nodes

# Run as root. Test in staging first.

set -euo pipefail

LOG=/var/log/hardening-$(date +%F_%T).log

exec > >(tee -a "$LOG") 2>&1

 

echo "Starting Debian/Proxmox hardening: $(hostname)"

 

# 1) Update & install required packages

apt-get update && apt-get -y full-upgrade

DEBS="auditd aide fail2ban rsyslog ufw haveged conntrack ipset apt-transport-https ca-certificates gnupg"

apt-get -y install $DEBS

 

# 2) Time sync

apt-get -y install chrony

systemctl enable --now chrony

 

# 3) Configure sysctl kernel hardening

cat >/etc/sysctl.d/99-hardening.conf <<'EOF'

# Network hardening

net.ipv4.ip_forward = 0

net.ipv4.conf.all.send_redirects = 0

net.ipv4.conf.default.send_redirects = 0

net.ipv4.conf.all.accept_redirects = 0

net.ipv4.conf.default.accept_redirects = 0

net.ipv4.conf.all.accept_source_route = 0

net.ipv4.conf.default.accept_source_route = 0

net.ipv4.conf.all.rp_filter = 1

net.ipv4.conf.default.rp_filter = 1

net.ipv4.tcp_syncookies = 1

# IPv6

net.ipv6.conf.all.accept_redirects = 0

net.ipv6.conf.default.accept_redirects = 0

EOF

sysctl --system

 

# 4) SSH hardening

# Ensure sshd config backup

SSHD=/etc/ssh/sshd_config

cp $SSHD ${SSHD}.bak.$(date +%F_%T)

# Key SSH settings—adjust PermitRootLogin to prohibit password root if needed

sed -i -e "s/^#\?PermitRootLogin.*/PermitRootLogin prohibit-password/" $SSHD || echo "PermitRootLogin prohibit-password" >> $SSHD

sed -i -e "s/^#\?PasswordAuthentication.*/PasswordAuthentication no/" $SSHD || echo "PasswordAuthentication no" >> $SSHD

sed -i -e "s/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/" $SSHD || echo "PubkeyAuthentication yes" >> $SSHD

sed -i -e "s/^#\?X11Forwarding.*/X11Forwarding no/" $SSHD || echo "X11Forwarding no" >> $SSHD

# Optional: restrict allowed Ciphers/MACs/KexAlgorithms per org policy

systemctl reload sshd

 

# 5) UFW firewall default deny and allow management networks (example placeholder IPs)

ufw --force reset

ufw default deny incoming

ufw default allow outgoing

# Allow from jump host and management network

# Replace <JUMP_HOST_IP> and <MGMT_NET_CIDR> with real values

ufw allow from <JUMP_HOST_IP> to any port 22 comment 'Allow SSH from jump host'

ufw allow proto udp from <COROSYNC_NET_CIDR> to any port 5405 comment 'Allow corosync'

ufw allow from <MGMT_NET_CIDR> to any port 8006 comment 'Allow Proxmox GUI'

ufw --force enable

 

# 6) Auditd basic rules

cat >/etc/audit/rules.d/hardening.rules <<'EOF'

# Auditd rules basic

-w /etc/ssh/sshd_config -p wa -k SSH_CONF

-w /etc/passwd -p wa -k ID_CHANGES

-w /etc/group -p wa -k ID_CHANGES

-w /etc/shadow -p wa -k ID_CHANGES

-w /etc/gshadow -p wa -k ID_CHANGES

EOF

systemctl restart auditd

 

# 7) AIDE initialization (file integrity)

aideinit || true

if [ -f /var/lib/aide/aide.db.new.gz ]; then

  mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

fi

 

# 8) Fail2ban default setup

systemctl enable --now fail2ban

 

# 9) Reduce attack surface - disable unused services

# list services you want to disable in an array

SERVICES=("rpcbind" "cups" "avahi-daemon" )

for s in "${SERVICES[@]}"; do

  if systemctl list-unit-files | grep -qw "$s"; then

    systemctl disable --now $s || true

  fi

done

 

# 10) Enable AppArmor (Proxmox uses AppArmor)

systemctl enable --now apparmor || true

 

# 11) Create minimal cron for auto-updates (optional, prefer orchestration)

cat >/etc/cron.weekly/apt-security <<'EOF'

#!/bin/sh

apt-get update

DEBIAN_FRONTEND=noninteractive apt-get -y --with-new-pkgs upgrade

EOF

chmod +x /etc/cron.weekly/apt-security

 

# 12) Configure rsyslog to forward to SIEM (example)

# Replace <SIEM_HOST> with your syslog collector

cat >/etc/rsyslog.d/90-siem.conf <<'EOF'

*.* @@<SIEM_HOST>:514

EOF

systemctl restart rsyslog

 

# 13) Final: report

echo "Hardening complete on $(hostname). Log: $LOG"

How to run: upload to host, chmod +x proxmox-debian-harden.sh, edit placeholders (<JUMP_HOST_IP>, <COROSYNC_NET_CIDR>, <MGMT_NET_CIDR>, <SIEM_HOST>), test, then run.

3.3 Proxmox-specific post-hardening configuration

  • Enable Proxmox firewall on datacenter level and node level, import deny-by-default rules and allow only required management ports (8006, 22 from jump host only, 5404/5405 for corosync).
  • Configure 2FA for all interactive accounts and enable it for root-like accounts. Use TOTP or WebAuthn.
  • Integrate AD/LDAP and configure RBAC groups: create least-privilege roles for VM ops, backup ops, storage ops.
  • Configure Proxmox Backup Server: enable encryption, rotate backup keys, store off-site copies.

4) Generic Linux (Debian/Ubuntu) guest/service hardening

Use the Debian script above as the baseline. Additional guest-level items:

  • Remove unused packages: apt-get purge --auto-remove package.
  • Restrict cron and at permissions.
  • Enable and configure SELinux (on RHEL family) or AppArmor (Debian family). For Debian guests, ensure AppArmor profiles exist for critical services.
  • Configure sshd to use AllowUsers/AllowGroups limiting login.
  • Use sysctl network hardening and tune tcp_tw_reuse/timeouts per application needs.
  • If containers (LXC) are used: apply LXC-specific AppArmor profiles, limit capabilities in container configs.

Provide common configuration management (Ansible playbook recommended) — example minimal Ansible role can call the bash script and manage templates.


5) RHEL/CentOS family hardening notes & script (rhel-harden.sh)

#!/usr/bin/env bash

# rhel-harden.sh - baseline hardening for RHEL/CentOS

set -euo pipefail

 

# Update system

yum -y update

 

# Install packages

yum -y install audit rsyslog chrony aide fail2ban policycoreutils-python-utils selinux-policy-targeted

systemctl enable --now chronyd

 

# Disable unnecessary services

systemctl disable --now rpcbind avahi-daemon cups || true

 

# SSH hardening

SSHD=/etc/ssh/sshd_config

cp $SSHD ${SSHD}.bak.$(date +%F_%T)

sed -i -e "s/^#\?PermitRootLogin.*/PermitRootLogin no/" $SSHD || echo "PermitRootLogin no" >> $SSHD

sed -i -e "s/^#\?PasswordAuthentication.*/PasswordAuthentication no/" $SSHD || echo "PasswordAuthentication no" >> $SSHD

systemctl reload sshd

 

# Sysctl network hardening

cat >/etc/sysctl.d/99-hardening.conf <<'EOF'

net.ipv4.ip_forward = 0

net.ipv4.conf.all.send_redirects = 0

net.ipv4.conf.default.send_redirects = 0

net.ipv4.conf.all.accept_redirects = 0

net.ipv4.conf.default.accept_redirects = 0

net.ipv4.conf.all.accept_source_route = 0

net.ipv4.conf.default.accept_source_route = 0

net.ipv4.conf.all.rp_filter = 1

net.ipv4.conf.default.rp_filter = 1

net.ipv4.tcp_syncookies = 1

EOF

sysctl --system

 

# Configure auditd rules

cat >/etc/audit/rules.d/hardening.rules <<'EOF'

-w /etc/ssh/sshd_config -p wa -k SSH_CONF

-w /etc/passwd -p wa -k ID_CHANGES

-w /etc/group -p wa -k ID_CHANGES

EOF

systemctl restart auditd

 

# Initialize AIDE

aide --init || true

if [ -f /var/lib/aide/aide.db.new.gz ]; then

  mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

fi

 

# SELinux enforce

setenforce 1 || true

sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config

 

# Configure firewall (firewalld)

systemctl enable --now firewalld

firewall-cmd --permanent --set-default-zone=public

# Example: allow only management networks (replace placeholders)

firewall-cmd --permanent --zone=trusted --add-source=<MGMT_NET_CIDR>

firewall-cmd --permanent --zone=trusted --add-service=ssh

firewall-cmd --reload

 


6) Proxmox-specific hardening — detailed runbook (start here)

Start point requested by operator: the runbook begins at this section (6). These steps assume you have completed sections 1–5 (firmware, Proxmox install, OS hardening) and are ready to configure Proxmox-specific controls, backup, logging, monitoring, and final verification.

Goals for this section

  • Lock down Proxmox management plane and APIs
  • Enforce authentication and RBAC
  • Harden network & firewall rules for Proxmox services
  • Configure backup server with encryption & KMS
  • Integrate logging, monitoring and alerting
  • Provide explicit rollout, verification, and rollback steps for production

Terminology / variables (replace before running)

  • PVE_NODES — list of Proxmox node hostnames/IPs (e.g., pve1.example.local,pve2,...)
  • JUMP_HOST_IP — IP of bastion/jump host allowed to manage nodes
  • MGMT_NET_CIDR — management subnet (e.g., 10.0.10.0/24)
  • COROSYNC_NET_CIDR — corosync network (e.g., 10.0.11.0/24)
  • STORAGE_NET_CIDR — storage/ceph network (e.g., 10.0.12.0/24)
  • SIEM_HOST — syslog/collector host
  • PBS_HOST — Proxmox Backup Server hostname/IP
  • KMS_ENDPOINT — KMS or Vault URL for key management
  • ADMIN_GROUP_DN / LDAP_URL — LDAP/AD parameters

Replace these values in scripts and configuration files before applying.


6.1 Proxmox management plane isolation — runbook steps

  1. Confirm out-of-band access: verify IPMI/DRAC/iLO console access to each node before making firewall changes.
  2. Configure network interfaces:
    • Bind Corosync to a dedicated NIC: update /etc/hosts and /etc/pve/corosync.conf or use pvecm updatecerts as needed. Verify corosync uses the correct interface by pvecm status.
    • Ensure storage network (Ceph) uses a separate NIC with low-latency path.
  3. Configure node-level firewall defaults (pve-firewall):
    • At datacenter level, enable firewall with deny-by-default for new rules.
    • Create datacenter-wide alias objects for networks (e.g., MGMT_NET, COROSYNC, STORAGE).

Example pve-firewall JSON rule set (datacenter level)

  • Deny all inbound by default.
  • Allow TCP 8006 (PVE GUI) from MGMT_NET.
  • Allow TCP 22 from JUMP_HOST_IP.
  • Allow UDP 5404-5405 from COROSYNC_NET.
  • Allow storage ports (Ceph) between STORAGE_NET nodes.

CLI method: create aliases and firewall groups

# create aliases (example)

pve-firewall local create alias MGMT_NET 10.0.10.0/24

pve-firewall local create alias COROSYNC_NET 10.0.11.0/24

 

# create a datacenter rule to allow GUI from MGMT only

pve-firewall local add-rule --datacenter --enable --comment 'Allow GUI from mgmt' --type in --action ACCEPT --proto tcp --dport 8006 --source MGMT_NET

 

# deny all other inbound by default (set default policy)

pve-firewall set --datacenter --defaults incoming DROP outgoing ACCEPT

Note: pve-firewall commands above are representative. In many Proxmox versions, the GUI is the recommended method for editing datacenter policies to ensure consistency. If scripting, validate pve-firewall CLI availability and syntax on your Proxmox version.

  1. Harden API & WebUI access:
    • Disable password login for root via SSH (already done in OS hardening).
    • Require access through a bastion host: restrict IPs in firewall to JUMP_HOST_IP.
    • Enable TLS client certificate verification for API if feasible (mutual TLS) via reverse proxy in front of PVE if needed.
  2. Enable 2FA for Web UI (TOTP or WebAuthn):
    • Enforce 2FA for all accounts with interactive access: in Proxmox GUI, go to Datacenter -> Authentication -> Realms -> configure required 2FA settings.
    • For automation/service accounts, use API tokens with restricted scopes instead of user passwords.
  3. Disable/limit root web login:
    • Create admin users with admin role in AD/LDAP (preferred) and disable direct root SSO for daily ops.

6.2 Identity, LDAP/AD integration & RBAC

Objective: integrate with enterprise identity provider and implement least-privilege roles for operators.

  1. Configure LDAP/AD realm (example using pveum CLI)

# Add an LDAP/AD realm

pveum realm add myad --type ldap --server ldap://ad.example.local --port 389 --base-dn "DC=example,DC=local" --user-attr sAMAccountName --bind-dn 'CN=svc-pve,OU=svc,DC=example,DC=local' --bind-pwd 'REDACTED'

 

# Or for LDAPS

pveum realm add myad-ldaps --type ldap --server ldaps://ad.example.local --port 636 --base-dn 'DC=example,DC=local' --user-attr sAMAccountName --tls 1 --bind-dn 'CN=svc-pve,OU=svc,DC=example,DC=local' --bind-pwd 'REDACTED'

  1. Create RBAC groups and roles
    • Create groups in AD for pve-admins, pve-ops, pve-backup.
    • Map AD groups to Proxmox roles and datacenter/node permissions.

Example mapping (CLI)

# Create a PVE group mapped to AD group

pveum group add pve-admins@pve --comment 'Proxmox Admins'

# Assign AD group as member (if using LDAP, membership is read from AD)

# Grant role at datacenter level

pveum acl modify / -group pve-admins@pve -role Administrator

 

# Create least-privilege role for backup operators

pveum role add BackupOperator -privs "VM.Backup VM.Audit Datastore.Allocate"

pveum acl modify / -group pve-backup@pve -role BackupOperator -path /

  1. Enforce MFA for interactive users
    • Instruct users to register TOTP/WebAuthn. Pair this with conditional access on IdP if supported.
  2. Service accounts & API tokens
    • Do not store long-lived credentials on hosts; use API tokens with scoped permissions and expiry.
    • Example: create API token for backup service

pveum useradd backupsvc@pve -comment 'Backup service account'

pveum passwd backupsvc@pve --password 'REDACTED'

# create token with limited privileges

pveum token add backupsvc@pve token1 --privsep 1

# then assign role

pveum acl modify / -user backupsvc@pve -role BackupOperator

Store API tokens in Vault/KMS and rotate frequently.

6.3 Proxmox firewall — detailed safe deployment steps

Prechecks:

  • Verify pvecm status healthy and corosync on dedicated interface.
  • Confirm SSH access from JUMP_HOST_IP.

Staged rollout:

  1. Create datacenter rules first, allow management CIDR and jump host.
  2. Test login from jump host and verify GUI/API access.
  3. Apply node-level rules that mirror datacenter policy.
  4. Monitor logs (rsyslog & pve-firewall logs) for denied connections.

Example node-level rule script

#!/bin/bash

# apply-node-firewall.sh - run on each node

pve-firewall local add-rule --enable --comment 'Allow ssh from jump' --type in --action ACCEPT --proto tcp --dport 22 --source ${JUMP_HOST_IP}

pve-firewall local add-rule --enable --comment 'Allow corosync' --type in --action ACCEPT --proto udp --dport 5404-5405 --source ${COROSYNC_NET_CIDR}

pve-firewall local add-rule --enable --comment 'Allow proxmox GUI mgmt' --type in --action ACCEPT --proto tcp --dport 8006 --source ${MGMT_NET_CIDR}

# default deny for others at node level

pve-firewall set --node --defaults incoming DROP outgoing ACCEPT

Rollback: if locked out, use IPMI console to revert pve-firewall config or run pve-firewall stop from local console.

6.4 Proxmox Backup Server (PBS) with encryption & KMS — runbook

Goal: centralize backups (VMs & containers), encrypt at rest, and manage keys via a KMS (HashiCorp Vault example).

Install & basic configuration (PBS)

  1. Install Proxmox Backup Server on dedicated VM or physical host following official docs.
  2. Secure the PBS host with same Debian hardening script (adjust to PBS packages).
  3. Create a datastore for backups and enable maintenance schedules (prune/expire policy).

Enable encryption for backups

  • PBS supports encryption with keys; best practice is to keep repository keys in a KMS and not on hosts in plaintext.

Example: create repository and enable encryption

# on PBS

proxmox-backup-manager datastore create vm-backups --path /var/lib/proxmox-backup/datastore/vm-backups --content backups

# create a key and enable encryption

proxmox-backup-manager key create backup-key --type repo --scope repository

proxmox-backup-manager datastore modify vm-backups --encryption-key backup-key

Integrate Vault as KMS (high level)

  • Use Vault to store repository keys and provide rotation. PBS doesn't have native Vault integration for encryption keys, so store keys in Vault and fetch during scheduled backup jobs with strict access control.
  • Alternatively, use an HSM or cloud KMS for key storage; ensure backups are encrypted with keys not stored on Proxmox nodes.

Backup client setup (on PVE)

  • Configure backup jobs (Datacenter -> Backup) to point to PBS host and use repository credentials or key.
  • Use API tokens created for backupsvc account and store tokens in Vault.

Verification & restore tests

  • Schedule automated restores to staging environment weekly/monthly and validate boot and application correctness.
  • Maintain an inventory of backed-up VM IDs and retention periods.

6.5 Logging, Monitoring & SIEM integration — detailed runbook

Objectives:

  • Centralize logs and metrics to SIEM and metrics DB (Prometheus) for alerting.
  • Ensure immutable logs for audit and forensic readiness.

Log forwarding (rsyslog / syslog-ng / Filebeat)

  1. Configure rsyslog on Proxmox nodes to forward logs to SIEM_HOST over TLS.

Example rsyslog TLS forwarder /etc/rsyslog.d/90-siem.conf:

\$DefaultNetstreamDriver gtls

\$ActionSendStreamDriverMode 1

\$ActionSendStreamDriverAuthMode x509/name

\$ActionSendStreamDriverPermittedPeer "siem.example.local"

*.* @@(o)siem.example.local:6514

  • Ensure certificates are signed by your enterprise CA.
  1. Forward Proxmox-specific logs: /var/log/pve/, journalctl -u pveproxy, pvedaemon, pvestatd, pve-firewall logs, corosync, and Ceph logs.
  2. For Windows hosts, use WEF (Windows Event Forwarding) or a collector (NXLog/Winlogbeat) to ship to SIEM.

Metrics with Prometheus & Grafana

  1. Deploy Prometheus and Node Exporter on each Proxmox node.
    • Use pve-exporter or prometheus-pve-exporter to get Proxmox-specific metrics.
  2. Deploy Grafana dashboards for cluster health, Ceph health, storage latency, and backup job status.
  3. Configure Alertmanager to notify on:
    • Corosync loss of quorum
    • Ceph HEALTH_WARN/HEALTH_ERR
    • Backup failures or missed backup jobs
    • Disk SMART failures and high latency

SIEM & Alerting playbook

  • Create SIEM detection rules for: repeated failed logins, new API tokens created, unusual backup activity, and configuration changes to firewall or corosync.
  • On critical alerts, trigger an automated runbook (chatops) to create incident ticket and capture host diagnostic logs.

6.6 Final verification, compliance scans and evidence gathering

Automated checks (commands & scripts)

  • Cluster quorum & corosync: pvecm status (expect quorum and online nodes).
  • Firewall status: pve-firewall status and pve-firewall local list.
  • LDAP/AD connectivity: pveum user list and test login via pveproxy or GUI.
  • Backup verification: proxmox-backup-client verify for sample backups, and script to test restore.
  • Ceph health: ceph -s (expect HEALTH_OK) and ceph osd tree.
  • AIDE check: aide --check.

Compliance scans

  • Run CIS benchmark checks for Debian/Proxmox and Windows.
  • Run OpenSCAP where applicable for RHEL nodes.
  • Run vulnerability scans (Qualys/Tenable/Nessus) and remediate critical findings.

Evidence to collect for audit

  • Baseline config snapshots (BIOS config, /etc tarball, pvecm corosync.conf snapshots)
  • RBAC and AD/LDAP configuration export (pveum role list, pveum acl list)
  • Firewall rule export (pve-firewall local list --datacenter)
  • Backup job logs and successful restore reports
  • SIEM retention policy and log forwarding configuration
  • Patch and update runbook entries and test results

6.7 Production rollout plan (schedule & steps)

Pre-rollout

  • Notify stakeholders and schedule maintenance window.
  • Take node snapshots/backups and ensure out-of-band access.
  • Apply changes to 1 node in a maintenance window and run full verification list.

Rollout sequence

  1. Apply datacenter-level firewall rules and identity config.
  2. Configure one node’s local firewall and verify management access via jump host.
  3. Integrate with PBS and run initial backup test.
  4. Enable log forwarding and validate on SIEM.
  5. Run compliance scans and remediate immediate high/critical issues.
  6. Roll changes to remaining nodes one at a time.

Rollback plan

  • If lockout occurs: access via IPMI and revert firewall via pve-firewall stop or restore previous firewall config from /etc/pve/firewall/ backups.
  • If backup failures: pause automated backup jobs and restore previous datastore settings.

6.8 Playbooks & automation snippets

Ansible high-level tasks

  • pve_hardening role to apply pve-firewall templates, push rsyslog config, create API tokens, and register PBS jobs.
  • windows_hardening role to apply GPO/DSC, install Sysmon, BitLocker.

Sample Ansible task to create pve firewall rule (pseudo)

- name: Ensure management datacenter alias

  command: pve-firewall local create alias MGMT_NET {{ mgmt_cidr }}

 

- name: Add datacenter rule to allow GUI

  command: pve-firewall local add-rule --datacenter --enable --comment 'Allow GUI from mgmt' --type in --action ACCEPT --proto tcp --dport 8006 --source MGMT_NET

6.9 Runbook sign-off & documentation

  • After successful rollout and verification, update change record and obtain sign-off from network, security, and application owners.
  • Store final baselines and runbook in the configuration management database (CMDB) and archive versions for audit.

7) Windows Server hardening — PowerShell baselines, runbook & scripts

This section matches the structure and prescriptive style of your Linux / Proxmox sections. It is designed for enterprise rollout (staging → CI/CD → production). Always test in staging first and ensure you have console/host-recovery (IPMI/VM console / Hyper-V manager) access before running anything that can lock you out (BitLocker, firewall, RDP changes).


7.0 Assumptions & prerequisites

  • Target OS: Windows Server 2016 / 2019 / 2022 / 2025+ (behavior differs slightly by version). Use GPO/Intune for scale where possible.
  • Run scripts elevated (Administrator). Prefer running from a management jump host or management automation tooling (SCCM, Intune, Ansible/WinRM).
  • You have an enterprise CA for WinRM HTTPS and TLS needs, or will use a client cert/thumbprint placeholder.
  • Recovery plan in place: local admin credentials stored securely, boot/console access, AD LAPS or equivalent for local admin management.
  • Test in staging VM/host identical to production (snapshot before changes).
  • Key placeholders to replace in scripts: <MGMT_NET_CIDR>, <JUMP_HOST_IP>, <CERT_THUMBPRINT_FOR_WINRM>, <AD_DOMAIN>, <RECOVERY_KEY_LOCATION>.

7.1 High-level deployment order (recommended)

  1. Validate prechecks (console access, AD connectivity, CA availability, time sync).
  2. Apply non-disruptive config (audit policy, firewall baseline opening mgmt ports to bastion, disable SMBv1).
  3. Configure WinRM (prefer HTTPS) and GPO/Intune mapping for remote management.
  4. Enforce Windows Update policy (or configure WSUS/SCCM).
  5. Deploy and enable Defender/AV & EDR, Sysmon, Event Forwarding agent (Winlogbeat/NXLog).
  6. Enable BitLocker (store recovery keys in AD/Vault) — do not enable until recovery keys stored.
  7. Harden RDP and remove/lock local admin accounts (use LAPS).
  8. Schedule compliance verification & monitoring; run an initial baseline scan.
  9. Rollout gradually via GPO/Intune/SCCM or automation tools.

7.2 PowerShell baseline script — windows-harden.ps1

  • Purpose: idempotent baseline hardening for single-server staging runs.
  • Caveat: It performs configuration changes. Review and adjust placeholders before running.
  • Run elevated. Test first on a single staging host.

Save as windows-harden.ps1 and run in an elevated PowerShell session.

<#

windows-harden.ps1

Baseline Windows Server hardening script (idempotent where possible).

Run as Administrator. TEST IN STAGING.

Replace placeholders: <MGMT_NET_CIDR>, <JUMP_HOST_IP>, <CERT_THUMBPRINT_FOR_WINRM>, <AD_DOMAIN>, <RECOVERY_KEY_LOCATION>

#>

 

# Safety: ensure running as admin

if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {

    Write-Error "Script must be run as Administrator."

    exit 1

}

 

$timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss")

$reportDir = "C:\Logs"

if (-not (Test-Path $reportDir)) { New-Item -Path $reportDir -ItemType Directory | Out-Null }

$reportPath = Join-Path $reportDir "windows-hardening-report-$timestamp.json"

$results = @()

 

function Add-Result {

    param($Check,$Status,$Message,$Extra)

    $results += [PSCustomObject]@{

        check = $Check

        status = $Status

        message = $Message

        extra = $Extra

    }

}

 

Write-Host "Starting Windows baseline hardening..."

 

### 1) Update Execution Policy (non-invasive)

try {

    Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force

    Add-Result -Check "execution_policy" -Status "passed" -Message "ExecutionPolicy set to RemoteSigned" -Extra @{}

} catch {

    Add-Result -Check "execution_policy" -Status "failed" -Message $_.Exception.Message -Extra @{}

}

 

### 2) Disable SMBv1 (non-disruptive)

try {

    # Disable SMB1 feature (server & client)

    Disable-WindowsOptionalFeature -Online -FeatureName SMB1Protocol -NoRestart -ErrorAction SilentlyContinue

    # Also turn off the server service if present

    if (Get-Service -Name "LanmanServer" -ErrorAction SilentlyContinue) {

        # do not stop LanmanServer (required), just ensure SMB1 is disabled feature-wise

    }

    Add-Result -Check "smb1" -Status "passed" -Message "SMBv1 optional feature disabled (if present)" -Extra @{}

} catch {

    Add-Result -Check "smb1" -Status "failed" -Message $_.Exception.Message -Extra @{}

}

 

### 3) WinRM setup — enable and prefer HTTPS listener

try {

    # Basic enable

    winrm quickconfig -q

 

    # Create HTTPS listener if certificate thumbprint provided (replace placeholder)

    $certThumb = "<CERT_THUMBPRINT_FOR_WINRM>"

    if ($certThumb -and $certThumb -ne "<CERT_THUMBPRINT_FOR_WINRM>") {

        # remove existing HTTPS listeners then add

        winrm enumerate winrm/config/Listener | Out-Null

        # create using wsman

        $uri = "https://+:5986/wsman"

        $cmd = "winrm create winrm/config/Listener?Address=*+Transport=HTTPS @{Hostname=`\"$env:COMPUTERNAME`\"; CertificateThumbprint=`"$certThumb`"}"

        & cmd /c $cmd | Out-Null

        Add-Result -Check "winrm_https" -Status "passed" -Message "WinRM HTTPS listener created with cert thumbprint" -Extra @{thumbprint=$certThumb}

    } else {

        Add-Result -Check "winrm_https" -Status "skipped" -Message "CERT_THUMBPRINT_FOR_WINRM not supplied; WinRM HTTP remains enabled (use HTTPS in prod)" -Extra @{}

    }

 

    # Configure WinRM service startup

    Set-Service -Name WinRM -StartupType Automatic

} catch {

    Add-Result -Check "winrm" -Status "failed" -Message $_.Exception.Message -Extra @{}

}

 

### 4) Configure Windows Firewall baseline (open mgmt ports only to bastion)

try {

    # Ensure firewall enabled for all profiles

    Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled True

 

    # Allow WinRM HTTPS (5986) from management network (placeholder)

    $mgmtNet = "<MGMT_NET_CIDR>"

    $jump = "<JUMP_HOST_IP>"

 

    if ($jump -and $jump -ne "<JUMP_HOST_IP>") {

        # allow SSH/RDP from jump only (RDP example)

        New-NetFirewallRule -DisplayName "Allow RDP from JumpHost" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 3389 -RemoteAddress $jump -Profile Any -ErrorAction SilentlyContinue

        Add-Result -Check "firewall_jump" -Status "passed" -Message "RDP allowed only from Jump Host" -Extra @{jump=$jump}

    } else {

        Add-Result -Check "firewall_jump" -Status "skipped" -Message "JUMP_HOST_IP placeholder not set; manual firewall tuning required" -Extra @{}

    }

 

    if ($mgmtNet -and $mgmtNet -ne "<MGMT_NET_CIDR>") {

        New-NetFirewallRule -DisplayName "Allow WinRM HTTPS from MGMT" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 5986 -RemoteAddress $mgmtNet -Profile Any -ErrorAction SilentlyContinue

        Add-Result -Check "firewall_mgmt" -Status "passed" -Message "WinRM HTTPS allowed from management network" -Extra @{mgmt=$mgmtNet}

    } else {

        Add-Result -Check "firewall_mgmt" -Status "skipped" -Message "MGMT_NET_CIDR placeholder not set; consider restricting 5986 to mgmt" -Extra @{}

    }

} catch {

    Add-Result -Check "firewall" -Status "failed" -Message $_.Exception.Message -Extra @{}

}

 

### 5) Windows Update (set to auto-download and scheduled install) — prefer WSUS/GPO for enterprise

try {

    # Create policy keys for automatic updates (AUOptions=4 => Auto download and schedule the install)

    $auPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU"

    if (-not (Test-Path $auPath)) { New-Item -Path $auPath -Force | Out-Null }

    Set-ItemProperty -Path $auPath -Name "AUOptions" -Value 4 -Type DWord -Force

    Set-ItemProperty -Path $auPath -Name "NoAutoRebootWithLoggedOnUsers" -Value 1 -Type DWord -Force

    Add-Result -Check "windows_update_policy" -Status "passed" -Message "Windows Update policy configured (local registry). For scale use WSUS/GPO." -Extra @{}

} catch {

    Add-Result -Check "windows_update_policy" -Status "failed" -Message $_.Exception.Message -Extra @{}

}

 

### 6) Audit policy — enable success & failure for high-value auditable events

try {

    # Example subcategories to enable (tune to your baseline)

    $audits = @{

        "Logon/Logoff" = "Success,Failure"

        "Account Logon" = "Success,Failure"

        "Account Management" = "Success,Failure"

        "Object Access" = "Success,Failure"

        "Policy Change" = "Success,Failure"

        "Privilege Use" = "Failure"

        "Process Creation" = "Success"

    }

 

    foreach ($k in $audits.Keys) {

        # auditpol requires subcategory names; mapping to category is approximate — validate names on your server

        auditpol /set /subcategory:"$k" /success:enable /failure:enable | Out-Null

    }

    Add-Result -Check "audit_policy" -Status "passed" -Message "Audit policy applied (verify subcategory names on server)" -Extra @{audits=$audits}

} catch {

    Add-Result -Check "audit_policy" -Status "failed" -Message $_.Exception.Message -Extra @{}

}

 

### 7) Windows Defender / AV — ensure service present & real-time enabled

try {

    $mp = Get-Service -Name WinDefend -ErrorAction SilentlyContinue

    if ($mp -and $mp.Status -ne "Running") { Start-Service -Name WinDefend }

    $defStatus = Get-MpComputerStatus -ErrorAction SilentlyContinue

    if ($defStatus) {

        Add-Result -Check "defender" -Status "passed" -Message "Windows Defender service present" -Extra @{AMService=$defStatus.AntivirusEnabled}

    } else {

        Add-Result -Check "defender" -Status "skipped" -Message "Get-MpComputerStatus not available; ensure AV/EDR present" -Extra @{}

    }

} catch {

    Add-Result -Check "defender" -Status "failed" -Message $_.Exception.Message -Extra @{}

}

 

### 8) BitLocker (prepare + enable recommendation) — **DO NOT enable until recovery key safe**

try {

    $bk = Get-WmiObject -Namespace root\cimv2\Security\MicrosoftVolumeEncryption -Class Win32_EncryptableVolume -ErrorAction SilentlyContinue

    if ($bk) {

        # check TPM presence

        $tpm = Get-WmiObject -Namespace "Root\CIMv2\Security\MicrosoftTpm" -Class Win32_Tpm -ErrorAction SilentlyContinue

        if ($tpm -and $tpm.IsActivated_InitialValue) {

            Add-Result -Check "tpm" -Status "passed" -Message "TPM present and activated" -Extra @{}

        } else {

            Add-Result -Check "tpm" -Status "skipped" -Message "TPM not present or not activated (BitLocker with TPM recommended)" -Extra @{}

        }

        # Guidance: create recovery protector and backup to AD/Vault

        Add-Result -Check "bitlocker_recommend" -Status "info" -Message "BitLocker ready. Do NOT enable until recovery keys will be backed up to AD/Vault." -Extra @{}

    } else {

        Add-Result -Check "bitlocker" -Status "skipped" -Message "BitLocker cmdlets not present or not supported" -Extra @{}

    }

} catch {

    Add-Result -Check "bitlocker" -Status "failed" -Message $_.Exception.Message -Extra @{}

}

 

### 9) Local Administrators & LAPS recommendation

try {

    $admins = Get-LocalGroupMember -Group Administrators -ErrorAction SilentlyContinue

    Add-Result -Check "local_admins" -Status "info" -Message "Enumerated local administrators" -Extra @{count = ($admins | Measure-Object).Count; members = ($admins | Select-Object -ExpandProperty Name -ErrorAction SilentlyContinue)}

    Add-Result -Check "laps_recommend" -Status "info" -Message "Recommend deploying LAPS to manage local admin passwords" -Extra @{}

} catch {

    Add-Result -Check "local_admins" -Status "skipped" -Message "Could not enumerate local admins" -Extra @{}

}

 

### 10) Sysmon installation (optional) — requires sysmon.exe and config xml

try {

    $sysmonExe = "C:\Tools\Sysmon.exe"   # place sysmon.exe here

    $sysmonConf = "C:\Tools\sysmon-config.xml" # place a config file here

    if ((Test-Path $sysmonExe) -and (Test-Path $sysmonConf)) {

        & $sysmonExe -accepteula -i $sysmonConf

        Add-Result -Check "sysmon" -Status "passed" -Message "Sysmon installed with provided config" -Extra @{}

    } else {

        Add-Result -Check "sysmon" -Status "skipped" -Message "Sysmon binary or config not found at C:\Tools" -Extra @{}

    }

} catch {

    Add-Result -Check "sysmon" -Status "failed" -Message $_.Exception.Message -Extra @{}

}

 

### 11) RDP hardening — enable NLA, restrict to mgmt/jump

try {

    # Ensure NLA

    Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" -Name "UserAuthentication" -Value 1 -Force

    # Ensure remote desktop allowed only if you need it — do not enable unless required

    Add-Result -Check "rdp_nla" -Status "passed" -Message "RDP configured to require NLA. Restrict RDP to management IPs via firewall." -Extra @{}

} catch {

    Add-Result -Check "rdp" -Status "failed" -Message $_.Exception.Message -Extra @{}

}

 

### 12) Time sync — prefer domain controllers or NTP pool

try {

    w32tm /query /status | Out-String | Out-Null

    Add-Result -Check "time_sync" -Status "passed" -Message "w32time present; ensure domain NTP or NTP servers configured" -Extra @{}

} catch {

    Add-Result -Check "time_sync" -Status "skipped" -Message "Time sync check failed" -Extra @{}

}

 

### 13) Logging & Event Forwarding / Shipping

Add-Result -Check "event_forwarding" -Status "info" -Message "Deploy Winlogbeat/NXLog/WEF depending on SIEM. Use TLS & cert pinning." -Extra @{}

 

### 14) Final report save

$results | ConvertTo-Json -Depth 6 | Out-File -FilePath $reportPath -Encoding UTF8

Write-Host "`nDone. Hardening report saved to $reportPath"

$results | Format-Table -AutoSize

 

# End script

Important notes about the script

  • Many enterprise controls are best applied via GPO / Intune / SCCM (e.g., Audit Policy, Windows Update, BitLocker key backup, LAPS). Use this script for initial staging and verification.
  • Do not enable BitLocker until recovery keys are backed up to AD or a secrets manager — enabling BitLocker without proper recovery key storage can result in data loss.
  • Replace placeholders (<CERT_THUMBPRINT_FOR_WINRM>, <MGMT_NET_CIDR>, <JUMP_HOST_IP>) before running.

7.3 GPO / Intune / SCCM (scale) guidance

  • Audit & Logging: Deploy Advanced Audit Policy settings using a Group Policy Preference (at least the categories you enabled in the script). Configure Event Log sizes & retention (Security, System, Application).
  • Windows Update: Use WSUS/SCCM or Intune Update Rings rather than local registry changes for predictable patch management.
  • BitLocker keys: Use Group Policy to “Store BitLocker recovery information in Active Directory Domain Services (Windows Server)” or configure BitLocker key escrow to Vault/HSM for non-AD environments.
  • LAPS: Deploy Microsoft LAPS (or third-party) to remove persistent local admin credentials.
  • WinRM: Prefer to configure via GPO (Allow remote server management through WinRM) and deploy certificates for HTTPS listeners via AD Certificate Services auto-enrollment or a device configuration profile in Intune.
  • Sysmon & EDR: Deploy Sysmon via software deployment tools with a standard sysmon config (e.g., SwiftOnSecurity baseline) and ship logs to SIEM via Winlogbeat/NXLog.

7.4 Verification commands & checks

Run these after changes to verify:

  • System info & updates:
    Get-ComputerInfo
    Get-WindowsUpdateLog / (use PSWindowsUpdate module)
  • SMBv1 disabled:
    Get-WindowsOptionalFeature -Online -FeatureName SMB1Protocol (should be Disabled)
  • WinRM listeners:
    winrm enumerate winrm/config/Listener
    Test-WSMan -ComputerName <hostname> -UseSSL (if HTTPS)
  • Firewall rules:
    Get-NetFirewallRule -PolicyStore ActiveStore | Where-Object { $_.DisplayName -like "*WinRM*" }
    Get-NetFirewallProfile
  • Audit policy:
    auditpol /get /category:*
    wevtutil sl Security /ms:31457280 (check log size)
  • BitLocker status:
    Get-BitLockerVolume | Format-List
  • Local admins:
    Get-LocalGroupMember -Group "Administrators"
  • Sysmon status:
    Get-Service -Name "Sysmon64" and look for Event ID 1/3/10 usage in Sysmon channel.
  • Event forwarding:
    wecutil gr (list subscriptions) and Get-WinEvent -LogName "ForwardedEvents" -MaxEvents 10
  • Defender status:
    Get-MpComputerStatus | Format-List AMService,AMProductVersion,AntiSpywareEnabled

7.5 Rollback & recovery

  • Firewall / WinRM lockout: Use console access (VM host console / IPMI) to access the server and run netsh advfirewall reset or remove restrictive firewall rules.
  • BitLocker: If BitLocker enabled and you are locked out, use the recovery key stored in AD/Vault. If not stored, recovery may be impossible — ensure recovery key storage before enabling.
  • Group Policy changes: Remove or revert GPO (use gpupdate /force afterwards). For Intune, revert device configuration profiles.
  • Sysmon: sysmon -u to uninstall (if needed).
  • Manual Restoration: Keep a pre-patch snapshot or machine image to recover from misconfiguration quickly.

7.6 Testing, verification & auditing commands (automated)

  • Incorporate the validate-windows-compliance.ps1 script (you already had earlier). Add checks for:
    • AD-backed BitLocker key presence: Get-ADObject -Filter ... (requires RSAT/AD module)
    • GPO application: gpresult /h gp.html
    • Patch compliance: SCCM/WSUS/API queries or Get-WindowsUpdateLog / PSWindowsUpdate.
  • Schedule scans weekly and forward JSON reports to SIEM for drift detection.

7.7 Rollout guidance & automation notes

  • Start with a pilot OU in AD or a pilot group in Intune to validate behavior and rollback processes.
  • Gate changes in CI/CD: require successful validate-windows-compliance.ps1 results before marking a node as hardened.
  • Use Desired State Configuration (DSC) or Ansible/WinRM tasks for idempotent enforcement.
  • Document all secrets and keys (cert thumbprints, BitLocker recovery key locations, API tokens) in your secrets manager and rotate per policy.

7.8 Appendix: sample PowerShell snippets (quick references)

  • Disable SMBv1 (one-liner):

Disable-WindowsOptionalFeature -Online -FeatureName SMB1Protocol -NoRestart

  • Create WinRM HTTPS listener (example requires cert thumbprint):

$thumb = "<CERT_THUMBPRINT_FOR_WINRM>"

if ($thumb -ne "<CERT_THUMBPRINT_FOR_WINRM>") {

  # Remove existing HTTPS listeners if needed and add

  winrm delete winrm/config/Listener?Address=*+Transport=HTTPS 2>$null

  winrm create winrm/config/Listener?Address=*+Transport=HTTPS "@{Hostname=`"$env:COMPUTERNAME`"; CertificateThumbprint=`"$thumb`"}"

}

  • Enable NLA for RDP:

Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" -Name "UserAuthentication" -Value 1 -Force

  • Backup BitLocker key to AD (example; requires AD module & rights):

# Ensure AD RSAT is installed and you're domain-admin or have proper rights

$vol = Get-BitLockerVolume -MountPoint 'C:'

$kp = Add-BitLockerKeyProtector -MountPoint 'C:' -RecoveryPasswordProtector

# Backup to AD:

Backup-BitLockerKeyProtector -MountPoint 'C:' -KeyProtectorId $kp.KeyProtectorId


7.9 Sign-off & evidence to collect

  • GPO/Intune configuration exports (JSON/XML), policy versions used.
  • BitLocker key escrow evidence (AD objects or KMIP/Vault entries).
  • Firewall rule exports (Get-NetFirewallRule | Export-Clixml ...).
  • Audit policy output (auditpol /get /category:*).
  • Sysmon config used and Event ID sample captures.
  • Patch compliance snapshot (WSUS/SCCM / Get-HotFix).
  • Report from validate-windows-compliance.ps1 (JSON).

 


8) Testing, verification & auditing commands

(Section retained with expanded checks — see section 6.6 above for Proxmox-specific checks; Windows checks unchanged.)


9) Rollout guidance and automation

(Section retained and expanded above with Ansible/DSC recommendations, testing pipeline, and rollback guidance.)


 

References & Supporting Standards

Security Frameworks & Benchmarks


Proxmox & Related Documentation


Operating System Hardening Guides


Logging, Monitoring, and SIEM

  • rsyslog TLS Configuration Guide
    🔗 https://www.rsyslog.com/doc/master/tutorials/tls_cert_summary.html
  • syslog-ng Hardened Logging Setup
    🔗 https://www.syslog-ng.com/technical-documents
  • Prometheus Documentation
    🔗 https://prometheus.io/docs/
  • Grafana Documentation
    🔗 https://grafana.com/docs/
  • Elastic Stack (ELK) Logging
    🔗 https://www.elastic.co/guide/

Key Management, Secrets & Automation


Recommended Reading & Tools

 

Comments

Popular posts from this blog

Proxmox VE + full Kubernetes (kubeadm) step-by-step

Monitoring Virtualized Environments with Graylog: A Complete Guide

Building a Secure Virtual OPNsense 26.1 Firewall with VLANs, DMZ, and CARP High Availability