Lewati ke konten
Client Panel

Proxmox VE Management Access Hardening

Konten ini belum tersedia dalam bahasa Anda.

Artikel ini belum diterjemahkan. Isi berikut berbahasa Inggris. Buka versi asli bahasa Inggris.

Cakupan: Proxmox VE standalone node dengan management interface langsung menghadap jaringan publik. Tujuan: membatasi akses management Proxmox tanpa mengubah atau mengganggu firewall milik VM/tenant.


Hardening ini ditujukan untuk kondisi ketika interface management Proxmox memiliki public IP dan tidak memungkinkan dipindahkan ke management LAN/private network.

Target akhirnya:

  • SSH TCP/22 hanya dapat diakses dari source administrator yang diizinkan.
  • Proxmox Web UI/API TCP/8006 hanya dapat diakses dari source administrator yang diizinkan.
  • Port management yang tidak digunakan tidak dibuka ke Internet.
  • Firewall VM/tenant tetap bekerja seperti sebelumnya.
  • Perubahan dapat divalidasi sebelum diterapkan.
  • Tersedia automatic rollback apabila akses management terputus.

Gunakan variabel agar dokumentasi tidak berisi IP produksi.

<PVE_HOSTNAME> = hostname node Proxmox
<PVE_PUBLIC_IP> = public IP management Proxmox
<ADMIN_NET_1> = subnet admin utama
<ADMIN_NET_2> = subnet admin backup
<ADMIN_NET_3> = subnet admin backup lainnya
<ADMIN_SINGLE_IP> = single trusted admin IP /32
<NON_ADMIN_TEST_IP> = IP yang dipakai untuk simulasi source non-admin

Contoh menggunakan documentation-only IP:

<PVE_HOSTNAME> = pve01
<PVE_PUBLIC_IP> = 198.51.100.10
<ADMIN_NET_1> = 203.0.113.16/28
<ADMIN_NET_2> = 198.51.100.64/29
<ADMIN_NET_3> = 192.0.2.128/29
<ADMIN_SINGLE_IP> = 203.0.113.250/32
<NON_ADMIN_TEST_IP> = 192.0.2.10

Untuk subnet, sebaiknya gunakan network address, bukan salah satu host address.

Contoh:

203.0.113.17/28

dinormalisasi menjadi:

203.0.113.16/28

3. Prinsip Penting: Host Firewall dan VM Firewall Terpisah

Section titled “3. Prinsip Penting: Host Firewall dan VM Firewall Terpisah”

Traffic yang menuju host Proxmox melewati jalur:

Internet
|
v
INPUT
|
v
PVEFW-INPUT
|
v
PVEFW-HOST-IN

Sedangkan traffic VM melewati:

Internet / Network
|
v
FORWARD
|
v
PVEFW-FWBR-IN / OUT
|
v
tap<VMID>i0-IN / OUT
|
v
VM

Karena itu hardening management host harus dibatasi pada host-side firewall.

Jangan melakukan tindakan berikut apabila firewall VM dikelola tenant/user:

iptables -F
iptables-legacy -F
pve-firewall stop
Datacenter Firewall enable: 0
mengubah firewall=1 pada NIC VM
mengubah /etc/pve/firewall/<VMID>.fw

Periksa status firewall:

Terminal window
pve-firewall status

Contoh output:

Status: enabled/running

Jika output seperti:

Status: disabled/stopped

jangan lanjut menggunakan prosedur yang mengasumsikan firewall guest sedang aktif.


5. Periksa Konfigurasi Datacenter Firewall

Section titled “5. Periksa Konfigurasi Datacenter Firewall”
Terminal window
cat /etc/pve/firewall/cluster.fw

Contoh kondisi yang terlalu terbuka:

[OPTIONS]
enable: 1
[RULES]
IN Web(ACCEPT) -log nolog
IN SSH(ACCEPT) -log nolog
IN ACCEPT -p udp -dport 8006 -log nolog
IN ACCEPT -p tcp -dport 8006 -log nolog

Konfigurasi seperti di atas berarti SSH dan 8006/tcp dapat diakses tanpa pembatasan source.


Terminal window
cat /etc/pve/nodes/$(hostname)/host.fw

Contoh:

[RULES]
IN ACCEPT -p icmp -log nolog

Rule node seperti ICMP dapat tetap dipertahankan apabila memang dibutuhkan.


Terminal window
pvecm status

Contoh standalone node:

Error: Corosync config '/etc/pve/corosync.conf' does not exist - is this node part of a cluster?

