Initial commit

This commit is contained in:
2025-11-12 20:48:28 +01:00
commit 0b6eea6113
7 changed files with 399 additions and 0 deletions

337
playbooks/security.yml Normal file
View File

@@ -0,0 +1,337 @@
---
- name: Security Hardening
hosts: arch-vps
become: yes
gather_facts: yes
tasks:
# ============================================
# System Updates and Package Security
# ============================================
- name: Update package database
pacman:
update_cache: yes
- name: Upgrade all packages to latest versions
pacman:
upgrade: yes
register: package_upgrade
- name: Display upgrade results
debug:
msg: "{{ package_upgrade.packages | length }} packages were upgraded"
when: package_upgrade.packages is defined
# ============================================
# Kernel/Module Version Check and Reboot
# ============================================
- name: Get current running kernel version
command: uname -r
register: current_kernel
changed_when: false
- name: Get latest available kernel modules version
shell: ls /lib/modules/ | sort -V | tail -1
register: latest_modules
changed_when: false
- name: Display kernel versions for debugging
debug:
msg:
- "Running kernel: {{ current_kernel.stdout }}"
- "Latest modules: {{ latest_modules.stdout }}"
- name: Test if nftables modules are available
command: nft list ruleset
register: nft_test_prereq
failed_when: false
changed_when: false
- name: Determine if reboot is needed
set_fact:
reboot_needed: "{{ current_kernel.stdout != latest_modules.stdout or nft_test_prereq.rc != 0 }}"
- name: Reboot system if kernel/module mismatch detected
reboot:
reboot_timeout: 60
test_command: uptime
when: reboot_needed | bool
- name: Wait for system to be fully ready after reboot
wait_for_connection:
delay: 15
timeout: 300
when: reboot_needed | bool
- name: Verify nftables is now available after reboot
command: nft list ruleset
register: nft_post_reboot
failed_when: false
changed_when: false
- name: Display post-reboot nftables status
debug:
msg: "nftables availability after reboot: {{ 'Working' if nft_post_reboot.rc == 0 else 'Failed' }}"
# ============================================
# SSH Hardening
# ============================================
- name: Harden SSH configuration
lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
backup: no
loop:
- { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication no' }
- { regexp: '^#?PermitRootLogin', line: 'PermitRootLogin prohibit-password' }
- { regexp: '^#?PubkeyAuthentication', line: 'PubkeyAuthentication yes' }
- { regexp: '^#?AuthorizedKeysFile', line: 'AuthorizedKeysFile .ssh/authorized_keys' }
- { regexp: '^#?PermitEmptyPasswords', line: 'PermitEmptyPasswords no' }
- { regexp: '^#?ChallengeResponseAuthentication', line: 'ChallengeResponseAuthentication no' }
- { regexp: '^#?UsePAM', line: 'UsePAM no' }
- { regexp: '^#?X11Forwarding', line: 'X11Forwarding no' }
- { regexp: '^#?MaxAuthTries', line: 'MaxAuthTries 3' }
- { regexp: '^#?ClientAliveInterval', line: 'ClientAliveInterval 300' }
- { regexp: '^#?ClientAliveCountMax', line: 'ClientAliveCountMax 2' }
register: ssh_config_changed
- name: Test SSH configuration syntax
command: sshd -t
changed_when: false
failed_when: false
register: ssh_test
- name: Fail if SSH configuration is invalid
fail:
msg: "SSH configuration test failed: {{ ssh_test.stderr }}"
when: ssh_test.rc != 0
- name: Restart SSH service if configuration changed
systemd:
name: sshd
state: restarted
when: ssh_config_changed.changed
- name: Wait for SSH service to be available
wait_for:
port: 22
host: "{{ ansible_host }}"
delay: 2
timeout: 30
delegate_to: localhost
become: no
when: ssh_config_changed.changed
- name: Test SSH connection with new configuration
ping:
when: ssh_config_changed.changed
# ============================================
# nftables Firewall Configuration
# ============================================
- name: Install nftables
pacman:
name: nftables
state: present
- name: Create nftables configuration
copy:
content: |
#!/usr/sbin/nft -f
# Main firewall table
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Allow loopback interface
iif "lo" accept
# Allow established and related connections
ct state established,related accept
# Allow SSH (port 22)
tcp dport 22 ct state new accept
# Allow HTTP and HTTPS for Caddy reverse proxy
tcp dport { 80, 443 } ct state new accept
# Allow ping with rate limiting
icmp type echo-request limit rate 1/second accept
icmpv6 type echo-request limit rate 1/second accept
# Log and drop everything else
counter drop
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
dest: /etc/nftables.conf
mode: '0755'
backup: yes
register: nft_config_changed
- name: Test nftables configuration syntax
command: nft -c -f /etc/nftables.conf
changed_when: false
failed_when: false
register: nft_test
- name: Fail if nftables configuration is invalid
fail:
msg: "nftables configuration test failed: {{ nft_test.stderr }}"
when: nft_test.rc != 0
- name: Flush existing nftables rules before applying new configuration
command: nft flush ruleset
failed_when: false
changed_when: false
when: nft_config_changed.changed
- name: Create firewall rollback safety script
copy:
content: |
#!/bin/bash
# Safety rollback script - automatically disables firewall after 5 minutes
echo "$(date): Starting 5-minute firewall safety timer"
sleep 300
echo "$(date): Safety timer expired, disabling firewall"
nft flush ruleset
systemctl stop nftables
rm -f /tmp/nft-rollback.sh
dest: /tmp/nft-rollback.sh
mode: '0755'
when: nft_config_changed.changed
- name: Start rollback safety timer in background
shell: nohup /tmp/nft-rollback.sh >> /tmp/nft-rollback.log 2>&1 &
when: nft_config_changed.changed
- name: Enable and start nftables service
systemd:
name: nftables
enabled: yes
state: restarted
when: nft_config_changed.changed
- name: Wait for nftables to be active
pause:
seconds: 3
when: nft_config_changed.changed
- name: Test SSH connection after firewall activation
wait_for:
port: 22
host: "{{ ansible_host }}"
timeout: 15
delegate_to: localhost
become: no
when: nft_config_changed.changed
- name: Cancel rollback timer if SSH connection works
shell: pkill -f nft-rollback.sh || true
when: nft_config_changed.changed
- name: Verify nftables rules are loaded
command: nft list ruleset
register: nft_rules
changed_when: false
- name: Display active firewall rules
debug:
var: nft_rules.stdout_lines
# ============================================
# Fail2ban Setup
# ============================================
- name: Install fail2ban
pacman:
name: fail2ban
state: present
- name: Create fail2ban local configuration
copy:
content: |
[DEFAULT]
# Ban hosts for 1 hour (3600 seconds)
bantime = 3600
banaction = nftables
banaction_allports = nftables[type=allports]
# A host is banned if it has generated "maxretry" failures during "findtime"
findtime = 600
maxretry = 3
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
dest: /etc/fail2ban/jail.local
backup: yes
notify: restart fail2ban
- name: Enable and start fail2ban service
systemd:
name: fail2ban
enabled: yes
state: started
# ============================================
# Kernel Network Hardening
# ============================================
- name: Apply kernel network security settings
sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
state: present
sysctl_file: /etc/sysctl.d/99-security.conf
reload: yes
loop:
# Disable IP forwarding
- { name: 'net.ipv4.ip_forward', value: '0' }
- { name: 'net.ipv6.conf.all.forwarding', value: '0' }
# Disable source routing
- { name: 'net.ipv4.conf.all.accept_source_route', value: '0' }
- { name: 'net.ipv6.conf.all.accept_source_route', value: '0' }
# Disable ICMP redirects
- { name: 'net.ipv4.conf.all.accept_redirects', value: '0' }
- { name: 'net.ipv6.conf.all.accept_redirects', value: '0' }
- { name: 'net.ipv4.conf.all.send_redirects', value: '0' }
# Enable syn flood protection
- { name: 'net.ipv4.tcp_syncookies', value: '1' }
# Ignore ping requests
- { name: 'net.ipv4.icmp_echo_ignore_all', value: '0' }
# Log suspicious packets
- { name: 'net.ipv4.conf.all.log_martians', value: '1' }
# Disable IPv6 if not needed
- { name: 'net.ipv6.conf.all.disable_ipv6', value: '0' }
- { name: 'net.ipv6.conf.default.disable_ipv6', value: '0' }
handlers:
- name: restart fail2ban
systemd:
name: fail2ban
state: restarted