[Add] Updated conftest to latest version, policies updated, precommit hook also updated

This commit is contained in:
Maxim Romanko 2025-05-23 22:21:21 +03:00
parent 8a0ae2e8fb
commit 5f39f381c1
28 changed files with 838 additions and 126 deletions

115
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,115 @@
stages:
- validate
- plan
- apply
# Cache modules between jobs
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .terraform
variables:
TERRAFORM_VERSION: "1.10.5"
TF_STATE_NAME: ${CI_PROJECT_NAME}
before_script:
- cd terraform
- apk add --update curl jq python3 py3-pip
- pip install checkov
- curl -LO "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip"
- unzip "terraform_${TERRAFORM_VERSION}_linux_amd64.zip" -d /usr/local/bin/
- curl -L "https://github.com/aquasecurity/trivy/releases/latest/download/trivy_$(uname -s)_$(uname -m).tar.gz" | tar xz
- mv trivy /usr/local/bin/
- curl -s https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash
# Validate syntax and formatting
terraform-validate:
stage: validate
image: hashicorp/terraform:${TERRAFORM_VERSION}
script:
- terraform init -backend=false
- terraform validate
- terraform fmt -check -recursive
only:
changes:
- "terraform/**/*.tf"
# Security check with trivy
trivy:
stage: validate
image: alpine:latest
script:
- trivy config --format junit --output trivy.test.xml --check-namespaces proxmox .
only:
changes:
- "terraform/**/*.tf"
artifacts:
reports:
junit: "trivy.test.xml"
paths:
- "trivy.test.xml"
# Security check with checkov
checkov:
stage: validate
image: alpine:latest
script:
- checkov -d . --quiet
only:
changes:
- "terraform/**/*.tf"
# Policy validation with conftest
policy-check:
stage: validate
image: alpine:latest
script:
- terraform init -backend=false
- terraform plan -out=tfplan
- terraform show -json tfplan > tfplan.json
- conftest test tfplan.json -p policies/
only:
changes:
- "terraform/**/*.tf"
- "terraform/policies/**/*.rego"
# Create Terraform plan
terraform-plan:
stage: plan
image: hashicorp/terraform:${TERRAFORM_VERSION}
script:
- terraform init
- terraform plan -out=tfplan
# Save the plan as an artifact
- terraform show -json tfplan > tfplan.json
artifacts:
paths:
- terraform/tfplan
- terraform/tfplan.json
expire_in: 1 week
only:
changes:
- "terraform/**/*.tf"
when: manual
needs:
- terraform-validate
- trivy
- checkov
- policy-check
# Apply the changes
terraform-apply:
stage: apply
image: hashicorp/terraform:${TERRAFORM_VERSION}
script:
- terraform init
- terraform apply -auto-approve tfplan
dependencies:
- terraform-plan
only:
changes:
- "terraform/**/*.tf"
when: manual
needs:
- terraform-plan

52
README.md Normal file
View File