Ini menunjukkan node tidak tergabung dalam Corosync cluster.

Pada environment cluster, desain firewall harus mempertimbangkan kebutuhan antar-node seperti migration, corosync, storage, dan sebagainya.


Terminal window
ip -4 -br addr

Contoh:

lo UNKNOWN 127.0.0.1/8
vmbr0 UP 198.51.100.10/30

Dalam contoh tersebut:

vmbr0 = management/public bridge
198.51.100.10 = public management IP

Periksa juga konfigurasi jaringan:

Terminal window
cat /etc/network/interfaces

Contoh:

auto vmbr0
iface vmbr0 inet static
address 198.51.100.10/30
gateway 198.51.100.9
bridge-ports eno1
bridge-stp off
bridge-fd 0

Terminal window
ip -6 addr show scope global

Jika kosong:

root@pve01:~#

berarti host saat ini tidak memiliki global IPv6 address.

Jika ada global IPv6, hardening IPv6 harus dilakukan juga karena sshd atau pveproxy mungkin listen di IPv6.


Terminal window
ss -lntup

Contoh:

tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",...))
tcp LISTEN 0 4096 *:8006 *:* users:(("pveproxy",...))
tcp LISTEN 0 4096 *:3128 *:* users:(("spiceproxy",...))

Service penting Proxmox biasanya:

TCP/22 SSH
TCP/8006 Web UI / API
TCP/3128 SPICE proxy

Jika tidak ada service yang listen pada 80/443, rule Web(ACCEPT) biasanya tidak diperlukan.

8006 menggunakan TCP, sehingga UDP/8006 tidak perlu dibuka.


Terminal window
iptables-legacy -S PVEFW-HOST-IN

Contoh rule terlalu terbuka:

-A PVEFW-HOST-IN -p tcp --dport 80 -j RETURN
-A PVEFW-HOST-IN -p tcp --dport 443 -j RETURN
-A PVEFW-HOST-IN -p tcp --dport 22 -j RETURN
-A PVEFW-HOST-IN -p udp --dport 8006 -j RETURN
-A PVEFW-HOST-IN -p tcp --dport 8006 -j RETURN

Pada Proxmox firewall legacy backend, RETURN di chain tersebut berfungsi sebagai jalur allow kembali menuju parent chain.


Periksa:

Terminal window
iptables-legacy -S PVEFW-FORWARD

Contoh:

-N PVEFW-FORWARD
-A PVEFW-FORWARD -m conntrack --ctstate INVALID -j DROP
-A PVEFW-FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A PVEFW-FORWARD -m physdev --physdev-in fwln+ --physdev-is-bridged -j PVEFW-FWBR-IN
-A PVEFW-FORWARD -m physdev --physdev-out fwln+ --physdev-is-bridged -j PVEFW-FWBR-OUT

Sedangkan host:

Terminal window
iptables-legacy -S PVEFW-INPUT

Contoh:

-N PVEFW-INPUT
-A PVEFW-INPUT -j PVEFW-HOST-IN

Ini menjadi salah satu validasi bahwa host management dan VM forwarding berada di chain berbeda.


Terminal window
pve-firewall localnet

Contoh sebelum hardening:

local hostname: pve01
local IP address: 198.51.100.10
network auto detect: 198.51.100.8/30
using detected local_network: 198.51.100.8/30

Masalahnya, seluruh /30 tersebut dapat masuk ke built-in management network Proxmox.

Cek:

Terminal window
ipset list PVEFW-0-management-v4

Contoh:

Name: PVEFW-0-management-v4
Type: hash:net
Number of entries: 1
Members:
198.51.100.8/30

Untuk standalone public host, lebih aman override local_network menjadi IP host sendiri.


Gunakan custom IPSet:

admin_sources

Contoh:

[IPSET admin_sources]
<ADMIN_NET_1>
<ADMIN_NET_2>
<ADMIN_NET_3>
<ADMIN_SINGLE_IP>

Contoh terisi:

[IPSET admin_sources]
203.0.113.16/28
198.51.100.64/29
192.0.2.128/29
203.0.113.250/32

Hindari memakai special IPSet bernama management untuk general allowlist apabila kebutuhan hanya SSH dan GUI, karena Proxmox menggunakan management network untuk beberapa service internal lain juga.


Target /etc/pve/firewall/cluster.fw:

