Working tests of policy and policies
This commit is contained in:
parent
9b34cd9199
commit
9c67e03de3
@ -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.
|
||||
```
|
||||
@ -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])
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
47
terraform/policy/test.json
Normal file
47
terraform/policy/test.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user