[Add] Updated conftest to latest version, policies updated, precommit hook also updated
This commit is contained in:
parent
8a0ae2e8fb
commit
5f39f381c1
115
.gitlab-ci.yml
Normal file
115
.gitlab-ci.yml
Normal 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
52
README.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Conftest
|
||||
|
||||
[](https://goreportcard.com/report/open-policy-agent/conftest) [](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
98
SECURITY_README.md
Normal 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
14
ansible/ansible.cfg
Normal 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
|
||||
13
ansible/inventory/group_vars/all.yml
Normal file
13
ansible/inventory/group_vars/all.yml
Normal 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: []
|
||||
13
ansible/inventory/k3s-cluster/group_vars/all.yml
Executable file
13
ansible/inventory/k3s-cluster/group_vars/all.yml
Executable 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: []
|
||||
10
ansible/inventory/k3s-cluster/hosts.ini
Executable file
10
ansible/inventory/k3s-cluster/hosts.ini
Executable 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
7
ansible/reset.yml
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
|
||||
- hosts: k3s_cluster
|
||||
gather_facts: true
|
||||
become: true
|
||||
roles:
|
||||
- role: reset
|
||||
36
ansible/roles/download/tasks/main.yml
Normal file
36
ansible/roles/download/tasks/main.yml
Normal 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"
|
||||
83
ansible/roles/k3s/master/tasks/main.yml
Normal file
83
ansible/roles/k3s/master/tasks/main.yml
Normal 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
|
||||
34
ansible/roles/k3s/master/templates/k3s.service.j2
Normal file
34
ansible/roles/k3s/master/templates/k3s.service.j2
Normal 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 %}
|
||||
@ -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 \
|
||||
16
ansible/roles/k3s/node/tasks/main.yml
Normal file
16
ansible/roles/k3s/node/tasks/main.yml
Normal 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
|
||||
24
ansible/roles/k3s/node/templates/k3s.service.j2
Normal file
24
ansible/roles/k3s/node/templates/k3s.service.j2
Normal 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
|
||||
65
ansible/roles/postconfig/localhost/tasks/main.yml
Normal file
65
ansible/roles/postconfig/localhost/tasks/main.yml
Normal 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
|
||||
1
ansible/roles/prereq/defaults/main.yml
Normal file
1
ansible/roles/prereq/defaults/main.yml
Normal file
@ -0,0 +1 @@
|
||||
dns_servers: []
|
||||
65
ansible/roles/prereq/tasks/main.yml
Normal file
65
ansible/roles/prereq/tasks/main.yml
Normal 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
|
||||
3
ansible/roles/prereq/templates/resolv.conf.j2
Normal file
3
ansible/roles/prereq/templates/resolv.conf.j2
Normal file
@ -0,0 +1,3 @@
|
||||
{% for dns_server in dns_servers %}
|
||||
nameserver {{ dns_server }}
|
||||
{% endfor %}
|
||||
42
ansible/roles/reset/tasks/main.yml
Normal file
42
ansible/roles/reset/tasks/main.yml
Normal 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
|
||||
16
ansible/roles/reset/tasks/umount_with_children.yml
Normal file
16
ansible/roles/reset/tasks/umount_with_children.yml
Normal 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
22
ansible/site.yml
Normal 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
15
terraform/LICENSE
Normal 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.
|
||||
@ -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.
|
||||
[](https://goreportcard.com/report/open-policy-agent/conftest) [](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
|
||||
msg := "Containers must not run as root"
|
||||
}
|
||||
|
||||
# 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
|
||||
deny[msg] {
|
||||
input.kind == "Deployment"
|
||||
not input.spec.selector.matchLabels.app
|
||||
|
||||
msg := "Containers must provide app label for pod selectors"
|
||||
}
|
||||
```
|
||||
|
||||
2. Install Trivy:
|
||||
```bash
|
||||
# For Debian/Ubuntu
|
||||
sudo apt-get install trivy
|
||||
Assuming you have a Kubernetes deployment in `deployment.yaml` you can run Conftest like so:
|
||||
|
||||
# For other systems, see: https://aquasecurity.github.io/trivy/latest/getting-started/installation/
|
||||
```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
|
||||
```
|
||||
|
||||
3. Install Checkov:
|
||||
```bash
|
||||
pip install checkov
|
||||
```
|
||||
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.
|
||||
|
||||
### Running Policy Checks
|
||||
## Want to contribute to Conftest?
|
||||
|
||||
1. Generate a Terraform plan and convert to JSON:
|
||||
```bash
|
||||
cd terraform
|
||||
terraform init
|
||||
terraform plan -out=tfplan
|
||||
terraform show -json tfplan > tfplan.json
|
||||
```
|
||||
* See [DEVELOPMENT.md](DEVELOPMENT.md) to build and test Conftest itself.
|
||||
* See [CONTRIBUTING.md](CONTRIBUTING.md) to get started.
|
||||
|
||||
2. Run Conftest with OPA policies:
|
||||
```bash
|
||||
conftest test tfplan.json -p policy/
|
||||
```
|
||||
|
||||
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" .
|
||||
```
|
||||
|
||||
4. Run Checkov:
|
||||
```
|
||||
For discussions and questions join us on the [Open Policy Agent Slack](https://slack.openpolicyagent.org/)
|
||||
in the `#opa-conftest` channel.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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}"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user