[OPTIONS]
enable: 1
[ALIASES]
local_network <PVE_PUBLIC_IP>
[IPSET admin_sources]
<ADMIN_NET_1>
<ADMIN_NET_2>
<ADMIN_NET_3>
<ADMIN_SINGLE_IP>
[RULES]
IN SSH(ACCEPT) -source +admin_sources -log nolog
IN ACCEPT -source +admin_sources -p tcp -dport 8006 -log nolog

Contoh:

[OPTIONS]
enable: 1
[ALIASES]
local_network 198.51.100.10
[IPSET admin_sources]
203.0.113.16/28
198.51.100.64/29
192.0.2.128/29
203.0.113.250/32
[RULES]
IN SSH(ACCEPT) -source +admin_sources -log nolog
IN ACCEPT -source +admin_sources -p tcp -dport 8006 -log nolog

Jika firewall VM sudah aktif, jangan melakukan:

enable: 0

sebagai metode staging.

Mematikan Datacenter Firewall dapat berdampak pada enforcement firewall VM.

Sebagai gantinya buat candidate di /root:

Terminal window
nano /root/cluster.fw.candidate

atau:

Terminal window
cat > /root/cluster.fw.candidate <<'EOF'
...
EOF

File ini belum dibaca oleh Proxmox firewall.


Gunakan parser Proxmox langsung:

Terminal window
perl -MPVE::Firewall -e '
my $f = "/root/cluster.fw.candidate";
my $conf = PVE::Firewall::load_clusterfw_conf($f);
die "PARSE FAILED\n" if !$conf;
print "PARSE OK\n";
my ($ruleset, $ipset_rules, $ipset_changes, $ebtables) =
PVE::Firewall::compile($conf, undef, undef, undef);
print "COMPILE OK\n";
'

Hasil yang diharapkan:

PARSE OK
COMPILE OK

Jika muncul error, jangan apply candidate.


Fingerprint semua file firewall VM:

Terminal window
find /etc/pve/firewall -maxdepth 1 -type f -regextype posix-extended \
-regex '.*/[0-9]+\.fw' -print0 \
| sort -z \
| xargs -0 sha256sum \
| sha256sum

Contoh:

=== GUEST FW CONFIG FINGERPRINT ===
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -

Fingerprint NIC VM:

Terminal window
for id in $(qm list | awk 'NR>1 {print $1}' | sort -n); do
echo "VM $id"
qm config "$id" | grep -E '^net[0-9]+:'
done | sha256sum

Contoh:

bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb -

Simpan kedua hash tersebut.

Sesudah hardening, hash harus tetap sama.


Terminal window
TS="$(date +%Y%m%d-%H%M%S)"
BACKUP_DIR="/root/pve-fw-backup-$TS"
mkdir -p "$BACKUP_DIR"
cp /etc/pve/firewall/cluster.fw \
"$BACKUP_DIR/cluster.fw"
if [ -f /etc/pve/nodes/<PVE_HOSTNAME>/host.fw ]; then
cp /etc/pve/nodes/<PVE_HOSTNAME>/host.fw \
"$BACKUP_DIR/host.fw"
fi

Contoh hasil:

/root/pve-fw-backup-YYYYMMDD-HHMMSS/
├── cluster.fw
└── host.fw

Verifikasi:

Terminal window
sha256sum \
/etc/pve/firewall/cluster.fw \
"$BACKUP_DIR/cluster.fw"

Hasil yang diharapkan, kedua hash sama:

111111... /etc/pve/firewall/cluster.fw
111111... /root/pve-fw-backup-YYYYMMDD-HHMMSS/cluster.fw

cat > /root/pve-fw-rollback.sh <<'EOF'
#!/bin/bash
set -e
cp /root/pve-fw-backup-<TIMESTAMP>/cluster.fw \
/etc/pve/firewall/cluster.fw
echo "Rollback config restored at $(date)"
sleep 15
echo "=== FIREWALL STATUS ==="
pve-firewall status
echo
echo "=== HOST RULES ==="
iptables-legacy -S PVEFW-HOST-IN
EOF
chmod 700 /root/pve-fw-rollback.sh

Sebelum apply:

Terminal window
systemd-run \
--unit=pve-fw-rollback \
--on-active=10m \
--timer-property=AccuracySec=1s \
/root/pve-fw-rollback.sh

Contoh:

Running timer as unit: pve-fw-rollback.timer
Will run service as unit: pve-fw-rollback.service

Verifikasi:

Terminal window
systemctl list-timers pve-fw-rollback.timer --no-pager

Contoh:

NEXT LEFT UNIT
Mon YYYY-MM-DD HH:MM:SS 9min pve-fw-rollback.timer