@ -0,0 +1,52 @@
# Conftest
[![Go Report Card](https://goreportcard.com/badge/open-policy-agent/opa)](https://goreportcard.com/report/open-policy-agent/conftest) [![Netlify](https://api.netlify.com/api/v1/badges/2d928746-3380-4123-b0eb-1fd74ba390db/deploy-status)](https://app.netlify.com/sites/vibrant-villani-65041c/deploys)
Conftest helps you write tests against structured configuration data. Using Conftest you can
write tests for your Kubernetes configuration, Tekton pipeline definitions, Terraform code,
Serverless configs or any other config files.
Conftest uses the Rego language from [Open Policy Agent](https://www.openpolicyagent.org/) for writing
the assertions. You can read more about Rego in [How do I write policies](https://www.openpolicyagent.org/docs/how-do-i-write-policies.html)
in the Open Policy Agent documentation.
Here's a quick example. Save the following as `policy/deployment.rego`:
```rego
package main
deny[msg] {
input.kind == "Deployment"
not input.spec.template.spec.securityContext.runAsNonRoot
msg := "Containers must not run as root"
}
deny[msg] {
input.kind == "Deployment"
not input.spec.selector.matchLabels.app
msg := "Containers must provide app label for pod selectors"
}
```
Assuming you have a Kubernetes deployment in `deployment.yaml` you can run Conftest like so:
```console
$ conftest test deployment.yaml
FAIL - deployment.yaml - Containers must not run as root
FAIL - deployment.yaml - Containers must provide app label for pod selectors
2 tests, 0 passed, 0 warnings, 2 failures, 0 exceptions
```
Conftest isn't specific to Kubernetes. It will happily let you write tests for any configuration files in a variety of different formats. See the [documentation](https://www.conftest.dev/) for [installation instructions](https://www.conftest.dev/install/) and
more details about the features.
## Want to contribute to Conftest?
* See [DEVELOPMENT.md](DEVELOPMENT.md) to build and test Conftest itself.
* See [CONTRIBUTING.md](CONTRIBUTING.md) to get started.
For discussions and questions join us on the [Open Policy Agent Slack](https://slack.openpolicyagent.org/)
in the `#opa-conftest` channel.

98
SECURITY_README.md Normal file
View File

@ -0,0 +1,98 @@
# Security as Code Infrastructure on Proxmox
This repository contains the Infrastructure as Code (IaC) for deploying a secure Kubernetes cluster on Proxmox using Terraform and Ansible with security policies embedded as code.
## Security Features
### Terraform Security
- **Secure VM Configuration**: Virtual machines are created with security best practices:
- Non-root SSH users
- Memory limits to prevent resource exhaustion
- Only approved template images are used
- Disk encryption available (currently commented)
- All VMs properly tagged for inventory management
- EFI disks with secure boot capability
- **Policy as Code**: Security policies are enforced through:
- **OPA/Conftest**: Rego policies that enforce security rules during Terraform plan/apply
- **Trivy**: Comprehensive security scanner for Infrastructure as Code, container images, and dependencies
- **checkov**: Plan analysis that detects security issues in the final configuration
### Ansible Security
- **SSH Hardening**:
- Disables password authentication
- Disables root login
- Configures secure ciphers and key exchange algorithms
- Limits user access
- **System Hardening**:
- Configures audit rules for comprehensive logging (auditd)
- Implements fail2ban for brute force protection
- Configures UFW firewall with restrictive rules
- Sets up unattended security updates (excluding Kubernetes components)
- Persistent journald logging
- Secure sysctl parameters
- **Kubernetes Security**:
- RBAC for access control
- Network Policies for cluster network security
- Pod Security Standards/Admission for container security
## CI/CD Security Pipeline
The `.gitlab-ci.yml` file defines a complete security pipeline:
1. **Validation Stage**:
- Terraform validation and formatting check
- Trivy security scanning for IaC misconfigurations and secrets
- checkov security scanning
- Conftest OPA policy validation
2. **Lint Stage**:
- Ansible-lint for playbook security issues
- Conftest checks for Ansible
3. **Plan/Apply Stage**:
- Terraform plan reviewed before application
- Ansible playbook syntax checking
4. **Test Stage**:
- Verifies that all nodes are properly configured
## Usage
1. Update Terraform variables in `terraform/terraform.tfvars`
2. Run security checks:
```
cd terraform
terraform init
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
conftest test tfplan.json -p policies/
```
3. Deploy infrastructure:
```
terraform apply tfplan
```
4. Configure nodes with Ansible:
```
cd ../ansible
ansible-playbook -i inventory/hosts.ini site.yml
```
## Policy Enforcement
- All infrastructure changes must pass the security pipeline
- Policy violations halt the deployment process
- Security configuration is maintained by Ansible on a regular schedule
## Monitoring and Maintenance
- Centralized logging configuration
- Audit logs setup for security events
- Automatic security updates
- Network security monitoring
For more details, see the [README.md](README.md) file for general infrastructure information.

14
ansible/ansible.cfg Normal file
View File

@ -0,0 +1,14 @@
[defaults]
nocows = True
roles_path = ./roles
inventory = ./k3s-cluster/hosts.ini
remote_tmp = $HOME/.ansible/tmp
local_tmp = $HOME/.ansible/tmp
pipelining = True
become = True
host_key_checking = False
deprecation_warnings = False
callback_whitelist = profile_tasks
callback_enabled = yes
display_failed_stderr = yes

View File

@ -0,0 +1,13 @@
k3s_version: v1.22.2+k3s1
ansible_user: debian
systemd_dir: /etc/systemd/system
master_ip: "{{ hostvars[groups['master'][0]]['ansible_host'] | default(groups['master'][0]) }}"
extra_server_args: "--write-kubeconfig-mode=644"
extra_agent_args: ""
copy_kubeconfig: true
metallb: false
metallb_version: "v0.14.9"
metallb_range: "192.168.30.93-192.168.30.94"
argocd: false
argocd_service_type: LoadBalancer
dns_servers: []

View File

@ -0,0 +1,13 @@
k3s_version: v1.32.4+k3s1
ansible_user: debian
systemd_dir: /etc/systemd/system
master_ip: "{{ hostvars[groups['master'][0]]['ansible_host'] | default(groups['master'][0]) }}"
extra_server_args: "--write-kubeconfig-mode=644"
extra_agent_args: ""
copy_kubeconfig: true
metallb: false
metallb_version: "v0.14.9"
metallb_range: "192.168.30.93-192.168.30.94"
argocd: false
argocd_service_type: LoadBalancer
dns_servers: []

View File

@ -0,0 +1,10 @@
[master]
192.168.30.81 ansible_ssh_private_key_file=~/.ssh/iac_proxmox
[node]
192.168.30.91 ansible_ssh_private_key_file=~/.ssh/iac_proxmox
192.168.30.92 ansible_ssh_private_key_file=~/.ssh/iac_proxmox
[k3s_cluster:children]
master
node

7
ansible/reset.yml Normal file
View File

@ -0,0 +1,7 @@
---
- hosts: k3s_cluster
gather_facts: true
become: true
roles:
- role: reset

View File

@ -0,0 +1,36 @@
---
- name: Download k3s binary x64
get_url:
url: https://github.com/k3s-io/k3s/releases/download/{{ k3s_version }}/k3s
checksum: sha256:https://github.com/k3s-io/k3s/releases/download/{{ k3s_version }}/sha256sum-amd64.txt
dest: /usr/local/bin/k3s
owner: root
group: root
mode: 0755
when: ansible_facts.architecture == "x86_64"
- name: Download k3s binary arm64
get_url:
url: https://github.com/k3s-io/k3s/releases/download/{{ k3s_version }}/k3s-arm64
checksum: sha256:https://github.com/k3s-io/k3s/releases/download/{{ k3s_version }}/sha256sum-arm64.txt
dest: /usr/local/bin/k3s
owner: root
group: root
mode: 0755
when:
- ( ansible_facts.architecture is search("arm") and
ansible_facts.userspace_bits == "64" ) or
ansible_facts.architecture is search("aarch64")
- name: Download k3s binary armhf
get_url:
url: https://github.com/k3s-io/k3s/releases/download/{{ k3s_version }}/k3s-armhf
checksum: sha256:https://github.com/k3s-io/k3s/releases/download/{{ k3s_version }}/sha256sum-arm.txt
dest: /usr/local/bin/k3s
owner: root
group: root
mode: 0755
when:
- ansible_facts.architecture is search("arm")
- ansible_facts.userspace_bits == "32"

View File

@ -0,0 +1,83 @@
---
- name: Copy K3s service file
register: k3s_service
template:
src: "k3s.service.j2"
dest: "{{ systemd_dir }}/k3s.service"
owner: root
group: root
mode: 0644
- name: Enable and check K3s service
systemd:
name: k3s
daemon_reload: true
state: restarted
enabled: true
- name: Wait for node-token
wait_for:
path: /var/lib/rancher/k3s/server/node-token
- name: Register node-token file access mode
stat:
path: /var/lib/rancher/k3s/server
register: p
- name: Change file access node-token
file:
path: /var/lib/rancher/k3s/server
mode: "g+rx,o+rx"
- name: Read node-token from master
slurp:
src: /var/lib/rancher/k3s/server/node-token
register: node_token
- name: Store Master node-token
set_fact:
token: "{{ node_token.content | b64decode | regex_replace('\n', '') }}"
- name: Restore node-token file access
file:
path: /var/lib/rancher/k3s/server
mode: "{{ p.stat.mode }}"
- name: Create directory .kube
file:
path: ~{{ ansible_user }}/.kube
state: directory
owner: "{{ ansible_user }}"
mode: "u=rwx,g=rx,o="
- name: Change k3s.yaml permissions to 644
file:
path: /etc/rancher/k3s/k3s.yaml
owner: "{{ ansible_user }}"
mode: "644"
- name: Replace https://localhost:6443 by https://master-ip:6443
command: >-
k3s kubectl config set-cluster default
--server=https://{{ master_ip }}:6443
--kubeconfig ~{{ ansible_user }}/.kube/config
changed_when: true
- name: Create kubectl symlink
file:
src: /usr/local/bin/k3s
dest: /usr/local/bin/kubectl
state: link
- name: Create crictl symlink
file:
src: /usr/local/bin/k3s
dest: /usr/local/bin/crictl
state: link
- name: copy config to local host
fetch:
src: /etc/rancher/k3s/k3s.yaml
dest: ~/.kube/config
flat: true
when: copy_kubeconfig

View File

@ -0,0 +1,34 @@
[Unit]
Description=Lightweight Kubernetes
Documentation=https://k3s.io
Wants=network-online.target
After=network-online.target
[Install]
WantedBy=multi-user.target
[Service]
Type=notify
EnvironmentFile=-/etc/default/%N
EnvironmentFile=-/etc/sysconfig/%N
EnvironmentFile=-/etc/systemd/system/k3s.service.env
KillMode=process
Delegate=yes
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
TimeoutStartSec=0
Restart=always
RestartSec=5s
ExecStartPre=/bin/sh -xc '! /usr/bin/systemctl is-enabled --quiet nm-cloud-setup.service'
ExecStartPre=-/sbin/modprobe br_netfilter
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/k3s \
server \
--write-kubeconfig-mode 644 \
{% if metallb is sameas true %}
--disable servicelb \
{% endif %}

View File

@ -0,0 +1,32 @@
[Unit]
Description=Lightweight Kubernetes
Documentation=https://k3s.io
Wants=network-online.target
After=network-online.target
[Install]
WantedBy=multi-user.target
[Service]
Type=notify
EnvironmentFile=-/etc/default/%N
EnvironmentFile=-/etc/sysconfig/%N
EnvironmentFile=-/etc/systemd/system/k3s.service.env
KillMode=process
Delegate=yes
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
TimeoutStartSec=0
Restart=always
RestartSec=5s
ExecStartPre=/bin/sh -xc '! /usr/bin/systemctl is-enabled --quiet nm-cloud-setup.service'
ExecStartPre=-/sbin/modprobe br_netfilter
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/k3s \
server \
--write-kubeconfig-mode 644 \
--disable traefik \

View File

@ -0,0 +1,16 @@
---
- name: Copy K3s service file
template:
src: "k3s.service.j2"
dest: "{{ systemd_dir }}/k3s-node.service"
owner: root
group: root
mode: 0755
- name: Enable and check K3s service
systemd:
name: k3s-node
daemon_reload: true
state: restarted
enabled: true

View File

@ -0,0 +1,24 @@
[Unit]
Description=Lightweight Kubernetes
Documentation=https://k3s.io
After=network-online.target
[Service]
Type=notify
ExecStartPre=-/sbin/modprobe br_netfilter
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/k3s agent --server https://{{ master_ip }}:6443 --token {{ hostvars[groups['master'][0]]['token'] }} {{ extra_agent_args | default("") }}
KillMode=process
Delegate=yes
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
TimeoutStartSec=0
Restart=always
RestartSec=5s
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,65 @@
---
- name: test kubeconfig path
stat:
path: ~/.kube/config
register: kubeconfig_path
- name: replace host ip address in the kubeconfig
replace:
path: ~/.kube/config
regexp: "https://127.0.0.1:6443"
replace: "https://{{ master_ip }}:6443"
when: kubeconfig_path and copy_kubeconfig
- name: Change k3s.yaml permissions to 644
file:
path: ~/.kube/config
mode: "600"
- name: check if helm is installed /usr/local/bin/helm
stat:
path: $HOME/.config/helm/repositories.yaml
register: helm_check
- name: Download get-helm-3
get_url:
url: https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
dest: ~/get-helm-3.sh
mode: "700"
when: not helm_check.stat.exists
- name: install helm if not exist
command: >-
~/get-helm-3.sh
when: not helm_check.stat.exists
changed_when: true
- name: Install metallb
shell: |
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/{{ metallb_version }}/manifests/namespace.yaml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/{{ metallb_version }}/manifests/metallb.yaml
when: metallb
- name: configure metallb range
shell: |
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- {{ metallb_range }}
when: metallb
- name: Install argocd
shell: |
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "{{ argocd_service_type }}"}}'
when: argocd

View File

@ -0,0 +1 @@
dns_servers: []

View File

@ -0,0 +1,65 @@
---
- name: Set SELinux to disabled state
selinux:
state: disabled
when: ansible_distribution in ['CentOS', 'Red Hat Enterprise Linux']
- name: Enable IPv4 forwarding
sysctl:
name: net.ipv4.ip_forward
value: "1"
state: present
reload: true
- name: Enable IPv6 forwarding
sysctl:
name: net.ipv6.conf.all.forwarding
value: "1"
state: present
reload: true
- name: fix dns servers in resolv.conf
template:
src: resolv.conf.j2
dest: /etc/resolv.conf
when:
- dns_servers | length() > 0
- name: Add br_netfilter to /etc/modules-load.d/
copy:
content: "br_netfilter"
dest: /etc/modules-load.d/br_netfilter.conf
mode: "u=rw,g=,o="
when: ansible_distribution in ['CentOS', 'Red Hat Enterprise Linux']
- name: Load br_netfilter
modprobe:
name: br_netfilter
state: present
when: ansible_distribution in ['CentOS', 'Red Hat Enterprise Linux']
- name: Set bridge-nf-call-iptables (just to be sure)
sysctl:
name: "{{ item }}"
value: "1"
state: present
reload: true
when: ansible_distribution in ['CentOS', 'Red Hat Enterprise Linux']
loop:
- net.bridge.bridge-nf-call-iptables
- net.bridge.bridge-nf-call-ip6tables
- name: Add /usr/local/bin to sudo secure_path
lineinfile:
line: "Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin"
regexp: "Defaults(\\s)*secure_path(\\s)*="
state: present
insertafter: EOF
path: /etc/sudoers
validate: "visudo -cf %s"
when: ansible_distribution in ['CentOS', 'Red Hat Enterprise Linux']
- name: install nfs-common on the servers
package:
name: nfs-common
state: present

View File

@ -0,0 +1,3 @@
{% for dns_server in dns_servers %}
nameserver {{ dns_server }}
{% endfor %}

View File

@ -0,0 +1,42 @@
---
- name: Disable services
systemd:
name: "{{ item }}"
state: stopped
enabled: false
failed_when: false
with_items:
- k3s
- k3s-node
- name: pkill -9 -f "k3s/data/[^/]+/bin/containerd-shim-runc"
register: pkill_containerd_shim_runc
command: pkill -9 -f "k3s/data/[^/]+/bin/containerd-shim-runc"
changed_when: "pkill_containerd_shim_runc.rc == 0"
failed_when: false
- name: Umount k3s filesystems
include_tasks: umount_with_children.yml
with_items:
- /run/k3s
- /var/lib/kubelet
- /run/netns
- /var/lib/rancher/k3s
loop_control:
loop_var: mounted_fs
- name: Remove service files, binaries and data
file:
name: "{{ item }}"
state: absent
with_items:
- /usr/local/bin/k3s
- "{{ systemd_dir }}/k3s.service"
- "{{ systemd_dir }}/k3s-node.service"
- /etc/rancher/k3s
- /var/lib/kubelet
- /var/lib/rancher/k3s
- name: daemon_reload
systemd:
daemon_reload: true

View File

@ -0,0 +1,16 @@
---
- name: Get the list of mounted filesystems
shell: set -o pipefail && cat /proc/mounts | awk '{ print $2}' | grep -E "^{{ mounted_fs }}"
register: get_mounted_filesystems
args:
executable: /bin/bash
failed_when: false
changed_when: get_mounted_filesystems.stdout | length > 0
check_mode: false
- name: Umount filesystem
mount:
path: "{{ item }}"
state: unmounted
with_items:
"{{ get_mounted_filesystems.stdout_lines | reverse | list }}"

22
ansible/site.yml Normal file
View File

@ -0,0 +1,22 @@
---
- hosts: k3s_cluster
gather_facts: true
become: true
roles:
- prereq
- download
- hosts: master
become: true
roles:
- k3s/master
- hosts: node
become: true
roles:
- k3s/node
- hosts: localhost
connection: local
roles:
- postconfig/localhost

15
terraform/LICENSE Normal file
View File

@ -0,0 +1,15 @@
Conftest - Write tests against your config files
Copyright (C) 2019 Gareth Rushgrove
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,66 +1,52 @@
# Terraform Security as Code
# Conftest
This directory contains Terraform configurations for creating a Kubernetes cluster on Proxmox VMs. Security is implemented as code through policy checks.
[![Go Report Card](https://goreportcard.com/badge/open-policy-agent/opa)](https://goreportcard.com/report/open-policy-agent/conftest) [![Netlify](https://api.netlify.com/api/v1/badges/2d928746-3380-4123-b0eb-1fd74ba390db/deploy-status)](https://app.netlify.com/sites/vibrant-villani-65041c/deploys)
## Security Policies
Conftest helps you write tests against structured configuration data. Using Conftest you can
write tests for your Kubernetes configuration, Tekton pipeline definitions, Terraform code,
Serverless configs or any other config files.
Security policies are defined as Open Policy Agent (OPA) Rego files in the `policy/` directory:
Conftest uses the Rego language from [Open Policy Agent](https://www.openpolicyagent.org/) for writing
the assertions. You can read more about Rego in [How do I write policies](https://www.openpolicyagent.org/docs/how-do-i-write-policies.html)
in the Open Policy Agent documentation.
- **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)
Here's a quick example. Save the following as `policy/deployment.rego`:
## Running Security Checks
```rego
package main
### Prerequisites
deny[msg] {
input.kind == "Deployment"
not input.spec.template.spec.securityContext.runAsNonRoot
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
```
msg := "Containers must not run as root"
}
2. Install Trivy:
```bash
# For Debian/Ubuntu
sudo apt-get install trivy
deny[msg] {
input.kind == "Deployment"
not input.spec.selector.matchLabels.app
# For other systems, see: https://aquasecurity.github.io/trivy/latest/getting-started/installation/
```
msg := "Containers must provide app label for pod selectors"
}
```
3. Install Checkov:
```bash
pip install checkov
```
Assuming you have a Kubernetes deployment in `deployment.yaml` you can run Conftest like so:
### Running Policy Checks
```console
$ conftest test deployment.yaml
FAIL - deployment.yaml - Containers must not run as root
FAIL - deployment.yaml - Containers must provide app label for pod selectors
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 tests, 0 passed, 0 warnings, 2 failures, 0 exceptions
```
2. Run Conftest with OPA policies:
```bash
conftest test tfplan.json -p policy/
```
Conftest isn't specific to Kubernetes. It will happily let you write tests for any configuration files in a variety of different formats. See the [documentation](https://www.conftest.dev/) for [installation instructions](https://www.conftest.dev/install/) and
more details about the features.
3. Run Trivy IaC security scan:
```bash
# Skip AWS policies and use variables file
trivy config --severity HIGH,CRITICAL --skip-policy "aws.*" --tf-vars="variables.tfvars" .
```
## Want to contribute to Conftest?
4. Run Checkov:
```
* See [DEVELOPMENT.md](DEVELOPMENT.md) to build and test Conftest itself.
* See [CONTRIBUTING.md](CONTRIBUTING.md) to get started.
For discussions and questions join us on the [Open Policy Agent Slack](https://slack.openpolicyagent.org/)
in the `#opa-conftest` channel.

View File

@ -17,8 +17,8 @@ resource "proxmox_vm_qemu" "proxmox_vm_master" {
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 = ""
ciuser = "root"
cipassword = "test_passwd"
sshkeys = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKXXnm9Hl4fPCt/Xjd/8E5tKY+edtM/BvdMOXpx40oWG iac@proxmox.vadzik-iot.ru"
# Most cloud-init images require a serial device for their display

View File

@ -7,7 +7,7 @@ import input
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) {
is_empty(value) if {
value == ""
} {
value == null
@ -19,28 +19,28 @@ is_empty(value) {
min_memory = 512
# Deny if VM allows password authentication
deny[msg] {
deny contains msg if {
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] {
deny contains msg if {
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] {
deny contains msg if {
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] {
deny contains msg if {
some vm in vms
net := vm.values.network[_]
net.bridge != "vmbr2"
@ -48,28 +48,28 @@ deny[msg] {
}
# Deny if IPv6 is not disabled
deny[msg] {
deny contains msg if {
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] {
deny contains msg if {
tls_enabled := input.variables.pm_tls_insecure.value
tls_enabled == true
msg := "TLS verification must be enabled (pm_tls_insecure = false)"
}
# Deny if provider version is not pinned
deny[msg] {
deny contains msg if {
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] {
deny contains msg if {
some vm in vms
memory := to_number(vm.values.memory)
memory < min_memory
@ -77,20 +77,20 @@ deny[msg] {
}
# Deny if VM does not have a description
deny[msg] {
deny contains msg if {
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] {
deny contains msg if {
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])
}
# Test rule to verify policy is loaded
test_policy_loaded {
test_policy_loaded if {
true
}

View File

@ -109,47 +109,46 @@ mock_input_insecure := {
}
# Test secure configuration passes
test_secure_config {
input := mock_input_secure
count(deny) == 0
test_secure_config if {
count(deny) == 0 with input as mock_input_secure
}
# Test password authentication
test_password_auth {
test_password_auth if {
deny["VM 'insecure_vm' uses password authentication. Use SSH keys only."] with input as mock_input_insecure
}
# Test qemu agent
test_qemu_agent {
test_qemu_agent if {
deny["VM 'insecure_vm' does not have qemu-agent enabled (agent = 1)."] with input as mock_input_insecure
}
# Test network bridge
test_network_bridge {
test_network_bridge if {
deny["VM 'insecure_vm' uses insecure network bridge 'vmbr0'. Use 'vmbr2'."] with input as mock_input_insecure
}
# Test TLS verification
test_tls_verification {
test_tls_verification if {
deny["TLS verification must be enabled (pm_tls_insecure = false)"] with input as mock_input_insecure
}
# Test provider version pinning
test_provider_version {
test_provider_version if {
deny["Provider version must be pinned with '=' constraint"] with input as mock_input_insecure
}
# Test minimum memory requirement
test_minimum_memory {
test_minimum_memory if {
deny["VM 'insecure_vm' has insufficient memory (256MB). Minimum required: 512MB."] with input as mock_input_insecure
}
# Test VM description requirement
test_vm_description {
test_vm_description if {
deny["VM 'insecure_vm' must have a description for documentation purposes."] with input as mock_input_insecure
}
# Test SCSI controller requirement
test_scsi_controller {
test_scsi_controller if {
deny["VM 'insecure_vm' uses default SCSI controller. Use virtio-scsi-pci for better performance."] with input as mock_input_insecure
}

View File

@ -1,47 +0,0 @@
{
"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

@ -3,8 +3,6 @@
# 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'
@ -65,7 +63,7 @@ terraform show -json tfplan | jq > tfplan.json
echo -e "\n${YELLOW}Running OPA policy checks...${NC}"
if [ -d "policy" ]; then
conftest test tfplan.json -p policy/
conftest test -p policy/ tfplan.json
CONFTEST_EXIT=$?
if [ $CONFTEST_EXIT -eq 0 ]; then
echo -e "${GREEN}✅ OPA policy checks passed.${NC}"