Terraform rules working
This commit is contained in:
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Terraform
|
||||||
|
*.tfstate
|
||||||
|
*.tfstate.*
|
||||||
|
.terraform/
|
||||||
|
*.tfvars
|
||||||
|
# Keep .terraform.lock.hcl in VCS for reproducible provider versions
|
||||||
|
tfplan
|
||||||
|
tfplan.json
|
||||||
|
# Ansible
|
||||||
|
.retry
|
||||||
|
# Общие
|
||||||
|
*.log
|
||||||
24
terraform/.terraform.lock.hcl
generated
Normal file
24
terraform/.terraform.lock.hcl
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# This file is maintained automatically by "terraform init".
|
||||||
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
|
provider "registry.terraform.io/hashicorp/local" {
|
||||||
|
version = "2.5.2"
|
||||||
|
hashes = [
|
||||||
|
"h1:JlMZD6nYqJ8sSrFfEAH0Vk/SL8WLZRmFaMUF9PJK5wM=",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "registry.terraform.io/hashicorp/template" {
|
||||||
|
version = "2.2.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:94qn780bi1qjrbC3uQtjJh3Wkfwd5+tTtJHOb7KTg9w=",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "registry.terraform.io/telmate/proxmox" {
|
||||||
|
version = "3.0.1-rc8"
|
||||||
|
constraints = "3.0.1-rc8"
|
||||||
|
hashes = [
|
||||||
|
"h1:W5X4T5AZUaqO++aAequNECUKJaXLC5upcws6Vp7mkBk=",
|
||||||
|
]
|
||||||
|
}
|
||||||
119
terraform/README.md
Normal file
119
terraform/README.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# Terraform Security as Code
|
||||||
|
|
||||||
|
This directory contains Terraform configurations for creating a Kubernetes cluster on Proxmox VMs. Security is implemented as code through policy checks.
|
||||||
|
|
||||||
|
## Security Policies
|
||||||
|
|
||||||
|
Security policies are defined as Open Policy Agent (OPA) Rego files in the `policy/` directory:
|
||||||
|
|
||||||
|
- **main.rego**: Combined security policy file that includes:
|
||||||
|
- VM security (password auth, root login, qemu-agent)
|
||||||
|
- Network security (bridge configuration, IPv6, DNS)
|
||||||
|
- Provider security (TLS verification, version pinning)
|
||||||
|
|
||||||
|
## Running Security Checks
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
1. Install OPA CLI and Conftest:
|
||||||
|
```bash
|
||||||
|
# Install OPA
|
||||||
|
curl -L -o opa https://openpolicy.io/downloads/latest/opa_linux_amd64
|
||||||
|
chmod 755 opa
|
||||||
|
sudo mv opa /usr/local/bin
|
||||||
|
|
||||||
|
# Install Conftest
|
||||||
|
wget https://github.com/open-policy-agent/conftest/releases/download/v0.42.1/conftest_0.42.1_Linux_x86_64.tar.gz
|
||||||
|
tar xzf conftest_0.42.1_Linux_x86_64.tar.gz
|
||||||
|
sudo mv conftest /usr/local/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install tfsec:
|
||||||
|
```bash
|
||||||
|
curl -s https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Install Checkov:
|
||||||
|
```bash
|
||||||
|
pip install checkov
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Policy Checks
|
||||||
|
|
||||||
|
1. Generate a Terraform plan and convert to JSON:
|
||||||
|
```bash
|
||||||
|
cd terraform
|
||||||
|
terraform init
|
||||||
|
terraform plan -out=tfplan
|
||||||
|
terraform show -json tfplan > tfplan.json
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run Conftest with OPA policies:
|
||||||
|
```bash
|
||||||
|
conftest test tfplan.json -p policy/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Run tfsec static analysis:
|
||||||
|
```bash
|
||||||
|
tfsec .
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
127
terraform/main.tf
Normal file
127
terraform/main.tf
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
resource "proxmox_vm_qemu" "proxmox_vm_master" {
|
||||||
|
count = var.num_k3s_masters
|
||||||
|
name = "k3s-master-${count.index}"
|
||||||
|
target_node = var.pm_node_name
|
||||||
|
clone = var.template_vm_name # The name of the template
|
||||||
|
agent = 1
|
||||||
|
cores = 2
|
||||||
|
memory = var.num_k3s_masters_mem
|
||||||
|
boot = "order=scsi0" # has to be the same as the OS disk of the template
|
||||||
|
scsihw = "virtio-scsi-single"
|
||||||
|
vm_state = "running"
|
||||||
|
automatic_reboot = true
|
||||||
|
|
||||||
|
# Cloud-Init configuration
|
||||||
|
cicustom = "vendor=local:snippets/qemu-guest-agent.yml" # /var/lib/vz/snippets/qemu-guest-agent.yml
|
||||||
|
ciupgrade = true
|
||||||
|
nameserver = "1.1.1.1 8.8.8.8"
|
||||||
|
ipconfig0 = "ip=${var.master_ips[count.index]}/${var.networkrange},gw=${var.gateway}"
|
||||||
|
skip_ipv6 = true
|
||||||
|
ciuser = "debian"
|
||||||
|
cipassword = ""
|
||||||
|
sshkeys = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKXXnm9Hl4fPCt/Xjd/8E5tKY+edtM/BvdMOXpx40oWG iac@proxmox.vadzik-iot.ru"
|
||||||
|
|
||||||
|
# Most cloud-init images require a serial device for their display
|
||||||
|
serial {
|
||||||
|
id = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
disks {
|
||||||
|
scsi {
|
||||||
|
scsi0 {
|
||||||
|
disk {
|
||||||
|
storage = "flash-VM"
|
||||||
|
size = "8G"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ide {
|
||||||
|
# Some images require a cloud-init disk on the IDE controller, others on the SCSI or SATA controller
|
||||||
|
ide1 {
|
||||||
|
cloudinit {
|
||||||
|
storage = "flash-VM"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
network {
|
||||||
|
id = 0
|
||||||
|
bridge = "vmbr2"
|
||||||
|
model = "virtio"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
resource "proxmox_vm_qemu" "proxmox_vm_workers" {
|
||||||
|
count = var.num_k3s_nodes
|
||||||
|
name = "k3s-worker-${count.index}"
|
||||||
|
target_node = var.pm_node_name
|
||||||
|
clone = var.template_vm_name
|
||||||
|
os_type = "cloud-init"
|
||||||
|
agent = 1
|
||||||
|
cores = 4
|
||||||
|
memory = var.num_k3s_nodes_mem
|
||||||
|
boot = "order=scsi0" # has to be the same as the OS disk of the template
|
||||||
|
scsihw = "virtio-scsi-single"
|
||||||
|
vm_state = "running"
|
||||||
|
automatic_reboot = true
|
||||||
|
|
||||||
|
# Cloud-Init configuration
|
||||||
|
cicustom = "vendor=local:snippets/qemu-guest-agent.yml" # /var/lib/vz/snippets/qemu-guest-agent.yml
|
||||||
|
ciupgrade = true
|
||||||
|
nameserver = "1.1.1.1 8.8.8.8"
|
||||||
|
ipconfig0 = "ip=${var.worker_ips[count.index]}/${var.networkrange},gw=${var.gateway}"
|
||||||
|
skip_ipv6 = true
|
||||||
|
ciuser = "debian"
|
||||||
|
cipassword = ""
|
||||||
|
sshkeys = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKXXnm9Hl4fPCt/Xjd/8E5tKY+edtM/BvdMOXpx40oWG iac@proxmox.vadzik-iot.ru"
|
||||||
|
|
||||||
|
# Most cloud-init images require a serial device for their display
|
||||||
|
serial {
|
||||||
|
id = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
disks {
|
||||||
|
scsi {
|
||||||
|
scsi0 {
|
||||||
|
disk {
|
||||||
|
storage = "flash-VM"
|
||||||
|
size = "8G"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ide {
|
||||||
|
# Some images require a cloud-init disk on the IDE controller, others on the SCSI or SATA controller
|
||||||
|
ide1 {
|
||||||
|
cloudinit {
|
||||||
|
storage = "flash-VM"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
network {
|
||||||
|
id = 0
|
||||||
|
bridge = "vmbr2"
|
||||||
|
model = "virtio"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data "template_file" "k8s" {
|
||||||
|
template = file("./templates/inventory.tpl")
|
||||||
|
vars = {
|
||||||
|
k3s_master_ip = "${join("\n", [for instance in proxmox_vm_qemu.proxmox_vm_master : join("", [instance.default_ipv4_address, " ansible_ssh_private_key_file=", var.pvt_key])])}"
|
||||||
|
k3s_node_ip = "${join("\n", [for instance in proxmox_vm_qemu.proxmox_vm_workers : join("", [instance.default_ipv4_address, " ansible_ssh_private_key_file=", var.pvt_key])])}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "local_file" "k8s_file" {
|
||||||
|
content = data.template_file.k8s.rendered
|
||||||
|
filename = "../ansible/inventory/k3s-cluster/hosts.ini"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "local_file" "var_file" {
|
||||||
|
source = "../ansible/inventory/group_vars/all.yml"
|
||||||
|
filename = "../ansible/inventory/k3s-cluster/group_vars/all.yml"
|
||||||
|
}
|
||||||
8
terraform/outputs.tf
Normal file
8
terraform/outputs.tf
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
output "Master-IPS" {
|
||||||
|
value = ["${proxmox_vm_qemu.proxmox_vm_master.*.default_ipv4_address}"]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "worker-IPS" {
|
||||||
|
value = ["${proxmox_vm_qemu.proxmox_vm_workers.*.default_ipv4_address}"]
|
||||||
|
}
|
||||||
99
terraform/policy/main.rego
Normal file
99
terraform/policy/main.rego
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
# 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"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper function to check if a value is empty or undefined
|
||||||
|
is_empty(value) {
|
||||||
|
value == ""
|
||||||
|
} {
|
||||||
|
value == null
|
||||||
|
} {
|
||||||
|
not value
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper function to check minimum memory requirements (in MB)
|
||||||
|
min_memory = 512
|
||||||
|
|
||||||
|
# Deny if VM allows password authentication
|
||||||
|
deny[msg] {
|
||||||
|
vm := get_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[_]
|
||||||
|
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[_]
|
||||||
|
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[_]
|
||||||
|
net := vm.values.network[_]
|
||||||
|
net.bridge != "vmbr2"
|
||||||
|
msg := sprintf("VM '%s' uses insecure network bridge '%s'. Use 'vmbr2'.", [vm.name, net.bridge])
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny if IPv6 is not disabled
|
||||||
|
deny[msg] {
|
||||||
|
vm := get_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
|
||||||
|
msg := "TLS verification must be enabled (pm_tls_insecure = false)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny if provider version is not pinned
|
||||||
|
deny[msg] {
|
||||||
|
provider := input.configuration.terraform.required_providers.proxmox
|
||||||
|
not startswith(provider.version_constraint, "=")
|
||||||
|
msg := "Provider version must be pinned with '=' constraint"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny if VM memory is below minimum requirement
|
||||||
|
deny[msg] {
|
||||||
|
vm := get_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])
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny if VM does not have a description
|
||||||
|
deny[msg] {
|
||||||
|
vm := get_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[_]
|
||||||
|
vm.values.scsihw == "lsi"
|
||||||
|
msg := sprintf("VM '%s' uses default SCSI controller. Use virtio-scsi-pci for better performance.", [vm.name])
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test rule to verify policy is loaded
|
||||||
|
test_policy_loaded {
|
||||||
|
true
|
||||||
|
}
|
||||||
184
terraform/policy/policy_test.rego
Normal file
184
terraform/policy/policy_test.rego
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test secure configuration passes
|
||||||
|
test_secure_config {
|
||||||
|
input := mock_input_secure
|
||||||
|
count(deny) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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 if VM does not have proper tags for identification
|
||||||
|
deny[msg] {
|
||||||
|
vm := get_vms[_]
|
||||||
|
is_empty(vm.values.tags)
|
||||||
|
msg := sprintf("VM '%s' must have tags for proper identification and management.", [vm.name])
|
||||||
|
}
|
||||||
|
# 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)."]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test network bridge
|
||||||
|
test_network_bridge {# Deny if VM does not have proper tags for identification
|
||||||
|
deny[msg] {
|
||||||
|
vm := get_vms[_]
|
||||||
|
is_empty(vm.values.tags)
|
||||||
|
msg := sprintf("VM '%s' must have tags for proper identification and management.", [vm.name])
|
||||||
|
}abled (skip_ipv6 = true)."]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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)"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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."]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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."]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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."]
|
||||||
|
}
|
||||||
14
terraform/policy_backup/policies_test.md
Normal file
14
terraform/policy_backup/policies_test.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
# Deny if VM does not have a backup strategy
|
||||||
|
deny[msg] {
|
||||||
|
vm := get_vms[_]
|
||||||
|
not vm.values.backup
|
||||||
|
msg := sprintf("VM '%s' must have backup enabled for disaster recovery.", [vm.name])
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny if VM does not have proper tags for identification
|
||||||
|
deny[msg] {
|
||||||
|
vm := get_vms[_]
|
||||||
|
is_empty(vm.values.tags)
|
||||||
|
msg := sprintf("VM '%s' must have tags for proper identification and management.", [vm.name])
|
||||||
|
}
|
||||||
24
terraform/provider.tf
Normal file
24
terraform/provider.tf
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
proxmox = {
|
||||||
|
source = "telmate/proxmox"
|
||||||
|
version = "3.0.1-rc8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "proxmox" {
|
||||||
|
pm_api_url = "https://${var.pm_host}:8006/api2/json"
|
||||||
|
pm_user = var.pm_user
|
||||||
|
pm_password = var.pm_password
|
||||||
|
pm_tls_insecure = var.pm_tls_insecure
|
||||||
|
pm_parallel = 10
|
||||||
|
pm_timeout = 600
|
||||||
|
# pm_debug = true
|
||||||
|
pm_log_enable = true
|
||||||
|
pm_log_file = "terraform-plugin-proxmox.log"
|
||||||
|
pm_log_levels = {
|
||||||
|
_default = "debug"
|
||||||
|
_capturelog = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
43
terraform/scripts/pre-commit
Executable file
43
terraform/scripts/pre-commit
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Pre-commit hook for Terraform security checks
|
||||||
|
# Place this file in .git/hooks/pre-commit and make it executable
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
YELLOW='\033[0;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Running Terraform security pre-commit checks...${NC}"
|
||||||
|
|
||||||
|
# Only run checks if terraform files have changed
|
||||||
|
TERRAFORM_FILES_CHANGED=$(git diff --cached --name-only | grep -E '\.tf$|\.tfvars$')
|
||||||
|
|
||||||
|
if [ -z "$TERRAFORM_FILES_CHANGED" ]; then
|
||||||
|
echo -e "${GREEN}No Terraform files changed. Skipping security checks.${NC}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Store current directory
|
||||||
|
CURRENT_DIR=$(pwd)
|
||||||
|
|
||||||
|
# Check if scripts/run_security_checks.sh exists
|
||||||
|
if [ -f "terraform/scripts/run_security_checks.sh" ]; then
|
||||||
|
# Change to terraform directory and run the security checks
|
||||||
|
cd terraform
|
||||||
|
if bash scripts/run_security_checks.sh --pre-commit; then
|
||||||
|
cd "$CURRENT_DIR"
|
||||||
|
echo -e "${GREEN}Terraform security checks passed!${NC}"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
cd "$CURRENT_DIR"
|
||||||
|
echo -e "${RED}Terraform security checks failed!${NC}"
|
||||||
|
echo -e "${YELLOW}You can bypass this check with git commit --no-verify, but this is NOT recommended.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${RED}Security check script not found at terraform/scripts/run_security_checks.sh${NC}"
|
||||||
|
echo -e "${YELLOW}Skipping security checks. Please set up the security check script.${NC}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
129
terraform/scripts/run_security_checks.sh
Executable file
129
terraform/scripts/run_security_checks.sh
Executable file
@@ -0,0 +1,129 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Security Check Script for Terraform
|
||||||
|
# This script runs all security checks on your Terraform configuration
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
YELLOW='\033[0;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Starting Terraform Security Checks...${NC}"
|
||||||
|
|
||||||
|
# Check if we're in the terraform directory
|
||||||
|
if [ ! -f "main.tf" ]; then
|
||||||
|
echo -e "${RED}Error: Please run this script from the terraform directory${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Function to check if a command exists
|
||||||
|
command_exists() {
|
||||||
|
command -v "$1" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check for required tools
|
||||||
|
echo -e "\n${YELLOW}Checking for required tools...${NC}"
|
||||||
|
|
||||||
|
MISSING_TOOLS=0
|
||||||
|
|
||||||
|
if ! command_exists terraform; then
|
||||||
|
echo -e "${RED}❌ terraform not found. Please install terraform.${NC}"
|
||||||
|
MISSING_TOOLS=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command_exists conftest; then
|
||||||
|
echo -e "${RED}❌ conftest not found. Please install conftest.${NC}"
|
||||||
|
echo " curl -L https://github.com/open-policy-agent/conftest/releases/download/v0.42.1/conftest_0.42.1_Linux_x86_64.tar.gz | tar xz"
|
||||||
|
echo " sudo mv conftest /usr/local/bin/"
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✅ All required tools are installed.${NC}"
|
||||||
|
|
||||||
|
# Step 1: Terraform validation
|
||||||
|
echo -e "\n${YELLOW}Running Terraform validation...${NC}"
|
||||||
|
terraform validate
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo -e "${GREEN}✅ Terraform validation passed.${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}❌ Terraform validation failed.${NC}"
|
||||||
|
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
|
||||||
|
|
||||||
|
# Step 3: Run checkov
|
||||||
|
echo -e "\n${YELLOW}Running checkov security scanner...${NC}"
|
||||||
|
checkov -d .
|
||||||
|
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}Generating Terraform plan...${NC}"
|
||||||
|
terraform plan -var-file="variables.tfvars" -out=tfplan
|
||||||
|
terraform show -json tfplan > tfplan.json
|
||||||
|
|
||||||
|
echo -e "\n${YELLOW}Running OPA policy checks...${NC}"
|
||||||
|
if [ -d "policies" ]; then
|
||||||
|
conftest test tfplan.json -p policies/
|
||||||
|
CONFTEST_EXIT=$?
|
||||||
|
if [ $CONFTEST_EXIT -eq 0 ]; then
|
||||||
|
echo -e "${GREEN}✅ OPA policy checks passed.${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}❌ OPA policy checks found issues.${NC}"
|
||||||
|
# We continue execution to show summary
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${RED}❌ Policies directory not found. Skipping OPA checks.${NC}"
|
||||||
|
CONFTEST_EXIT=1
|
||||||
|
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
|
||||||
|
echo -e "\n${GREEN}All security checks passed!${NC}"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo -e "\n${RED}Some security checks failed. Please address the issues before proceeding.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
9
terraform/templates/inventory.tpl
Normal file
9
terraform/templates/inventory.tpl
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[master]
|
||||||
|
${k3s_master_ip}
|
||||||
|
|
||||||
|
[node]
|
||||||
|
${k3s_node_ip}
|
||||||
|
|
||||||
|
[k3s_cluster:children]
|
||||||
|
master
|
||||||
|
node
|
||||||
65
terraform/variables.tf
Normal file
65
terraform/variables.tf
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
variable "pm_user" {
|
||||||
|
description = "The username for the proxmox user"
|
||||||
|
type = string
|
||||||
|
sensitive = false
|
||||||
|
default = "root@pam"
|
||||||
|
|
||||||
|
}
|
||||||
|
variable "pm_password" {
|
||||||
|
description = "The password for the proxmox user"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pm_tls_insecure" {
|
||||||
|
description = "Set to true to ignore certificate errors"
|
||||||
|
type = bool
|
||||||
|
default = false
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pm_host" {
|
||||||
|
description = "The hostname or IP of the proxmox server"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pm_node_name" {
|
||||||
|
description = "name of the proxmox node to create the VMs on"
|
||||||
|
type = string
|
||||||
|
default = "pve"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pvt_key" {}
|
||||||
|
|
||||||
|
variable "num_k3s_masters" {
|
||||||
|
default = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "num_k3s_masters_mem" {
|
||||||
|
default = "4096"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "num_k3s_nodes" {
|
||||||
|
default = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "num_k3s_nodes_mem" {
|
||||||
|
default = "4096"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "template_vm_name" {}
|
||||||
|
|
||||||
|
variable "master_ips" {
|
||||||
|
description = "List of ip addresses for master nodes"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "worker_ips" {
|
||||||
|
description = "List of ip addresses for worker nodes"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "networkrange" {
|
||||||
|
default = 24
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "gateway" {
|
||||||
|
default = "192.168.30.1"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user