Jangan apply sebelum rollback timer terlihat aktif.


Apply:

Terminal window
cp /root/cluster.fw.candidate \
/etc/pve/firewall/cluster.fw

Gunakan plain cp.

Menggunakan:

Terminal window
cp -a

ke /etc/pve dapat menghasilkan warning seperti:

cp: clearing permissions for '/etc/pve/firewall/cluster.fw': Operation not permitted

karena /etc/pve menggunakan Proxmox Cluster File System (pmxcfs).

Tunggu reload:

Terminal window
sleep 10

Kemudian:

Terminal window
pve-firewall status

Hasil yang diharapkan:

Status: enabled/running

Terminal window
iptables-legacy -S PVEFW-HOST-IN

Bagian penting expected:

-A PVEFW-HOST-IN -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A PVEFW-HOST-IN -m set --match-set PVEFW-0-admin_sources-v4 src -p tcp --dport 22 -j RETURN
-A PVEFW-HOST-IN -m set --match-set PVEFW-0-admin_sources-v4 src -p tcp --dport 8006 -j RETURN
...
-A PVEFW-HOST-IN -j PVEFW-Drop
-A PVEFW-HOST-IN -j DROP

Rule berikut sudah tidak boleh ada:

-p tcp --dport 22 -j RETURN

tanpa source restriction.

Begitu juga:

-p tcp --dport 8006 -j RETURN

tanpa source restriction.


Terminal window
ipset list PVEFW-0-admin_sources-v4

Contoh:

Name: PVEFW-0-admin_sources-v4
Type: hash:net
Number of entries: 4
Members:
203.0.113.16/28
198.51.100.64/29
192.0.2.128/29
203.0.113.250

Jumlah dan isi harus sesuai dengan daftar administrator.


Terminal window
pve-firewall localnet

Hasil yang diharapkan:

local hostname: pve01
local IP address: 198.51.100.10
network auto detect: 198.51.100.8/30
using user defined local_network: 198.51.100.10

Perhatikan:

using user defined local_network

bukan:

using detected local_network

Terminal window
pve-firewall simulate \
--from outside \
--to host \
--source <NON_ADMIN_TEST_IP> \
--dest <PVE_PUBLIC_IP> \
--protocol tcp \
--dport 22 \
--verbose 1 | tail -n 12

Contoh:

Test packet:
from : outside
to : host
proto : tcp
source : 192.0.2.10
dest : 198.51.100.10
dport : 22
ACTION: DROP

Hasil yang diharapkan:

ACTION: DROP

Terminal window
pve-firewall simulate \
--from outside \
--to host \
--source <ADMIN_SINGLE_IP> \
--dest <PVE_PUBLIC_IP> \
--protocol tcp \
--dport 22 \
--verbose 1 | tail -n 12

Contoh output:

Test packet:
from : outside
to : host
proto : tcp
source : 203.0.113.250
dest : 198.51.100.10
dport : 22
ACTION: ACCEPT

Hasil yang diharapkan:

ACTION: ACCEPT

Terminal window
pve-firewall simulate \
--from outside \
--to host \
--source <NON_ADMIN_TEST_IP> \
--dest <PVE_PUBLIC_IP> \
--protocol tcp \
--dport 8006 \
--verbose 1 | tail -n 12

Hasil yang diharapkan:

Test packet:
from : outside
to : host
proto : tcp
source : 192.0.2.10
dest : 198.51.100.10
dport : 8006
ACTION: DROP

Terminal window
pve-firewall simulate \
--from outside \
--to host \
--source <ADMIN_SINGLE_IP> \
--dest <PVE_PUBLIC_IP> \
--protocol tcp \
--dport 8006 \
--verbose 1 | tail -n 12

Hasil yang diharapkan:

Test packet:
from : outside
to : host
proto : tcp
source : 203.0.113.250
dest : 198.51.100.10
dport : 8006
ACTION: ACCEPT

Jangan hanya mengandalkan session browser yang sudah terbuka.

Koneksi lama dapat tetap hidup karena rule:

RELATED,ESTABLISHED

Buka browser incognito/private baru lalu akses:

https://<PVE_PUBLIC_IP>:8006

Setelah berhasil, cek:

Terminal window
ss -tnp state established '( sport = :8006 )'

Contoh:

Recv-Q Send-Q Local Address:Port Peer Address:Port
0 0 198.51.100.10:8006 203.0.113.250:45123
0 0 198.51.100.10:8006 203.0.113.25:51290

