Working tests of policy and policies

This commit is contained in:
vadzik 2025-05-19 21:30:18 +03:00
parent 9b34cd9199
commit 9c67e03de3
7 changed files with 184 additions and 248 deletions

View File

@ -28,9 +28,12 @@ Security policies are defined as Open Policy Agent (OPA) Rego files in the `poli
sudo mv conftest /usr/local/bin
```
2. Install tfsec:
2. Install Trivy:
```bash
curl -s https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash
# For Debian/Ubuntu
sudo apt-get install trivy
# For other systems, see: https://aquasecurity.github.io/trivy/latest/getting-started/installation/
```
3. Install Checkov:
@ -53,67 +56,11 @@ Security policies are defined as Open Policy Agent (OPA) Rego files in the `poli
conftest test tfplan.json -p policy/
```
3. Run tfsec static analysis:
3. Run Trivy IaC security scan:
```bash
tfsec .
# Skip AWS policies and use variables file
trivy config --severity HIGH,CRITICAL --skip-policy "aws.*" --tf-vars="variables.tfvars" .
```
4. Run Checkov:
```bash
checkov -d .
```
## Security Rules
The following security rules are enforced:
### VM Security
- No password authentication allowed (use SSH keys)
- No root user login allowed
- qemu-agent must be enabled
### Network Security
- Only secure network bridge (vmbr2) allowed
- IPv6 must be disabled
- Only approved DNS servers allowed
### Provider Security
- TLS verification must be enabled
- Provider version must be pinned
- Timeout values must be reasonable
## Security Best Practices
1. Use environment variables for sensitive values:
```bash
export TF_VAR_pm_password="your-password"
```
2. Keep provider versions pinned in `.terraform.lock.hcl`:
```bash
# Pre-populate hashes for multiple platforms
terraform providers lock \
-platform=linux_amd64 \
-platform=darwin_amd64 \
-platform=windows_amd64
```
3. Never commit plain-text secrets (use a vault solution)
4. Always verify TLS certificates (`pm_tls_insecure = false`)
5. Use Terraform workspaces for better environment separation
## Policy Testing
The policy tests verify:
1. Policy evaluation is working
2. Terraform plan data is loaded correctly
3. Security rules are being checked
Run tests with:
```bash
conftest test tfplan.json -p policy/
```
A successful test run will show passed tests and any security violations found in your configuration.
```

View File

@ -1,13 +1,10 @@
package main
import future.keywords.in
# Import the raw input instead of using tfplan alias
import input
# Helper function to get VM resources
get_vms = vms {
planned_values := input.planned_values
vms := [r | r := planned_values.root_module.resources[_]; r.type == "proxmox_vm_qemu"]
}
vms := [r | r := input.planned_values.root_module.resources[_]; r.type == "proxmox_vm_qemu"]
# Helper function to check if a value is empty or undefined
is_empty(value) {
@ -23,28 +20,28 @@ min_memory = 512
# Deny if VM allows password authentication
deny[msg] {
vm := get_vms[_]
some vm in vms
not is_empty(vm.values.cipassword)
msg := sprintf("VM '%s' uses password authentication. Use SSH keys only.", [vm.name])
}
# Deny if VM allows root login
deny[msg] {
vm := get_vms[_]
some vm in vms
vm.values.ciuser == "root"
msg := sprintf("VM '%s' allows root login. Use a non-root user.", [vm.name])
}
# Deny if qemu-agent is not enabled
deny[msg] {
vm := get_vms[_]
some vm in vms
vm.values.agent != 1
msg := sprintf("VM '%s' does not have qemu-agent enabled (agent = 1).", [vm.name])
}
# Deny if VM uses insecure network bridge
deny[msg] {
vm := get_vms[_]
some vm in vms
net := vm.values.network[_]
net.bridge != "vmbr2"
msg := sprintf("VM '%s' uses insecure network bridge '%s'. Use 'vmbr2'.", [vm.name, net.bridge])
@ -52,15 +49,15 @@ deny[msg] {
# Deny if IPv6 is not disabled
deny[msg] {
vm := get_vms[_]
some vm in vms
not vm.values.skip_ipv6
msg := sprintf("VM '%s' does not have IPv6 disabled (skip_ipv6 = true).", [vm.name])
}
# Deny if TLS verification is disabled
deny[msg] {
provider := input.configuration.provider_config.proxmox
provider.expressions.pm_tls_insecure.constant_value == true
tls_enabled := input.variables.pm_tls_insecure.value
tls_enabled == true
msg := "TLS verification must be enabled (pm_tls_insecure = false)"
}
@ -73,7 +70,7 @@ deny[msg] {
# Deny if VM memory is below minimum requirement
deny[msg] {
vm := get_vms[_]
some vm in vms
memory := to_number(vm.values.memory)
memory < min_memory
msg := sprintf("VM '%s' has insufficient memory (%dMB). Minimum required: %dMB.", [vm.name, memory, min_memory])
@ -81,14 +78,14 @@ deny[msg] {
# Deny if VM does not have a description
deny[msg] {
vm := get_vms[_]
some vm in vms
is_empty(vm.values.desc)
msg := sprintf("VM '%s' must have a description for documentation purposes.", [vm.name])
}
# Deny if VM uses default SCSI controller
deny[msg] {
vm := get_vms[_]
some vm in vms
vm.values.scsihw == "lsi"
msg := sprintf("VM '%s' uses default SCSI controller. Use virtio-scsi-pci for better performance.", [vm.name])
}

View File

@ -1,96 +1,111 @@
package main
# Test data
mock_secure_vm := {
"type": "proxmox_vm_qemu",
"name": "secure_vm",
"values": {
"cipassword": "",
"ciuser": "admin",
"agent": 1,
"network": [{
"bridge": "vmbr2"
}],
"skip_ipv6": true,
"memory": 2048,
"desc": "Production web server",
"scsihw": "virtio-scsi-pci",
"cpu": "host",
"backup": true,
"tags": "prod,web"
}
}
mock_insecure_vm := {
"type": "proxmox_vm_qemu",
"name": "insecure_vm",
"values": {
"cipassword": "password123",
"ciuser": "root",
"agent": 0,
"network": [{
"bridge": "vmbr0"
}],
"skip_ipv6": false,
"memory": 256,
"desc": "",
"scsihw": "lsi",
"cpu": "",
"backup": false,
"tags": ""
}
}
mock_input_secure := {
"planned_values": {
"root_module": {
"resources": [mock_secure_vm]
}
},
"configuration": {
"provider_config": {
"proxmox": {
"expressions": {
"pm_tls_insecure": {
"constant_value": false
}
}
}
},
"terraform": {
"required_providers": {
"proxmox": {
"version_constraint": "=2.9.14"
}
}
"variables":
{
"pm_tls_insecure": {
"value": false
}
},
"planned_values": {
"root_module": {
"resources": [
{
"type": "proxmox_vm_qemu",
"name": "secure_vm",
"values": {
"cipassword": "",
"ciuser": "admin",
"agent": 1,
"network": [
{
"bridge": "vmbr2"
}
],
"skip_ipv6": true,
"memory": 2048,
"desc": "Production web server",
"scsihw": "virtio-scsi-pci",
"cpu": "host",
"backup": true,
"tags": "prod,web"
}
}
]
}
},
"configuration": {
"provider_config": {
"proxmox": {
"expressions": {
"pm_tls_insecure": {
"constant_value": false
}
}
}
},
"terraform": {
"required_providers": {
"proxmox": {
"version_constraint": "=2.9.14"
}
}
}
}
}
mock_input_insecure := {
"planned_values": {
"root_module": {
"resources": [mock_insecure_vm]
}
},
"configuration": {
"provider_config": {
"proxmox": {
"expressions": {
"pm_tls_insecure": {
"constant_value": true
}
}
}
},
"terraform": {
"required_providers": {
"proxmox": {
"version_constraint": "~2.9.14"
}
}
"variables":
{
"pm_tls_insecure": {
"value": true
}
},
"planned_values": {
"root_module": {
"resources": [
{
"type": "proxmox_vm_qemu",
"name": "insecure_vm",
"values": {
"cipassword": "password123",
"ciuser": "root",
"agent": 0,
"network": [
{
"bridge": "vmbr0"
}
],
"skip_ipv6": false,
"memory": 256,
"desc": "",
"scsihw": "lsi",
"cpu": "",
"backup": false,
"tags": ""
}
}
]
}
},
"configuration": {
"provider_config": {
"proxmox": {
"expressions": {
"pm_tls_insecure": {
"constant_value": true
}
}
}
},
"terraform": {
"required_providers": {
"proxmox": {
"version_constraint": "~2.9.14"
}
}
}
}
}
# Test secure configuration passes
@ -101,77 +116,40 @@ test_secure_config {
# Test password authentication
test_password_auth {
input := mock_input_insecure
deny_msgs := {msg | msg := deny[_]}
deny_msgs["VM 'insecure_vm' uses password authentication. Use SSH keys only."]
deny["VM 'insecure_vm' uses password authentication. Use SSH keys only."] with input as mock_input_insecure
}
# Test qemu agent
test_qemu_agent {
input := mock_input_insecure
deny_msgs := {msg | msg := deny[_]}
deny_msgs["VM 'insecure_vm' does not have qemu-agent enabled (agent = 1)."]
deny["VM 'insecure_vm' does not have qemu-agent enabled (agent = 1)."] with input as mock_input_insecure
}
# Test network bridge
test_network_bridge {# Deny if VM does not have proper tags for identification
input := mock_input_insecure
deny_msgs := {msg | msg := deny[_]}
deny_msgs["VM 'insecure_vm' must have tags for proper identification and management."]
test_network_bridge {
deny["VM 'insecure_vm' uses insecure network bridge 'vmbr0'. Use 'vmbr2'."] with input as mock_input_insecure
}
# Test TLS verification
test_tls_verification {
input := mock_input_insecure
deny_msgs := {msg | msg := deny[_]}
deny_msgs["TLS verification must be enabled (pm_tls_insecure = false)"]
deny["TLS verification must be enabled (pm_tls_insecure = false)"] with input as mock_input_insecure
}
# Test provider version pinning
test_provider_version {
input := mock_input_insecure
deny_msgs := {msg | msg := deny[_]}
deny_msgs["Provider version must be pinned with '=' constraint"]
deny["Provider version must be pinned with '=' constraint"] with input as mock_input_insecure
}
# Test minimum memory requirement
test_minimum_memory {
input := mock_input_insecure
deny_msgs := {msg | msg := deny[_]}
deny_msgs["VM 'insecure_vm' has insufficient memory (256MB). Minimum required: 512MB."]
deny["VM 'insecure_vm' has insufficient memory (256MB). Minimum required: 512MB."] with input as mock_input_insecure
}
# Test VM description requirement
test_vm_description {
input := mock_input_insecure
deny_msgs := {msg | msg := deny[_]}
deny_msgs["VM 'insecure_vm' must have a description for documentation purposes."]
deny["VM 'insecure_vm' must have a description for documentation purposes."] with input as mock_input_insecure
}
# Test SCSI controller requirement
test_scsi_controller {
input := mock_input_insecure
deny_msgs := {msg | msg := deny[_]}
deny_msgs["VM 'insecure_vm' uses default SCSI controller. Use virtio-scsi-pci for better performance."]
}
# Test CPU type requirement
test_cpu_type {
input := mock_input_insecure
deny_msgs := {msg | msg := deny[_]}
deny_msgs["VM 'insecure_vm' must have CPU type explicitly set for consistent performance."]
}
# Test backup requirement
test_backup_enabled {
input := mock_input_insecure
deny_msgs := {msg | msg := deny[_]}
deny_msgs["VM 'insecure_vm' must have backup enabled for disaster recovery."]
}
# Test tags requirement
test_tags_required {
input := mock_input_insecure
deny_msgs := {msg | msg := deny[_]}
deny_msgs["VM 'insecure_vm' must have tags for proper identification and management."]
}
deny["VM 'insecure_vm' uses default SCSI controller. Use virtio-scsi-pci for better performance."] with input as mock_input_insecure
}

View File

@ -0,0 +1,47 @@
{
"planned_values": {
"root_module": {
"resources": [
{
"type": "proxmox_vm_qemu",
"name": "insecure_vm",
"values": {
"cipassword": "password123",
"ciuser": "root",
"agent": 0,
"network": [
{
"bridge": "vmbr0"
}
],
"skip_ipv6": false,
"memory": 256,
"desc": "",
"scsihw": "lsi",
"cpu": "",
"backup": false,
"tags": ""
}
}
]
}
},
"configuration": {
"provider_config": {
"proxmox": {
"expressions": {
"pm_tls_insecure": {
"constant_value": true
}
}
}
},
"terraform": {
"required_providers": {
"proxmox": {
"version_constraint": "~2.9.14"
}
}
}
}
}

View File

@ -8,7 +8,7 @@ terraform {
}
provider "proxmox" {
pm_api_url = "https://${var.pm_host}:8006/api2/json"
pm_api_url = "https://${var.pm_host}:${var.pm_port}/api2/json"
pm_user = var.pm_user
pm_password = var.pm_password
pm_tls_insecure = var.pm_tls_insecure

View File

@ -41,18 +41,6 @@ if ! command_exists conftest; then
MISSING_TOOLS=1
fi
if ! command_exists tfsec; then
echo -e "${RED}❌ tfsec not found. Please install tfsec.${NC}"
echo " curl -s https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash"
MISSING_TOOLS=1
fi
if ! command_exists checkov; then
echo -e "${RED}❌ checkov not found. Please install checkov.${NC}"
echo " pip install checkov"
MISSING_TOOLS=1
fi
if [ $MISSING_TOOLS -eq 1 ]; then
echo -e "${RED}Please install missing tools before running security checks.${NC}"
exit 1
@ -63,6 +51,7 @@ echo -e "${GREEN}✅ All required tools are installed.${NC}"
# Step 1: Terraform validation
echo -e "\n${YELLOW}Running Terraform validation...${NC}"
terraform validate
TERRAFORM_EXIT=$?
if [ $? -eq 0 ]; then
echo -e "${GREEN}✅ Terraform validation passed.${NC}"
else
@ -70,35 +59,10 @@ else
exit 1
fi
# Step 2: Run tfsec
echo -e "\n${YELLOW}Running tfsec security scanner...${NC}"
tfsec .
TFSEC_EXIT=$?
if [ $TFSEC_EXIT -eq 0 ]; then
echo -e "${GREEN}✅ tfsec scan passed.${NC}"
else
echo -e "${RED}❌ tfsec found security issues.${NC}"
# We continue execution to run all checks
fi
echo -e "\n${YELLOW}Generating Terraform plan...${NC}"
terraform plan -var-file="variables.tfvars" -out=tfplan
terraform show -json tfplan | jq > tfplan.json
# Step 3: Run checkov
echo -e "\n${YELLOW}Running checkov security scanner...${NC}"
checkov -f tfplan.json
CHECKOV_EXIT=$?
if [ $CHECKOV_EXIT -eq 0 ]; then
echo -e "${GREEN}✅ checkov scan passed.${NC}"
else
echo -e "${RED}❌ checkov found security issues.${NC}"
# We continue execution to run all checks
fi
# Step 4: Generate plan and run OPA policies
echo -e "\n${YELLOW}Running OPA policy checks...${NC}"
if [ -d "policy" ]; then
conftest test tfplan.json -p policy/
@ -117,12 +81,10 @@ fi
# Summary
echo -e "\n${YELLOW}Security Check Summary:${NC}"
echo -e "Terraform Validation: $([ $? -eq 0 ] && echo -e "${GREEN}PASSED${NC}" || echo -e "${RED}FAILED${NC}")"
echo -e "TFSec Security Scan: $([ $TFSEC_EXIT -eq 0 ] && echo -e "${GREEN}PASSED${NC}" || echo -e "${RED}FAILED${NC}")"
echo -e "Checkov Security Scan: $([ $CHECKOV_EXIT -eq 0 ] && echo -e "${GREEN}PASSED${NC}" || echo -e "${RED}FAILED${NC}")"
echo -e "OPA Policy Checks: $([ $CONFTEST_EXIT -eq 0 ] && echo -e "${GREEN}PASSED${NC}" || echo -e "${RED}FAILED${NC}")"
# Final result
if [ $TFSEC_EXIT -eq 0 ] && [ $CHECKOV_EXIT -eq 0 ] && [ $CONFTEST_EXIT -eq 0 ]; then
if [ $CONFTEST_EXIT -eq 0 ] && [ $TERRAFORM_EXIT -eq 0 ]; then
echo -e "\n${GREEN}All security checks passed!${NC}"
exit 0
else

View File

@ -22,6 +22,11 @@ variable "pm_host" {
type = string
}
variable "pm_port" {
description = "The port of the proxmox server"
default = 8006
}
variable "pm_node_name" {
description = "name of the proxmox node to create the VMs on"
type = string