Security Hardening
This example sets up firewall rules, fail2ban, OpenSSH hardening, and sudo configuration using multiple modules with depends_on ordering.
Firewall Module
stockpile/modules/firewall.vgo:
name: firewall
vars:
ssh_port: "22"
extra_allow: []
resources:
- name: ufw-package
type: package
package: ufw
state: present
when: "os_family('debian')"
- name: firewall-package
type: package
package: firewalld
state: present
when: "!os_family('debian')"
- name: ufw-default-deny
type: exec
command: "ufw default deny incoming"
onlyif: "! ufw status verbose | grep -q 'Default: deny (incoming)'"
when: "os_family('debian')"
- name: ufw-enable
type: exec
command: 'echo "y" | ufw enable'
onlyif: "ufw status | grep -q inactive"
when: "os_family('debian')"
depends_on: [ufw-default-deny]
- name: allow-ssh
type: firewall
port: "{{ .Vars.ssh_port }}"
proto: tcp
action: allow
comment: SSH access
depends_on: [ufw-enable]
# Additional ports from common.vgo vars (foreach expansion)
- name: "allow-{{ .Each.name }}"
type: firewall
foreach: extra_allow
port: "{{ .Each.port }}"
proto: "{{ .Each.proto }}"
action: allow
comment: "{{ .Each.comment }}"
depends_on: [ufw-enable]
Override extra_allow in common.vgo to open additional ports without editing the module:
# common.vgo
vars:
extra_allow:
- name: http
port: "80"
proto: tcp
comment: HTTP
- name: https
port: "443"
proto: tcp
comment: HTTPS
SSH Hardening Module
stockpile/modules/ssh-hardening.vgo:
name: ssh-hardening
depends_on:
- firewall
vars:
ssh_port: "22"
ssh_permit_root: "no"
ssh_password_auth: "no"
ssh_max_auth_tries: "3"
resources:
- name: sshd-config
type: file
target_path: /etc/ssh/sshd_config.d/99-hardening.conf
owner: root
group: root
mode: "0644"
content: |
Port {{ .Vars.ssh_port }}
PermitRootLogin {{ .Vars.ssh_permit_root }}
PasswordAuthentication {{ .Vars.ssh_password_auth }}
MaxAuthTries {{ .Vars.ssh_max_auth_tries }}
X11Forwarding no
AllowTcpForwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
notify:
- sshd-service
- name: sshd-service
type: service
service: sshd
state: running
enabled: true
Fail2ban Module
stockpile/modules/fail2ban.vgo:
name: fail2ban
depends_on:
- ssh-hardening
vars:
fail2ban_bantime: "3600"
fail2ban_maxretry: "5"
resources:
- name: fail2ban-package
type: package
package: fail2ban
state: present
- name: fail2ban-jail-config
type: file
target_path: /etc/fail2ban/jail.local
owner: root
group: root
mode: "0644"
content: |
[DEFAULT]
bantime = {{ .Vars.fail2ban_bantime }}
maxretry = {{ .Vars.fail2ban_maxretry }}
backend = systemd
[sshd]
enabled = true
port = {{ .Vars.ssh_port }}
notify:
- fail2ban-service
- name: fail2ban-service
type: service
service: fail2ban
state: running
enabled: true
Sudo Module
stockpile/modules/sudo-config.vgo:
name: sudo-config
vars:
sudo_group: admin
resources:
- name: sudo-package
type: package
package: sudo
state: present
- name: sudoers-config
type: file
target_path: /etc/sudoers.d/vigo-managed
owner: root
group: root
mode: "0440"
content: |
# Managed by Vigo - do not edit manually
%{{ .Vars.sudo_group }} ALL=(ALL) NOPASSWD: ALL
Defaults env_reset
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Defaults logfile="/var/log/sudo.log"
Role Definition
stockpile/roles/hardened.vgo:
name: hardened
modules:
- firewall
- ssh-hardening
- fail2ban
- sudo-config
Node Assignment
stockpile/envoys/nodes.vgo:
envoys:
- match: "*.example.com"
roles: [hardened]
vars:
ssh_port: "2222"
fail2ban_bantime: "86400"
sudo_group: ops
Execution Order
The depends_on declarations create a DAG:
- firewall -- applied first (no dependencies)
- ssh-hardening -- depends on firewall
- fail2ban -- depends on ssh-hardening
- sudo-config -- no dependencies, can run in parallel with firewall