Pastikan Peer Address berasal dari salah satu <ADMIN_NET_X>.


Jalankan ulang fingerprint firewall VM:

Terminal window
find /etc/pve/firewall -maxdepth 1 -type f -regextype posix-extended \
-regex '.*/[0-9]+\.fw' -print0 \
| sort -z \
| xargs -0 sha256sum \
| sha256sum

Contoh sebelum:

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -

Contoh sesudah:

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -

Harus sama.

Kemudian NIC:

Terminal window
for id in $(qm list | awk 'NR>1 {print $1}' | sort -n); do
echo "VM $id"
qm config "$id" | grep -E '^net[0-9]+:'
done | sha256sum

Sebelum:

bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb -

Sesudah:

bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb -

Jika hash berubah, investigasi sebelum cancel rollback.


Rollback baru boleh dicancel apabila seluruh checklist ini lolos:

[OK] Firewall enabled/running
[OK] Non-admin -> TCP/22 = DROP
[OK] Admin -> TCP/22 = ACCEPT
[OK] Non-admin -> TCP/8006 = DROP
[OK] Admin -> TCP/8006 = ACCEPT
[OK] Fresh browser connection berhasil
[OK] Guest firewall fingerprint sama
[OK] Guest NIC fingerprint sama

Stop timer:

Terminal window
systemctl stop pve-fw-rollback.timer

Verifikasi:

Terminal window
systemctl list-timers --all --no-pager \
| grep 'pve-fw-rollback' \
|| echo 'No active rollback timers'

Hasil yang diharapkan:

No active rollback timers

Bandingkan candidate dengan live config:

Terminal window
sha256sum \
/etc/pve/firewall/cluster.fw \
/root/cluster.fw.candidate

Hasil yang diharapkan:

cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc /etc/pve/firewall/cluster.fw
cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc /root/cluster.fw.candidate

Hash harus identik.


Opsional:

Terminal window
systemctl reset-failed \
pve-fw-rollback.service \
pve-fw-rollback-apply.service \
pve-fw-rollback-verify.service \
pve-fw-rollback-finalcheck.service 2>/dev/null || true

Kemudian:

Terminal window
systemctl list-units --all 'pve-fw-rollback*' --no-pager

Contoh hasil bersih:

UNIT LOAD ACTIVE SUB DESCRIPTION
0 loaded units listed.

Backup sebaiknya jangan langsung dihapus.


Pada:

Datacenter
└── Firewall
└── Rules

rule yang terlihat cukup:

OnTypeActionMacroProtocolSourceD.Port
✓inACCEPTSSH+admin_sources
✓inACCEPTtcp+admin_sources8006

Sedangkan:

Datacenter
└── Firewall
└── IPSet

berisi:

admin_sources
├── <ADMIN_NET_1>
├── <ADMIN_NET_2>
├── <ADMIN_NET_3>
└── <ADMIN_SINGLE_IP>

Dan:

Datacenter
└── Firewall
└── Alias

memiliki:

local_network = <PVE_PUBLIC_IP>

Setelah firewall allowlist stabil, audit SSH:

Terminal window
sshd -T | grep -E \
'^(permitrootlogin|passwordauthentication|pubkeyauthentication|maxauthtries) '

Contoh kondisi awal:

maxauthtries 6
permitrootlogin yes
pubkeyauthentication yes
passwordauthentication yes

Target jangka panjang dapat berupa:

pubkeyauthentication yes
passwordauthentication no

Untuk PermitRootLogin, sesuaikan dengan operational model.

Jangan disable password authentication sebelum semua lokasi administrator telah diuji menggunakan public key.


INTERNET
|
+--------------+--------------+
| |
NON-ADMIN ADMIN
| admin_sources
| |
TCP/22 DROP TCP/22 ACCEPT
TCP/8006 DROP TCP/8006 ACCEPT
| |
+--------------+--------------+
|
v
+-------------+
| PVE HOST |
| public mgmt |
+-------------+
|
+--------------+--------------+
| |
v v
HOST INPUT VM FORWARD
PVEFW-HOST-IN PVEFW-FWBR-*
| |
| v
admin-only access tap<VMID>
|
v
VM Firewall
|
Tenant Managed
|
DO NOT MODIFY

Harden the Proxmox host without treating tenant VM firewall rules as part of the host hardening scope.

Management access harus bersifat explicit allowlist, sedangkan konfigurasi firewall VM/tenant harus dianggap sebagai domain terpisah yang tidak diubah selama proses hardening host.