Restructure into one folder per private repo/host, not a flat Docker/ dump

rocky-docker-lxc/, caddy-vm/, monitoring-vm/ (was missing entirely -
Grafana+Loki+Prometheus stack, refreshed and CA mount stripped same as
everything else), and automation/{ansible,automation-vm,iac}/ for the
three repos that live on the automation VM. Hawser is genuinely
deployed on all three of caddy-vm/monitoring-vm/automation-vm so it's
duplicated in each rather than picking one canonical home.

Old Ansible/ and "Infrastrucutre as Code/" content moved under
automation/ansible/old-playbooks and automation/iac/old-content as-is
for now - contents still need refreshing against the current repos and
a per-folder README, that's the next pass.
This commit is contained in:
thesabear committed 2026-07-06 02:41:53 +03:00
1 parent 08f705ffd7
commit c05c7164c8
146 files changed
+101 -84

No files matched your search

@@ -0,0 +1,11 @@
---
cis_banner: |
*********************************************************************
* WARNING: Unauthorized access to this system is prohibited. *
* All activities are monitored and recorded. *
*********************************************************************
cis_motd_banner: |
*********************************************************************
* Welcome to the system. Access is restricted to authorized users. *
*********************************************************************
@@ -0,0 +1,26 @@
---
# Login banners for /etc/motd, /etc/issue, /etc/issue.net
- name: Ensure /etc/motd contains standard CIS banner
ansible.builtin.copy:
dest: /etc/motd
owner: root
group: root
mode: "0644"
content: "{{ cis_motd_banner }}"
- name: Ensure /etc/issue contains standard CIS banner
ansible.builtin.copy:
dest: /etc/issue
owner: root
group: root
mode: "0644"
content: "{{ cis_motd_banner }}"
- name: Ensure /etc/issue.net contains standard CIS banner
ansible.builtin.copy:
dest: /etc/issue.net
owner: root
group: root
mode: "0644"
content: "{{ cis_banner }}"
@@ -0,0 +1,6 @@
---
# Parent-level defaults
cis_tag: cis_hardening
audit_rules_file: /etc/audit/rules.d/cis.rules
rsyslog_remote: "" # set to "logserver.example.com:514" to forward logs
txt_grub_args: "" # additional kernel args if needed, e.g. "audit=1"
@@ -0,0 +1,16 @@
---
# Default ownership and permissions for critical files and directories
file_permissions_list:
- { path: "/root", mode: "0700", state: "directory" }
- { path: "/etc/passwd", mode: "0644", state: "file" }
- { path: "/etc/shadow", mode: "0400", state: "file" }
- { path: "/etc/group", mode: "0644", state: "file" }
- { path: "/etc/gshadow", mode: "0400", state: "file" }
- { path: "/etc/crontab", mode: "0600", state: "file" }
- { path: "/etc/cron.hourly", mode: "0700", state: "directory" }
- { path: "/etc/cron.daily", mode: "0700", state: "directory" }
- { path: "/etc/cron.weekly", mode: "0700", state: "directory" }
- { path: "/etc/cron.monthly", mode: "0700", state: "directory" }
- { path: "/etc/cron.d", mode: "0700", state: "directory" }
- { path: "/etc/rsyslog.conf", mode: "0640", state: "file" }
@@ -0,0 +1,11 @@
---
- name: Ensure system files and directories have correct ownership and permissions
ansible.builtin.file:
path: "{{ file_item.path }}"
owner: root
group: root
mode: "{{ file_item.mode }}"
state: "{{ file_item.state }}"
loop: "{{ file_permissions_list }}"
loop_control:
loop_var: file_item
@@ -0,0 +1,4 @@
tmp_mount_options: "defaults,nosuid,nodev,noexec,relatime"
var_tmp_mount_options: "defaults,nosuid,nodev,noexec,relatime"
home_mount_options: "defaults,nosuid,nodev,relatime"
dev_shm_mount_options: "defaults,nosuid,nodev,noexec,relatime"
@@ -0,0 +1,34 @@
---
# Remount and persist secure options using defaults variables
- name: Gather mount facts
ansible.builtin.setup:
filter: ansible_mounts
- name: Ensure /tmp has secure mount options
ansible.builtin.mount:
path: /tmp
fstype: "{{ ansible_mounts | selectattr('mount','equalto','/tmp') | map(attribute='fstype') | first }}"
opts: "{{ tmp_mount_options }}"
state: mounted
- name: Ensure /var/tmp has secure mount options
ansible.builtin.mount:
path: /var/tmp
fstype: "{{ ansible_mounts | selectattr('mount','equalto','/var/tmp') | map(attribute='fstype') | first }}"
opts: "{{ var_tmp_mount_options }}"
state: mounted
- name: Ensure /home has secure mount options
ansible.builtin.mount:
path: /home
fstype: "{{ ansible_mounts | selectattr('mount','equalto','/home') | map(attribute='fstype') | first }}"
opts: "{{ home_mount_options }}"
state: mounted
- name: Ensure /dev/shm has secure mount options
ansible.builtin.mount:
path: /dev/shm
fstype: tmpfs
opts: "{{ dev_shm_mount_options }}"
state: mounted
@@ -0,0 +1,17 @@
---
- name: restart rsyslog
ansible.builtin.service:
name: rsyslog
state: restarted
- name: restart auditd
ansible.builtin.service:
name: auditd
state: restarted
- name: reload systemd
ansible.builtin.command: systemctl daemon-reexec
- name: update initramfs
ansible.builtin.command: dracut -f
when: ansible_facts['os_family'] == 'RedHat' or ansible_facts['distribution'] in ['Rocky','RedHat','CentOS']
@@ -0,0 +1,7 @@
---
# Kernel defaults (override per host if needed)
modprobe_blacklist_file: /etc/modprobe.d/cis_blacklist.conf
# Additional kernel modules to disable
blacklist_modules:
- tipc
@@ -0,0 +1,18 @@
# blacklist_module.yml
- name: Check if module {{ mod }} exists on system
ansible.builtin.shell: "modprobe -n -v {{ mod }}"
register: modprobe_check
ignore_errors: true
- name: Blacklist module {{ mod }} in {{ modprobe_blacklist_file }}
ansible.builtin.lineinfile:
path: "{{ modprobe_blacklist_file }}"
line: "install {{ mod }} /bin/false"
create: yes
state: present
when: modprobe_check.rc == 0
- name: Unload module {{ mod }} if currently loaded
ansible.builtin.shell: "modprobe -r {{ mod }}"
ignore_errors: true
when: modprobe_check.rc == 0
@@ -0,0 +1,78 @@
---
# Kernel & sysctl hardening (CIS network/kernel items)
- name: Ensure IPv4 forwarding is disabled
ansible.builtin.sysctl:
name: net.ipv4.ip_forward
value: "0"
state: present
reload: yes
- name: Ensure IPv6 forwarding is disabled
ansible.builtin.sysctl:
name: net.ipv6.conf.all.forwarding
value: "0"
state: present
reload: yes
- name: Ensure source routed packets are not accepted
ansible.builtin.sysctl:
name: net.ipv4.conf.all.accept_source_route
value: "0"
state: present
reload: yes
- name: Ensure ICMP redirects are not accepted
ansible.builtin.sysctl:
name: net.ipv4.conf.all.accept_redirects
value: "0"
state: present
reload: yes
- name: Ensure secure ICMP redirects are not accepted
ansible.builtin.sysctl:
name: net.ipv4.conf.all.secure_redirects
value: "0"
state: present
reload: yes
- name: Ensure Address Space Layout Randomization (ASLR) is enabled
ansible.builtin.sysctl:
name: kernel.randomize_va_space
value: "2"
state: present
reload: yes
- name: Ensure fs.suid_dumpable is 0 (disable core dumps)
ansible.builtin.sysctl:
name: fs.suid_dumpable
value: "0"
state: present
reload: yes
- name: Create /etc/modprobe.d/cis_blacklist.conf with common blacklists
ansible.builtin.copy:
dest: /etc/modprobe.d/cis_blacklist.conf
owner: root
group: root
mode: "0644"
content: |
# CIS blacklist
install cramfs /bin/true
install freevxfs /bin/true
install jffs2 /bin/true
install hfs /bin/true
install hfsplus /bin/true
install squashfs /bin/true
install udf /bin/true
- name: Regenerate initramfs if modprobe blacklist changed
ansible.builtin.command: dracut -f
changed_when: false
when: ansible_facts.get('os_family') == 'RedHat' or ansible_facts.get('distribution') in ['Rocky', 'RedHat', 'CentOS']
- name: Blacklist all modules
include_tasks: blacklist_module.yml
loop: "{{ blacklist_modules }}"
loop_control:
loop_var: mod
@@ -0,0 +1,9 @@
---
# Password policy defaults
pw_minlen: 14
pw_minclass: 4
pw_max_days: 365
pw_min_days: 7
pw_warn_age: 7
faillock_deny: 5
faillock_unlock_time: 900
@@ -0,0 +1,60 @@
---
# Password and PAM related hardening
- name: Ensure password hashing algorithm is SHA512
ansible.builtin.lineinfile:
path: /etc/login.defs
regexp: "^ENCRYPT_METHOD"
line: "ENCRYPT_METHOD SHA512"
create: yes
- name: Ensure PASS_MAX_DAYS is 365
ansible.builtin.lineinfile:
path: /etc/login.defs
regexp: "^PASS_MAX_DAYS"
line: "PASS_MAX_DAYS 365"
create: yes
- name: Ensure PASS_MIN_DAYS is 7
ansible.builtin.lineinfile:
path: /etc/login.defs
regexp: "^PASS_MIN_DAYS"
line: "PASS_MIN_DAYS 7"
create: yes
- name: Ensure PASS_WARN_AGE is 7
ansible.builtin.lineinfile:
path: /etc/login.defs
regexp: "^PASS_WARN_AGE"
line: "PASS_WARN_AGE 7"
create: yes
- name: Ensure pwquality minlen and requirements
ansible.builtin.lineinfile:
path: /etc/security/pwquality.conf
regexp: "^minlen"
line: "minlen = 14"
create: yes
- name: Ensure pwquality minclass (character classes) is 4
ansible.builtin.lineinfile:
path: /etc/security/pwquality.conf
regexp: "^minclass"
line: "minclass = 4"
create: yes
- name: Ensure pam_faillock preauth is configured
ansible.builtin.lineinfile:
path: /etc/pam.d/system-auth
insertafter: "^auth.*required.*pam_unix.so"
line: "auth required pam_faillock.so preauth silent audit deny=5 unlock_time=900"
state: present
backrefs: no
- name: Ensure pam_faillock authfail/account lines exist
ansible.builtin.blockinfile:
path: /etc/pam.d/system-auth
insertafter: "^auth.*required.*pam_unix.so"
block: |
auth [default=die] pam_faillock.so authfail deny=5 unlock_time=900 audit
account required pam_faillock.so
@@ -0,0 +1,10 @@
---
unused_services:
- avahi-daemon
- bluetooth
- cups
- rpcbind
- nfslock
- rsh.socket
- telnet.socket
- rsyncd
@@ -0,0 +1,10 @@
- name: Ensure list of unnecessary services are stopped and masked
ansible.builtin.service:
name: "{{ svc }}"
enabled: no
state: stopped
masked: yes
loop: "{{ unused_services }}"
loop_control:
loop_var: svc
ignore_errors: true
@@ -0,0 +1,32 @@
---
# General settings
ssh_protocol: 2
ssh_permit_root_login: "no"
ssh_password_authentication: "no"
ssh_challenge_response_authentication: "no"
ssh_kerberos_authentication: "no"
ssh_gssapi_authentication: "no"
ssh_use_pam: "yes"
PrintLastLog: "yes"
# Session security
ssh_x11_forwarding: "no"
ssh_allow_tcp_forwarding: "yes"
ssh_permit_tunnel: "no"
ssh_allow_agent_forwarding: "no"
# Idle timeout / login attempts
ssh_client_alive_interval: 300
ssh_client_alive_count_max: 3
ssh_login_grace_time: 60
ssh_max_auth_tries: 3
ssh_max_sessions: 2
ssh_max_startups: "10:30:60"
# SSH access restrictions
ssh_allow_users: "adminuser ansibleuser"
# Strong encryption
ssh_ciphers: "aes256-ctr,aes192-ctr,aes128-ctr"
ssh_macs: "hmac-sha2-512,hmac-sha2-256"
ssh_kex_algorithms: "[email protected],diffie-hellman-group-exchange-sha256"
@@ -0,0 +1,82 @@
---
- name: Ensure OpenSSH server is installed
ansible.builtin.package:
name: openssh-server
state: present
- name: Ensure SSH daemon is enabled and running
ansible.builtin.service:
name: sshd
enabled: true
state: started
- name: Backup current sshd_config
ansible.builtin.copy:
src: /etc/ssh/sshd_config
dest: /etc/ssh/sshd_config.bak
owner: root
group: root
mode: "0600"
remote_src: true
- name: Harden SSH configuration
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ ssh_item.regexp }}"
line: "{{ ssh_item.line }}"
state: present
backrefs: yes
loop:
- regexp: "^#?Protocol"
line: "Protocol {{ ssh_protocol }}"
- regexp: "^#?PermitRootLogin"
line: "PermitRootLogin {{ ssh_permit_root_login }}"
- regexp: "^#?PasswordAuthentication"
line: "PasswordAuthentication {{ ssh_password_authentication }}"
- regexp: "^#?ChallengeResponseAuthentication"
line: "ChallengeResponseAuthentication {{ ssh_challenge_response_authentication }}"
- regexp: "^#?KerberosAuthentication"
line: "KerberosAuthentication {{ ssh_kerberos_authentication }}"
- regexp: "^#?GSSAPIAuthentication"
line: "GSSAPIAuthentication {{ ssh_gssapi_authentication }}"
- regexp: "^#?UsePAM"
line: "UsePAM {{ ssh_use_pam }}"
- regexp: "^#?X11Forwarding"
line: "X11Forwarding {{ ssh_x11_forwarding }}"
- regexp: "^#?AllowTcpForwarding"
line: "AllowTcpForwarding {{ ssh_allow_tcp_forwarding }}"
- regexp: "^#?PermitTunnel"
line: "PermitTunnel {{ ssh_permit_tunnel }}"
- regexp: "^#?AllowAgentForwarding"
line: "AllowAgentForwarding {{ ssh_allow_agent_forwarding }}"
- regexp: "^#?ClientAliveInterval"
line: "ClientAliveInterval {{ ssh_client_alive_interval }}"
- regexp: "^#?ClientAliveCountMax"
line: "ClientAliveCountMax {{ ssh_client_alive_count_max }}"
- regexp: "^#?LoginGraceTime"
line: "LoginGraceTime {{ ssh_login_grace_time }}"
- regexp: "^#?MaxAuthTries"
line: "MaxAuthTries {{ ssh_max_auth_tries }}"
- regexp: "^#?MaxSessions"
line: "MaxSessions {{ ssh_max_sessions }}"
- regexp: "^#?MaxStartups"
line: "MaxStartups {{ ssh_max_startups }}"
- regexp: "^#?Ciphers"
line: "Ciphers {{ ssh_ciphers }}"
- regexp: "^#?MACs"
line: "MACs {{ ssh_macs }}"
- regexp: "^#?KexAlgorithms"
line: "KexAlgorithms {{ ssh_kex_algorithms }}"
- regexp: "^#?PrintLastLog"
line: "PrintLastLog {{ PrintLastLog }}"
- regexp: '^\s*#?\s*AllowUsers'
line: "AllowUsers {{ ssh_allow_users }}"
- regexp: "^#?Banner"
line: "Banner /etc/issue.net"
loop_control:
loop_var: ssh_item
- name: Restart SSH service to apply changes
ansible.builtin.service:
name: sshd
state: restarted
@@ -0,0 +1,29 @@
---
# Parent CIS hardening role: import each subrole with single tag
- import_role:
name: cis_hardening/kernel_hardening
tags: [cis_hardening]
- import_role:
name: cis_hardening/password_policy
tags: [cis_hardening]
- import_role:
name: cis_hardening/file_permissions
tags: [cis_hardening]
- import_role:
name: cis_hardening/service_disable
tags: [cis_hardening]
- import_role:
name: cis_hardening/banners
tags: [cis_hardening]
- import_role:
name: cis_hardening/ssh_hardening
tags: [cis_hardening]
- import_role:
name: cis_hardening/filesystem_hardening
tags: [cis_hardening]