infrastructure/policies/security.rego
root 74eeabb354
Some checks failed
PR Checks / tofu-checks (pull_request) Failing after 2s
1/1 projects applied successfully.
feat: add tenant VM module for VM-as-a-Service (Step 5.2)
Reusable OpenTofu module for creating isolated tenant VMs with:
- Public IP on vmbr1 (bridged, firewall=true)
- Cloud-init: password auth, fail2ban, UFW hardening
- Per-VM Proxmox firewall (IN: SSH+ICMP, OUT: allow, block SMTP)

Includes test-tenant VM (185.47.204.227) for verification.

Changes:
- modules/tenant-vm/ — reusable module (VM + FW + cloud-init)
- environments/production/tenant-vms.tf — tenant VM definitions
- policies/security.rego — require firewall=true on vmbr1
- atlantis.yaml — trigger on module file changes
- main.tf — updated host prerequisites comment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 20:01:38 +01:00

39 lines
1.4 KiB
Rego

package main
import rego.v1
# NOTE: Per-NIC Proxmox firewall (bridge-nf-call-iptables) conflicts with NAT
# routing on bridges without physical uplink (vmbr0). Security is ensured by:
# - NAT isolation (VMs not reachable from outside)
# - Host-level Proxmox firewall (default DROP on input/output)
# - SSH key-only authentication via cloud-init
# No password authentication on VMs using user_account block
# (Tenant VMs use cloud-init snippets with user_data_file_id instead,
# so this rule does not apply to them)
deny contains msg if {
some resource in input.resource_changes
resource.type == "proxmox_virtual_environment_vm"
resource.change.actions[_] in {"create", "update"}
resource.change.after_sensitive.initialization[0].user_account[0].password == true
msg := sprintf(
"SECURITY: VM '%s' uses password auth via user_account. Use SSH keys or cloud-init snippet.",
[resource.address],
)
}
# VMs on vmbr1 (bridged, public IP) MUST have firewall=true on NIC
deny contains msg if {
some resource in input.resource_changes
resource.type == "proxmox_virtual_environment_vm"
resource.change.actions[_] in {"create", "update"}
resource.change.after.network_device[0].bridge == "vmbr1"
not resource.change.after.network_device[0].firewall
msg := sprintf(
"SECURITY: VM '%s' on vmbr1 (public bridge) must have firewall=true on NIC.",
[resource.address],
)
}