You've already forked ansible-linux-infra
initial commit
This commit is contained in:
59
roles/aide_integrity/tasks/main.yml
Normal file
59
roles/aide_integrity/tasks/main.yml
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
- name: Include OS-family-specific variables
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "{{ ansible_facts['os_family'] }}.yml"
|
||||
- "default.yml"
|
||||
tags: [aide_integrity, always]
|
||||
|
||||
- name: Install AIDE
|
||||
ansible.builtin.package:
|
||||
name: aide
|
||||
state: present
|
||||
tags: [aide_integrity]
|
||||
|
||||
- name: Deploy custom AIDE configuration (additional watched paths)
|
||||
ansible.builtin.template:
|
||||
src: aide-custom.conf.j2
|
||||
dest: "{{ aide_integrity_config_dest }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
tags: [aide_integrity]
|
||||
|
||||
- name: Check whether an AIDE database already exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ aide_integrity_db_path }}"
|
||||
register: aide_integrity_db_stat
|
||||
tags: [aide_integrity]
|
||||
|
||||
- name: Initialize AIDE database (first run only)
|
||||
ansible.builtin.command: aide --init
|
||||
when: not aide_integrity_db_stat.stat.exists
|
||||
changed_when: true
|
||||
tags: [aide_integrity]
|
||||
|
||||
- name: Activate the initial AIDE database
|
||||
ansible.builtin.command: "mv {{ aide_integrity_db_new_path }} {{ aide_integrity_db_path }}"
|
||||
when: not aide_integrity_db_stat.stat.exists
|
||||
changed_when: true
|
||||
tags: [aide_integrity]
|
||||
|
||||
- name: Schedule regular AIDE check via cron
|
||||
ansible.builtin.cron:
|
||||
name: "Daily AIDE integrity check"
|
||||
hour: "{{ aide_check_cron_hour }}"
|
||||
minute: "{{ aide_check_cron_minute }}"
|
||||
job: >-
|
||||
{{ aide_integrity_check_cmd }} | /usr/bin/mail -s
|
||||
"AIDE integrity report {{ inventory_hostname }}" {{ aide_notify_email }}
|
||||
user: root
|
||||
tags: [aide_integrity]
|
||||
|
||||
- name: Note on updating the database after legitimate changes
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
After intended system changes (patches, config changes made outside
|
||||
Ansible), update the AIDE database manually:
|
||||
aide --update && mv {{ aide_integrity_db_new_path }} {{ aide_integrity_db_path }}
|
||||
tags: [aide_integrity]
|
||||
12
roles/aide_integrity/templates/aide-custom.conf.j2
Normal file
12
roles/aide_integrity/templates/aide-custom.conf.j2
Normal file
@@ -0,0 +1,12 @@
|
||||
# {{ ansible_managed }}
|
||||
/etc/ssh/sshd_config FIPSR
|
||||
/etc/sudoers.d FIPSR
|
||||
/etc/security/pwquality.conf FIPSR
|
||||
/etc/security/faillock.conf FIPSR
|
||||
/etc/audit/rules.d FIPSR
|
||||
/etc/firewalld FIPSR
|
||||
|
||||
!/var/log
|
||||
!/var/cache
|
||||
!/tmp
|
||||
!/var/tmp
|
||||
5
roles/aide_integrity/vars/Debian.yml
Normal file
5
roles/aide_integrity/vars/Debian.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
aide_integrity_config_dest: /etc/aide/aide.conf.d/99-custom.conf
|
||||
aide_integrity_db_path: /var/lib/aide/aide.db
|
||||
aide_integrity_db_new_path: /var/lib/aide/aide.db.new
|
||||
aide_integrity_check_cmd: /usr/bin/aide.wrapper --check
|
||||
5
roles/aide_integrity/vars/RedHat.yml
Normal file
5
roles/aide_integrity/vars/RedHat.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
aide_integrity_config_dest: /etc/aide.conf.d/99-custom.conf
|
||||
aide_integrity_db_path: /var/lib/aide/aide.db.gz
|
||||
aide_integrity_db_new_path: /var/lib/aide/aide.db.new.gz
|
||||
aide_integrity_check_cmd: /usr/sbin/aide --check
|
||||
4
roles/apparmor_config/defaults/main.yml
Normal file
4
roles/apparmor_config/defaults/main.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
apparmor_config_complain_profiles: []
|
||||
# apparmor_config_complain_profiles:
|
||||
# - /usr/sbin/nginx
|
||||
42
roles/apparmor_config/tasks/main.yml
Normal file
42
roles/apparmor_config/tasks/main.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
- name: Install AppArmor packages
|
||||
ansible.builtin.package:
|
||||
name:
|
||||
- apparmor
|
||||
- apparmor-utils
|
||||
- apparmor-profiles
|
||||
state: present
|
||||
tags: [apparmor_config]
|
||||
|
||||
- name: Enable and start AppArmor
|
||||
ansible.builtin.systemd:
|
||||
name: apparmor
|
||||
enabled: true
|
||||
state: started
|
||||
tags: [apparmor_config]
|
||||
|
||||
- name: Set enforce mode for all loaded profiles
|
||||
ansible.builtin.shell: aa-enforce /etc/apparmor.d/*
|
||||
register: apparmor_config_enforce_result
|
||||
changed_when: "'Setting' in apparmor_config_enforce_result.stdout"
|
||||
failed_when: false
|
||||
tags: [apparmor_config]
|
||||
|
||||
- name: Set application-specific profiles to complain mode (exceptions)
|
||||
ansible.builtin.command: "aa-complain {{ item }}"
|
||||
loop: "{{ apparmor_config_complain_profiles }}"
|
||||
when: apparmor_config_complain_profiles | length > 0
|
||||
changed_when: true
|
||||
tags: [apparmor_config]
|
||||
|
||||
- name: Query AppArmor status
|
||||
ansible.builtin.command: aa-status --verbose
|
||||
register: apparmor_config_status
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
tags: [apparmor_config]
|
||||
|
||||
- name: Report AppArmor status
|
||||
ansible.builtin.debug:
|
||||
msg: "AppArmor status on {{ inventory_hostname }}: {{ apparmor_config_status.stdout_lines[0] | default('unavailable') }}"
|
||||
tags: [apparmor_config]
|
||||
6
roles/backup_agent/defaults/main.yml
Normal file
6
roles/backup_agent/defaults/main.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
# veeam_agent_repo_url: "https://repo.example.corp/veeam/veeam.repo"
|
||||
# veeam_agent_apt_uri: "https://repo.example.corp/veeam/deb"
|
||||
# veeam_agent_apt_suite: "stable"
|
||||
# veeam_agent_apt_components: "main"
|
||||
# veeam_agent_apt_signed_by: "https://repo.example.corp/veeam/gpg"
|
||||
48
roles/backup_agent/tasks/main.yml
Normal file
48
roles/backup_agent/tasks/main.yml
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
- name: Report that backup_agent is disabled for this host
|
||||
ansible.builtin.debug:
|
||||
msg: "veeam_agent_enabled is false - skipping backup_agent role for {{ inventory_hostname }}."
|
||||
when: not veeam_agent_enabled | bool
|
||||
tags: [backup_agent]
|
||||
|
||||
- name: Configure Veeam repository (RedHat family, customer-specific URL required)
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ veeam_agent_repo_url }}"
|
||||
dest: /etc/yum.repos.d/veeam.repo
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when:
|
||||
- veeam_agent_enabled | bool
|
||||
- ansible_facts['os_family'] == "RedHat"
|
||||
- veeam_agent_repo_url is defined
|
||||
tags: [backup_agent]
|
||||
|
||||
- name: Configure Veeam repository (Debian family, customer-specific values required)
|
||||
ansible.builtin.deb822_repository:
|
||||
name: veeam
|
||||
types: deb
|
||||
uris: "{{ veeam_agent_apt_uri }}"
|
||||
suites: "{{ veeam_agent_apt_suite }}"
|
||||
components: "{{ veeam_agent_apt_components }}"
|
||||
signed_by: "{{ veeam_agent_apt_signed_by | default(omit) }}"
|
||||
when:
|
||||
- veeam_agent_enabled | bool
|
||||
- ansible_facts['os_family'] == "Debian"
|
||||
- veeam_agent_apt_uri is defined
|
||||
tags: [backup_agent]
|
||||
|
||||
- name: Install Veeam agent
|
||||
ansible.builtin.package:
|
||||
name: veeam
|
||||
state: present
|
||||
when: veeam_agent_enabled | bool
|
||||
tags: [backup_agent]
|
||||
|
||||
- name: Enable and start Veeam agent service
|
||||
ansible.builtin.systemd:
|
||||
name: veeamservice
|
||||
enabled: true
|
||||
state: started
|
||||
when: veeam_agent_enabled | bool
|
||||
tags: [backup_agent]
|
||||
5
roles/banners/handlers/main.yml
Normal file
5
roles/banners/handlers/main.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: restart sshd (banners)
|
||||
ansible.builtin.systemd:
|
||||
name: sshd
|
||||
state: restarted
|
||||
21
roles/banners/tasks/main.yml
Normal file
21
roles/banners/tasks/main.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
- name: Deploy local console login banner
|
||||
ansible.builtin.template:
|
||||
src: issue.j2
|
||||
dest: /etc/issue
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: banner_enabled | bool
|
||||
tags: [banners]
|
||||
|
||||
- name: Deploy network (pre-auth SSH) login banner
|
||||
ansible.builtin.template:
|
||||
src: issue.net.j2
|
||||
dest: /etc/issue.net
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: banner_enabled | bool
|
||||
notify: restart sshd (banners)
|
||||
tags: [banners]
|
||||
5
roles/banners/templates/issue.j2
Normal file
5
roles/banners/templates/issue.j2
Normal file
@@ -0,0 +1,5 @@
|
||||
******************************************************************
|
||||
AUTHORIZED ACCESS ONLY.
|
||||
All activity on this system is logged and monitored.
|
||||
Unauthorized access is prohibited and may be prosecuted.
|
||||
******************************************************************
|
||||
5
roles/banners/templates/issue.net.j2
Normal file
5
roles/banners/templates/issue.net.j2
Normal file
@@ -0,0 +1,5 @@
|
||||
******************************************************************
|
||||
AUTHORIZED ACCESS ONLY.
|
||||
All activity on this system is logged and monitored.
|
||||
Unauthorized access is prohibited and may be prosecuted.
|
||||
******************************************************************
|
||||
5
roles/base_os/handlers/main.yml
Normal file
5
roles/base_os/handlers/main.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: restart chrony
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ base_os_chrony_service_name }}"
|
||||
state: restarted
|
||||
75
roles/base_os/tasks/main.yml
Normal file
75
roles/base_os/tasks/main.yml
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
- name: Include OS-family-specific variables
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "{{ ansible_facts['os_family'] }}.yml"
|
||||
- "default.yml"
|
||||
tags: [base_os, always]
|
||||
|
||||
- name: Update apt cache
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
when: ansible_facts['os_family'] == "Debian"
|
||||
tags: [base_os]
|
||||
|
||||
- name: Install base packages
|
||||
ansible.builtin.package:
|
||||
name: "{{ base_os_packages + (base_extra_packages | default([])) }}"
|
||||
state: present
|
||||
tags: [base_os]
|
||||
|
||||
- name: Set timezone
|
||||
community.general.timezone:
|
||||
name: "{{ timezone }}"
|
||||
tags: [base_os]
|
||||
|
||||
- name: Deploy chrony configuration
|
||||
ansible.builtin.template:
|
||||
src: chrony.conf.j2
|
||||
dest: "{{ base_os_chrony_config_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: restart chrony
|
||||
tags: [base_os]
|
||||
|
||||
- name: Enable and start chrony
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ base_os_chrony_service_name }}"
|
||||
enabled: true
|
||||
state: started
|
||||
tags: [base_os]
|
||||
|
||||
- name: Set hostname from inventory
|
||||
ansible.builtin.hostname:
|
||||
name: "{{ inventory_hostname }}"
|
||||
tags: [base_os]
|
||||
|
||||
- name: Remove unwanted packages
|
||||
ansible.builtin.package:
|
||||
name: "{{ base_os_unwanted_packages }}"
|
||||
state: absent
|
||||
tags: [base_os, cis_hardening]
|
||||
|
||||
- name: Gather service facts
|
||||
ansible.builtin.service_facts:
|
||||
tags: [base_os]
|
||||
|
||||
- name: Disable unneeded services if installed
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: false
|
||||
state: stopped
|
||||
loop: "{{ base_unneeded_services }}"
|
||||
when: (item + '.service') in ansible_facts.services
|
||||
tags: [base_os]
|
||||
|
||||
- name: Deploy post-login MOTD
|
||||
ansible.builtin.template:
|
||||
src: motd.j2
|
||||
dest: /etc/motd
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
tags: [base_os]
|
||||
9
roles/base_os/templates/chrony.conf.j2
Normal file
9
roles/base_os/templates/chrony.conf.j2
Normal file
@@ -0,0 +1,9 @@
|
||||
# {{ ansible_managed }}
|
||||
{% for server in ntp_servers %}
|
||||
server {{ server }} iburst
|
||||
{% endfor %}
|
||||
|
||||
driftfile /var/lib/chrony/drift
|
||||
makestep 1.0 3
|
||||
rtcsync
|
||||
logdir /var/log/chrony
|
||||
6
roles/base_os/templates/motd.j2
Normal file
6
roles/base_os/templates/motd.j2
Normal file
@@ -0,0 +1,6 @@
|
||||
******************************************************************
|
||||
AUTHORIZED ACCESS ONLY.
|
||||
All activity on this system is logged and monitored.
|
||||
Unauthorized access is prohibited and may be prosecuted.
|
||||
Managed via Ansible ({{ inventory_hostname }}).
|
||||
******************************************************************
|
||||
17
roles/base_os/vars/Debian.yml
Normal file
17
roles/base_os/vars/Debian.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
base_os_packages:
|
||||
- vim
|
||||
- chrony
|
||||
- auditd
|
||||
- firewalld
|
||||
- rsyslog
|
||||
- sudo
|
||||
base_os_unwanted_packages:
|
||||
- sendmail
|
||||
- telnet
|
||||
- rsh-client
|
||||
- rsh-redone-client
|
||||
- nis
|
||||
- tftp
|
||||
base_os_chrony_config_path: /etc/chrony/chrony.conf
|
||||
base_os_chrony_service_name: chrony
|
||||
17
roles/base_os/vars/RedHat.yml
Normal file
17
roles/base_os/vars/RedHat.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
base_os_packages:
|
||||
- vim-enhanced
|
||||
- chrony
|
||||
- audit
|
||||
- policycoreutils-python-utils
|
||||
- firewalld
|
||||
- rsyslog
|
||||
- sudo
|
||||
base_os_unwanted_packages:
|
||||
- sendmail
|
||||
- telnet
|
||||
- rsh
|
||||
- ypbind
|
||||
- tftp
|
||||
base_os_chrony_config_path: /etc/chrony.conf
|
||||
base_os_chrony_service_name: chronyd
|
||||
20
roles/cis_hardening/handlers/main.yml
Normal file
20
roles/cis_hardening/handlers/main.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
- name: reload auditd rules
|
||||
ansible.builtin.command: augenrules --load
|
||||
changed_when: true
|
||||
|
||||
- name: restart auditd
|
||||
ansible.builtin.systemd:
|
||||
name: auditd
|
||||
state: restarted
|
||||
|
||||
- name: restart systemd-journald
|
||||
ansible.builtin.systemd:
|
||||
name: systemd-journald
|
||||
state: restarted
|
||||
|
||||
- name: regenerate grub config
|
||||
ansible.builtin.command: >-
|
||||
{{ cis_hardening_grub_mkconfig_cmd }}
|
||||
{{ ('-o ' + (cis_hardening_grub_cfg_path_uefi if grub_bootloader_is_uefi | default(false) else cis_hardening_grub_cfg_path_bios)) if (cis_hardening_grub_cfg_path_bios | default('')) | length > 0 else '' }}
|
||||
changed_when: true
|
||||
255
roles/cis_hardening/tasks/main.yml
Normal file
255
roles/cis_hardening/tasks/main.yml
Normal file
@@ -0,0 +1,255 @@
|
||||
---
|
||||
- name: Include OS-family-specific variables
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "{{ ansible_facts['os_family'] }}.yml"
|
||||
- "default.yml"
|
||||
tags: [cis_hardening, always]
|
||||
|
||||
- name: Disable unneeded filesystem kernel modules
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/modprobe.d/cis-disable-filesystems.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
# {{ ansible_managed }}
|
||||
{% for fs in cis_disable_filesystems %}
|
||||
install {{ fs }} /bin/true
|
||||
{% endfor %}
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Set kernel/network sysctl hardening parameters
|
||||
ansible.posix.sysctl:
|
||||
name: "{{ item.name }}"
|
||||
value: "{{ item.value }}"
|
||||
sysctl_set: true
|
||||
state: present
|
||||
reload: true
|
||||
loop:
|
||||
- { name: "net.ipv4.ip_forward", value: "0" }
|
||||
- { name: "net.ipv4.conf.all.send_redirects", value: "0" }
|
||||
- { name: "net.ipv4.conf.all.accept_redirects", value: "0" }
|
||||
- { name: "net.ipv4.conf.all.accept_source_route", value: "0" }
|
||||
- { name: "net.ipv4.conf.all.log_martians", value: "1" }
|
||||
- { name: "net.ipv4.icmp_echo_ignore_broadcasts", value: "1" }
|
||||
- { name: "kernel.randomize_va_space", value: "2" }
|
||||
- { name: "fs.suid_dumpable", value: "0" }
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Enable strict reverse-path filtering (CIS Level 2)
|
||||
ansible.posix.sysctl:
|
||||
name: "net.ipv4.conf.all.rp_filter"
|
||||
value: "1"
|
||||
sysctl_set: true
|
||||
state: present
|
||||
reload: true
|
||||
when: cis_level | int >= 2
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Harden /tmp mount options (noexec,nosuid,nodev)
|
||||
ansible.posix.mount:
|
||||
path: /tmp
|
||||
src: tmpfs
|
||||
fstype: tmpfs
|
||||
opts: "defaults,rw,nosuid,nodev,noexec,relatime"
|
||||
state: mounted
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Bind and harden /var/tmp (CIS Level 2)
|
||||
ansible.posix.mount:
|
||||
path: /var/tmp
|
||||
src: /tmp
|
||||
fstype: none
|
||||
opts: "bind,nosuid,nodev,noexec"
|
||||
state: mounted
|
||||
when: cis_level | int >= 2
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Disable additional kernel modules (CIS Level 2)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/modprobe.d/cis-disable-level2-modules.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
# {{ ansible_managed }}
|
||||
{% for mod in cis_disable_modules_level2 %}
|
||||
install {{ mod }} /bin/true
|
||||
{% endfor %}
|
||||
when: cis_level | int >= 2
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Disable core dumps
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/security/limits.d/99-cis-disable-coredumps.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
# {{ ansible_managed }}
|
||||
* hard core 0
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Enforce default umask
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/profile.d/99-cis-umask.sh
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
# {{ ansible_managed }}
|
||||
umask {{ cis_umask }}
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Set shell auto-logout timeout (TMOUT)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/profile.d/99-cis-tmout.sh
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
# {{ ansible_managed }}
|
||||
TMOUT={{ cis_shell_tmout_seconds }}
|
||||
readonly TMOUT
|
||||
export TMOUT
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Mask Ctrl-Alt-Del reboot target
|
||||
ansible.builtin.systemd:
|
||||
name: ctrl-alt-del.target
|
||||
masked: true
|
||||
when: cis_disable_ctrl_alt_del | bool
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Find world-writable directories missing the sticky bit
|
||||
ansible.builtin.shell: >
|
||||
set -o pipefail;
|
||||
df --local -P | awk '{if (NR!=1) print $6}' |
|
||||
xargs -I '{}' find '{}' -xdev -type d \( -perm -0002 -a ! -perm -1000 \) 2>/dev/null
|
||||
register: cis_hardening_sticky_bit_dirs
|
||||
changed_when: false
|
||||
when: cis_sticky_bit_enforce | bool
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Apply sticky bit to world-writable directories
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
mode: "a+t"
|
||||
loop: "{{ cis_hardening_sticky_bit_dirs.stdout_lines | default([]) }}"
|
||||
when: cis_sticky_bit_enforce | bool
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Configure journald persistent storage and size cap
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/systemd/journald.conf
|
||||
regexp: "^#?{{ item.key }}="
|
||||
line: "{{ item.key }}={{ item.value }}"
|
||||
loop:
|
||||
- { key: "Storage", value: "persistent" }
|
||||
- { key: "SystemMaxUse", value: "{{ cis_journald_max_use }}" }
|
||||
when: cis_journald_persistent | bool
|
||||
notify: restart systemd-journald
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Schedule weekly report of unowned/ungrouped files
|
||||
ansible.builtin.cron:
|
||||
name: "Weekly unowned files report"
|
||||
weekday: "0"
|
||||
hour: "{{ cis_unowned_files_report_cron_hour }}"
|
||||
minute: "0"
|
||||
job: >-
|
||||
/usr/bin/find / -xdev \( -nouser -o -nogroup \) 2>/dev/null |
|
||||
/usr/bin/mail -s "Unowned files report {{ inventory_hostname }}" root
|
||||
user: root
|
||||
when: cis_unowned_files_report_enabled | bool
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Harden /boot mount options in fstab (requires manual review and reboot)
|
||||
ansible.builtin.replace:
|
||||
path: /etc/fstab
|
||||
regexp: '^(\S+\s+/boot\s+\S+\s+)(?!.*nodev)(\S+)(\s+.*)$'
|
||||
replace: '\1\2,nodev,nosuid\3'
|
||||
when: cis_boot_hardening_enabled | bool
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Deploy baseline auditd rules (logins, time changes, account changes)
|
||||
ansible.builtin.template:
|
||||
src: audit-baseline.rules.j2
|
||||
dest: /etc/audit/rules.d/10-baseline.rules
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
notify: reload auditd rules
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Deploy extended auditd rules (CIS Level 2)
|
||||
ansible.builtin.template:
|
||||
src: audit-level2.rules.j2
|
||||
dest: /etc/audit/rules.d/20-level2.rules
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
when: cis_level | int >= 2
|
||||
notify: reload auditd rules
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Configure auditd log rotation and disk-space handling
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/audit/auditd.conf
|
||||
regexp: "^{{ item.key }}\\s*="
|
||||
line: "{{ item.key }} = {{ item.value }}"
|
||||
loop:
|
||||
- { key: "max_log_file", value: "50" }
|
||||
- { key: "max_log_file_action", value: "rotate" }
|
||||
- { key: "num_logs", value: "10" }
|
||||
- { key: "space_left", value: "10%" }
|
||||
- { key: "space_left_action", value: "email" }
|
||||
- { key: "admin_space_left", value: "5%" }
|
||||
- { key: "admin_space_left_action", value: "halt" }
|
||||
notify: restart auditd
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Set login.defs password aging policy
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/login.defs
|
||||
regexp: "^{{ item.key }}"
|
||||
line: "{{ item.key }}\t{{ item.value }}"
|
||||
loop:
|
||||
- { key: "PASS_MAX_DAYS", value: "90" }
|
||||
- { key: "PASS_MIN_DAYS", value: "1" }
|
||||
- { key: "PASS_WARN_AGE", value: "7" }
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Restrict cron/at to authorized users
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: touch
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
loop:
|
||||
- /etc/cron.allow
|
||||
- /etc/at.allow
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Configure GRUB bootloader password (pre-generated PBKDF2 hash)
|
||||
ansible.builtin.blockinfile:
|
||||
path: /etc/grub.d/40_custom
|
||||
marker: "# {mark} ANSIBLE MANAGED BLOCK"
|
||||
insertafter: "EOF"
|
||||
block: |
|
||||
set superusers="{{ grub_bootloader_username }}"
|
||||
password_pbkdf2 {{ grub_bootloader_username }} {{ grub_bootloader_password_hash }}
|
||||
when:
|
||||
- cis_grub_password_enabled | bool
|
||||
- grub_bootloader_password_hash | length > 0
|
||||
notify: regenerate grub config
|
||||
tags: [cis_hardening]
|
||||
|
||||
- name: Enable and start auditd
|
||||
ansible.builtin.systemd:
|
||||
name: auditd
|
||||
enabled: true
|
||||
state: started
|
||||
tags: [cis_hardening]
|
||||
19
roles/cis_hardening/templates/audit-baseline.rules.j2
Normal file
19
roles/cis_hardening/templates/audit-baseline.rules.j2
Normal file
@@ -0,0 +1,19 @@
|
||||
# {{ ansible_managed }}
|
||||
-D
|
||||
-b 8192
|
||||
|
||||
-w /var/log/lastlog -p wa -k logins
|
||||
-w /var/run/faillock -p wa -k logins
|
||||
|
||||
-a always,exit -F arch=b64 -S adjtimex,settimeofday,clock_settime -k time-change
|
||||
-w /etc/localtime -p wa -k time-change
|
||||
|
||||
-w /etc/passwd -p wa -k identity
|
||||
-w /etc/group -p wa -k identity
|
||||
-w /etc/shadow -p wa -k identity
|
||||
-w /etc/sudoers -p wa -k identity
|
||||
-w /etc/sudoers.d/ -p wa -k identity
|
||||
|
||||
-w /home/{{ breakglass_username }} -p rwxa -k breakglass-usage
|
||||
|
||||
-e 2
|
||||
9
roles/cis_hardening/templates/audit-level2.rules.j2
Normal file
9
roles/cis_hardening/templates/audit-level2.rules.j2
Normal file
@@ -0,0 +1,9 @@
|
||||
# {{ ansible_managed }}
|
||||
-a always,exit -F arch=b64 -S open,truncate,ftruncate,creat,openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -k access-denied
|
||||
-a always,exit -F arch=b64 -S open,truncate,ftruncate,creat,openat -F exit=-EPERM -F auid>=1000 -F auid!=unset -k access-denied
|
||||
|
||||
-a always,exit -F arch=b64 -S mount -F auid>=1000 -F auid!=unset -k mounts
|
||||
|
||||
-w /sbin/insmod -p x -k modules
|
||||
-w /sbin/rmmod -p x -k modules
|
||||
-w /sbin/modprobe -p x -k modules
|
||||
4
roles/cis_hardening/vars/Debian.yml
Normal file
4
roles/cis_hardening/vars/Debian.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
cis_hardening_grub_mkconfig_cmd: "update-grub"
|
||||
cis_hardening_grub_cfg_path_bios: ""
|
||||
cis_hardening_grub_cfg_path_uefi: ""
|
||||
4
roles/cis_hardening/vars/RedHat.yml
Normal file
4
roles/cis_hardening/vars/RedHat.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
cis_hardening_grub_mkconfig_cmd: "grub2-mkconfig"
|
||||
cis_hardening_grub_cfg_path_bios: "/boot/grub2/grub.cfg"
|
||||
cis_hardening_grub_cfg_path_uefi: "/boot/efi/EFI/rocky/grub.cfg"
|
||||
5
roles/fail2ban/handlers/main.yml
Normal file
5
roles/fail2ban/handlers/main.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: restart fail2ban
|
||||
ansible.builtin.systemd:
|
||||
name: fail2ban
|
||||
state: restarted
|
||||
37
roles/fail2ban/tasks/main.yml
Normal file
37
roles/fail2ban/tasks/main.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
- name: Gather package facts
|
||||
ansible.builtin.package_facts:
|
||||
manager: auto
|
||||
when: ansible_facts['os_family'] == "RedHat"
|
||||
tags: [fail2ban]
|
||||
|
||||
- name: Fail with guidance if EPEL is missing on RedHat family
|
||||
ansible.builtin.fail:
|
||||
msg: "fail2ban requires EPEL on RedHat family. Set epel_enabled: true (repo_management role) or provide fail2ban via custom_yum_repos."
|
||||
when:
|
||||
- ansible_facts['os_family'] == "RedHat"
|
||||
- "'epel-release' not in ansible_facts.packages"
|
||||
tags: [fail2ban]
|
||||
|
||||
- name: Install fail2ban
|
||||
ansible.builtin.package:
|
||||
name: fail2ban
|
||||
state: present
|
||||
tags: [fail2ban]
|
||||
|
||||
- name: Deploy fail2ban local jail configuration
|
||||
ansible.builtin.template:
|
||||
src: jail.local.j2
|
||||
dest: /etc/fail2ban/jail.local
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: restart fail2ban
|
||||
tags: [fail2ban]
|
||||
|
||||
- name: Enable and start fail2ban
|
||||
ansible.builtin.systemd:
|
||||
name: fail2ban
|
||||
enabled: true
|
||||
state: started
|
||||
tags: [fail2ban]
|
||||
11
roles/fail2ban/templates/jail.local.j2
Normal file
11
roles/fail2ban/templates/jail.local.j2
Normal file
@@ -0,0 +1,11 @@
|
||||
# {{ ansible_managed }}
|
||||
[DEFAULT]
|
||||
bantime = {{ fail2ban_bantime }}
|
||||
findtime = {{ fail2ban_findtime }}
|
||||
maxretry = {{ fail2ban_maxretry }}
|
||||
ignoreip = {{ fail2ban_ignoreip }}
|
||||
backend = systemd
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
port = {{ ssh_port }}
|
||||
9
roles/falcon_onboarding/defaults/main.yml
Normal file
9
roles/falcon_onboarding/defaults/main.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
# The sensor can only be downloaded via an authenticated CrowdStrike API/
|
||||
# console session, so exactly one of these must be provided per environment:
|
||||
# falcon_onboarding_sensor_package_url - internal hosted download URL
|
||||
# falcon_onboarding_sensor_package_src - local path on the control node
|
||||
falcon_onboarding_sensor_package_url: ""
|
||||
falcon_onboarding_sensor_package_src: ""
|
||||
falcon_onboarding_provisioning_token: ""
|
||||
falcon_onboarding_cloud_region: "eu-1"
|
||||
5
roles/falcon_onboarding/handlers/main.yml
Normal file
5
roles/falcon_onboarding/handlers/main.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: restart falcon-sensor
|
||||
ansible.builtin.systemd:
|
||||
name: falcon-sensor
|
||||
state: restarted
|
||||
100
roles/falcon_onboarding/tasks/main.yml
Normal file
100
roles/falcon_onboarding/tasks/main.yml
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
- name: Include OS-family-specific variables
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "{{ ansible_facts['os_family'] }}.yml"
|
||||
- "default.yml"
|
||||
tags: [falcon_onboarding, always]
|
||||
|
||||
- name: Verify a sensor package source is defined
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- falcon_onboarding_sensor_package_url | length > 0 or falcon_onboarding_sensor_package_src | length > 0
|
||||
fail_msg: >-
|
||||
Neither falcon_onboarding_sensor_package_url nor
|
||||
falcon_onboarding_sensor_package_src is set. Provide the sensor
|
||||
package matching this host's OS (see roles/falcon_onboarding/defaults/main.yml).
|
||||
tags: [falcon_onboarding]
|
||||
|
||||
- name: Download sensor package from internal URL
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ falcon_onboarding_sensor_package_url }}"
|
||||
dest: "/tmp/falcon-sensor.{{ falcon_onboarding_package_ext }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: falcon_onboarding_sensor_package_url | length > 0
|
||||
tags: [falcon_onboarding]
|
||||
|
||||
- name: Copy sensor package from the Ansible control node
|
||||
ansible.builtin.copy:
|
||||
src: "{{ falcon_onboarding_sensor_package_src }}"
|
||||
dest: "/tmp/falcon-sensor.{{ falcon_onboarding_package_ext }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: falcon_onboarding_sensor_package_url | length == 0 and falcon_onboarding_sensor_package_src | length > 0
|
||||
tags: [falcon_onboarding]
|
||||
|
||||
- name: Install Falcon sensor (RedHat family)
|
||||
ansible.builtin.dnf:
|
||||
name: "/tmp/falcon-sensor.rpm"
|
||||
state: present
|
||||
disable_gpg_check: false
|
||||
when: ansible_facts['os_family'] == "RedHat"
|
||||
register: falcon_onboarding_install_result
|
||||
tags: [falcon_onboarding]
|
||||
|
||||
- name: Install Falcon sensor (Debian family)
|
||||
ansible.builtin.apt:
|
||||
deb: "/tmp/falcon-sensor.deb"
|
||||
when: ansible_facts['os_family'] == "Debian"
|
||||
register: falcon_onboarding_install_result
|
||||
tags: [falcon_onboarding]
|
||||
|
||||
- name: Remove downloaded/copied sensor package
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/falcon-sensor.{{ falcon_onboarding_package_ext }}"
|
||||
state: absent
|
||||
tags: [falcon_onboarding]
|
||||
|
||||
- name: Configure Customer ID (CID)
|
||||
ansible.builtin.command: "/opt/CrowdStrike/falconctl -s --cid={{ falcon_cid }}"
|
||||
changed_when: true
|
||||
no_log: true
|
||||
notify: restart falcon-sensor
|
||||
tags: [falcon_onboarding]
|
||||
|
||||
- name: Configure provisioning token if required
|
||||
ansible.builtin.command: "/opt/CrowdStrike/falconctl -s --provisioning-token={{ falcon_onboarding_provisioning_token }}"
|
||||
changed_when: true
|
||||
no_log: true
|
||||
when: falcon_onboarding_provisioning_token | length > 0
|
||||
notify: restart falcon-sensor
|
||||
tags: [falcon_onboarding]
|
||||
|
||||
- name: Enable and start the Falcon sensor service
|
||||
ansible.builtin.systemd:
|
||||
name: falcon-sensor
|
||||
enabled: true
|
||||
state: started
|
||||
tags: [falcon_onboarding]
|
||||
|
||||
- name: Check reduced-functionality mode (RFM)
|
||||
ansible.builtin.command: /opt/CrowdStrike/falconctl -g --rfm-state
|
||||
register: falcon_onboarding_rfm_state
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
tags: [falcon_onboarding]
|
||||
|
||||
- name: Query agent ID (AID) for verification
|
||||
ansible.builtin.command: /opt/CrowdStrike/falconctl -g --aid
|
||||
register: falcon_onboarding_aid
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
tags: [falcon_onboarding]
|
||||
|
||||
- name: Report Falcon onboarding status
|
||||
ansible.builtin.debug:
|
||||
msg: "CrowdStrike Falcon on {{ inventory_hostname }} - {{ falcon_onboarding_rfm_state.stdout }} | {{ falcon_onboarding_aid.stdout }}"
|
||||
tags: [falcon_onboarding]
|
||||
2
roles/falcon_onboarding/vars/Debian.yml
Normal file
2
roles/falcon_onboarding/vars/Debian.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
---
|
||||
falcon_onboarding_package_ext: deb
|
||||
2
roles/falcon_onboarding/vars/RedHat.yml
Normal file
2
roles/falcon_onboarding/vars/RedHat.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
---
|
||||
falcon_onboarding_package_ext: rpm
|
||||
56
roles/firewall_config/tasks/main.yml
Normal file
56
roles/firewall_config/tasks/main.yml
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
- name: Install firewalld
|
||||
ansible.builtin.package:
|
||||
name: firewalld
|
||||
state: present
|
||||
tags: [firewall_config]
|
||||
|
||||
- name: Enable and start firewalld
|
||||
ansible.builtin.systemd:
|
||||
name: firewalld
|
||||
enabled: true
|
||||
state: started
|
||||
tags: [firewall_config]
|
||||
|
||||
- name: Enable the restrictive default zone
|
||||
ansible.posix.firewalld:
|
||||
zone: "{{ firewall_default_zone }}"
|
||||
state: enabled
|
||||
permanent: true
|
||||
immediate: true
|
||||
tags: [firewall_config]
|
||||
|
||||
- name: Set the restrictive zone as system default
|
||||
ansible.builtin.command: "firewall-cmd --set-default-zone={{ firewall_default_zone }}"
|
||||
changed_when: true
|
||||
tags: [firewall_config]
|
||||
|
||||
- name: Enable logging of denied packets
|
||||
ansible.builtin.command: "firewall-cmd --set-log-denied={{ firewall_log_denied }}"
|
||||
changed_when: true
|
||||
tags: [firewall_config]
|
||||
|
||||
- name: Allow SSH only from the administration subnet
|
||||
ansible.posix.firewalld:
|
||||
zone: "{{ firewall_default_zone }}"
|
||||
rich_rule: >-
|
||||
rule family="ipv4" source address="{{ firewall_admin_subnet }}"
|
||||
port protocol="tcp" port="{{ ssh_port | default(22) }}" accept
|
||||
permanent: true
|
||||
immediate: true
|
||||
state: enabled
|
||||
tags: [firewall_config]
|
||||
|
||||
- name: Allow defined application services/ports
|
||||
ansible.posix.firewalld:
|
||||
zone: "{{ firewall_default_zone }}"
|
||||
rich_rule: >-
|
||||
rule family="ipv4"
|
||||
{{ 'source address="' + item.source + '"' if item.source is defined and item.source != 'any' else '' }}
|
||||
service name="{{ item.service }}" accept
|
||||
permanent: true
|
||||
immediate: true
|
||||
state: enabled
|
||||
loop: "{{ firewall_allowed_services }}"
|
||||
when: firewall_allowed_services | length > 0
|
||||
tags: [firewall_config]
|
||||
5
roles/identity_ad/handlers/main.yml
Normal file
5
roles/identity_ad/handlers/main.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: restart sssd
|
||||
ansible.builtin.systemd:
|
||||
name: sssd
|
||||
state: restarted
|
||||
89
roles/identity_ad/tasks/main.yml
Normal file
89
roles/identity_ad/tasks/main.yml
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
- name: Include OS-family-specific variables
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "{{ ansible_facts['os_family'] }}.yml"
|
||||
- "default.yml"
|
||||
tags: [identity_ad, always]
|
||||
|
||||
- name: Install AD integration packages
|
||||
ansible.builtin.package:
|
||||
name: "{{ identity_ad_packages }}"
|
||||
state: present
|
||||
tags: [identity_ad]
|
||||
|
||||
- name: Check whether host is already domain-joined
|
||||
ansible.builtin.command: realm list
|
||||
register: identity_ad_realm_status
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
tags: [identity_ad]
|
||||
|
||||
- name: Join Active Directory domain
|
||||
ansible.builtin.command: >
|
||||
realm join --user={{ ad_join_username }}
|
||||
--computer-ou="{{ ad_ou }}"
|
||||
{{ ad_domain }}
|
||||
args:
|
||||
stdin: "{{ ad_join_password }}"
|
||||
when: ad_domain not in identity_ad_realm_status.stdout
|
||||
changed_when: true
|
||||
no_log: true
|
||||
tags: [identity_ad]
|
||||
|
||||
- name: Deploy sssd.conf
|
||||
ansible.builtin.template:
|
||||
src: sssd.conf.j2
|
||||
dest: /etc/sssd/sssd.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
notify: restart sssd
|
||||
tags: [identity_ad]
|
||||
|
||||
- name: Enable oddjobd (required for pam_mkhomedir, RedHat family)
|
||||
ansible.builtin.systemd:
|
||||
name: oddjobd
|
||||
enabled: true
|
||||
state: started
|
||||
when: ansible_facts['os_family'] == "RedHat"
|
||||
tags: [identity_ad]
|
||||
|
||||
- name: Enable automatic home directory creation (RedHat family)
|
||||
ansible.builtin.command: authselect enable-feature with-mkhomedir
|
||||
register: identity_ad_authselect_result
|
||||
changed_when: "'already enabled' not in identity_ad_authselect_result.stderr"
|
||||
failed_when: false
|
||||
when: ansible_facts['os_family'] == "RedHat"
|
||||
tags: [identity_ad]
|
||||
|
||||
- name: Check whether pam_mkhomedir is already enabled (Debian family)
|
||||
ansible.builtin.command: grep -q pam_mkhomedir.so /etc/pam.d/common-session
|
||||
register: identity_ad_mkhomedir_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: ansible_facts['os_family'] == "Debian"
|
||||
tags: [identity_ad]
|
||||
|
||||
- name: Enable automatic home directory creation (Debian family)
|
||||
ansible.builtin.command: pam-auth-update --enable mkhomedir
|
||||
when:
|
||||
- ansible_facts['os_family'] == "Debian"
|
||||
- identity_ad_mkhomedir_check.rc != 0
|
||||
changed_when: true
|
||||
tags: [identity_ad]
|
||||
|
||||
- name: Enable and start sssd
|
||||
ansible.builtin.systemd:
|
||||
name: sssd
|
||||
enabled: true
|
||||
state: started
|
||||
tags: [identity_ad]
|
||||
|
||||
- name: Restrict login access to authorized AD groups
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/sssd/sssd.conf
|
||||
regexp: "^access_provider"
|
||||
line: "access_provider = simple"
|
||||
notify: restart sssd
|
||||
tags: [identity_ad]
|
||||
23
roles/identity_ad/templates/sssd.conf.j2
Normal file
23
roles/identity_ad/templates/sssd.conf.j2
Normal file
@@ -0,0 +1,23 @@
|
||||
# {{ ansible_managed }}
|
||||
[sssd]
|
||||
services = nss, pam
|
||||
domains = {{ ad_domain }}
|
||||
|
||||
[domain/{{ ad_domain }}]
|
||||
id_provider = ad
|
||||
access_provider = simple
|
||||
simple_allow_groups = {{ ad_admin_group }}, {{ ad_operator_group }}
|
||||
auth_provider = ad
|
||||
chpass_provider = ad
|
||||
|
||||
ad_domain = {{ ad_domain }}
|
||||
krb5_realm = {{ ad_domain_realm }}
|
||||
|
||||
use_fully_qualified_names = {{ sssd_use_fully_qualified_names | lower }}
|
||||
fallback_homedir = /home/%d/%u
|
||||
default_shell = /bin/bash
|
||||
|
||||
cache_credentials = true
|
||||
enumerate = false
|
||||
|
||||
ldap_id_mapping = true
|
||||
10
roles/identity_ad/vars/Debian.yml
Normal file
10
roles/identity_ad/vars/Debian.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
identity_ad_packages:
|
||||
- realmd
|
||||
- sssd
|
||||
- sssd-tools
|
||||
- adcli
|
||||
- samba-common-bin
|
||||
- krb5-user
|
||||
- libnss-sss
|
||||
- libpam-sss
|
||||
10
roles/identity_ad/vars/RedHat.yml
Normal file
10
roles/identity_ad/vars/RedHat.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
identity_ad_packages:
|
||||
- realmd
|
||||
- sssd
|
||||
- sssd-tools
|
||||
- oddjob
|
||||
- oddjob-mkhomedir
|
||||
- adcli
|
||||
- samba-common-tools
|
||||
- krb5-workstation
|
||||
5
roles/local_accounts/handlers/main.yml
Normal file
5
roles/local_accounts/handlers/main.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: restart sshd (local_accounts)
|
||||
ansible.builtin.systemd:
|
||||
name: sshd
|
||||
state: restarted
|
||||
67
roles/local_accounts/tasks/main.yml
Normal file
67
roles/local_accounts/tasks/main.yml
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
- name: Create break-glass group
|
||||
ansible.builtin.group:
|
||||
name: "{{ breakglass_username }}"
|
||||
state: present
|
||||
tags: [local_accounts]
|
||||
|
||||
- name: Create break-glass account
|
||||
ansible.builtin.user:
|
||||
name: "{{ breakglass_username }}"
|
||||
comment: "{{ breakglass_comment }}"
|
||||
group: "{{ breakglass_username }}"
|
||||
shell: /bin/bash
|
||||
create_home: true
|
||||
home: "/home/{{ breakglass_username }}"
|
||||
password: "{{ breakglass_password_hash | default('!', true) }}"
|
||||
update_password: on_create
|
||||
state: present
|
||||
tags: [local_accounts]
|
||||
|
||||
- name: Lock down break-glass home directory
|
||||
ansible.builtin.file:
|
||||
path: "/home/{{ breakglass_username }}"
|
||||
owner: "{{ breakglass_username }}"
|
||||
group: "{{ breakglass_username }}"
|
||||
mode: "0700"
|
||||
tags: [local_accounts]
|
||||
|
||||
- name: Deploy unrestricted sudo drop-in for break-glass account
|
||||
ansible.builtin.copy:
|
||||
dest: "/etc/sudoers.d/00-{{ breakglass_username }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0440"
|
||||
content: "{{ breakglass_username }} ALL=(ALL) ALL\n"
|
||||
validate: "visudo -cf %s"
|
||||
tags: [local_accounts]
|
||||
|
||||
- name: Block SSH access for the break-glass account
|
||||
ansible.builtin.blockinfile:
|
||||
path: /etc/ssh/sshd_config.d/00-breakglass-deny.conf
|
||||
create: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
marker: "# {mark} ANSIBLE MANAGED BLOCK"
|
||||
block: |
|
||||
Match User {{ breakglass_username }}
|
||||
PasswordAuthentication no
|
||||
PubkeyAuthentication no
|
||||
validate: "/usr/sbin/sshd -t -f %s"
|
||||
notify: restart sshd (local_accounts)
|
||||
tags: [local_accounts]
|
||||
|
||||
- name: Create optional additional local service accounts
|
||||
ansible.builtin.user:
|
||||
name: "{{ item.name }}"
|
||||
comment: "{{ item.comment | default('Local service account, managed via Ansible') }}"
|
||||
system: "{{ item.system | default(true) }}"
|
||||
shell: "{{ item.shell | default('/sbin/nologin') }}"
|
||||
create_home: "{{ item.create_home | default(false) }}"
|
||||
state: present
|
||||
loop: "{{ local_service_accounts }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
when: local_service_accounts | length > 0
|
||||
tags: [local_accounts]
|
||||
6
roles/logging_rsyslog/files/README.md
Normal file
6
roles/logging_rsyslog/files/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
Place the customer's internal CA root/intermediate certificate here
|
||||
(filename set via syslog_ca_cert_src in group_vars, default: siem-ca.crt),
|
||||
unless the SIEM/syslog-collector certificate is already signed by a
|
||||
publicly trusted CA already present in the system trust store.
|
||||
|
||||
Only relevant when syslog_ca_cert_enabled: true.
|
||||
9
roles/logging_rsyslog/handlers/main.yml
Normal file
9
roles/logging_rsyslog/handlers/main.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
- name: restart rsyslog
|
||||
ansible.builtin.systemd:
|
||||
name: rsyslog
|
||||
state: restarted
|
||||
|
||||
- name: update CA trust
|
||||
ansible.builtin.command: "{{ logging_rsyslog_ca_trust_update_cmd }}"
|
||||
changed_when: true
|
||||
60
roles/logging_rsyslog/tasks/main.yml
Normal file
60
roles/logging_rsyslog/tasks/main.yml
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
- name: Include OS-family-specific variables
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "{{ ansible_facts['os_family'] }}.yml"
|
||||
- "default.yml"
|
||||
tags: [logging_rsyslog, always]
|
||||
|
||||
- name: Install rsyslog with TLS support
|
||||
ansible.builtin.package:
|
||||
name:
|
||||
- rsyslog
|
||||
- rsyslog-gnutls
|
||||
state: present
|
||||
when: syslog_use_tls | bool
|
||||
tags: [logging_rsyslog]
|
||||
|
||||
- name: Install rsyslog without TLS support
|
||||
ansible.builtin.package:
|
||||
name: rsyslog
|
||||
state: present
|
||||
when: not syslog_use_tls | bool
|
||||
tags: [logging_rsyslog]
|
||||
|
||||
- name: Distribute internal CA certificate for SIEM TLS connection
|
||||
ansible.builtin.copy:
|
||||
src: "{{ syslog_ca_cert_src }}"
|
||||
dest: "{{ logging_rsyslog_ca_cert_dest }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: syslog_ca_cert_enabled | bool
|
||||
notify: update CA trust
|
||||
tags: [logging_rsyslog]
|
||||
|
||||
- name: Deploy forwarding configuration
|
||||
ansible.builtin.template:
|
||||
src: 60-forward-siem.conf.j2
|
||||
dest: /etc/rsyslog.d/60-forward-siem.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: restart rsyslog
|
||||
tags: [logging_rsyslog]
|
||||
|
||||
- name: Deploy logrotate configuration
|
||||
ansible.builtin.template:
|
||||
src: logrotate-cis.j2
|
||||
dest: /etc/logrotate.d/rsyslog-cis
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
tags: [logging_rsyslog]
|
||||
|
||||
- name: Enable and start rsyslog
|
||||
ansible.builtin.systemd:
|
||||
name: rsyslog
|
||||
enabled: true
|
||||
state: started
|
||||
tags: [logging_rsyslog]
|
||||
24
roles/logging_rsyslog/templates/60-forward-siem.conf.j2
Normal file
24
roles/logging_rsyslog/templates/60-forward-siem.conf.j2
Normal file
@@ -0,0 +1,24 @@
|
||||
# {{ ansible_managed }}
|
||||
{% if syslog_use_tls %}
|
||||
$DefaultNetstreamDriver gtls
|
||||
$DefaultNetstreamDriverCAFile /etc/pki/tls/certs/ca-bundle.crt
|
||||
$ActionSendStreamDriverMode 1
|
||||
$ActionSendStreamDriverAuthMode x509/name
|
||||
$ActionSendStreamDriverPermittedPeer {{ syslog_collector_host }}
|
||||
|
||||
$ActionQueueType LinkedList
|
||||
$ActionQueueFileName siem_fwd_queue
|
||||
$ActionQueueMaxDiskSpace 1g
|
||||
$ActionQueueSaveOnShutdown on
|
||||
$ActionResumeRetryCount -1
|
||||
|
||||
auth,authpriv.*;kern.*;cron.* @@(o){{ syslog_collector_host }}:{{ syslog_collector_port }}
|
||||
{% else %}
|
||||
$ActionQueueType LinkedList
|
||||
$ActionQueueFileName siem_fwd_queue
|
||||
$ActionQueueMaxDiskSpace 1g
|
||||
$ActionQueueSaveOnShutdown on
|
||||
$ActionResumeRetryCount -1
|
||||
|
||||
auth,authpriv.*;kern.*;cron.* @{{ syslog_collector_host }}:{{ syslog_collector_port }}
|
||||
{% endif %}
|
||||
14
roles/logging_rsyslog/templates/logrotate-cis.j2
Normal file
14
roles/logging_rsyslog/templates/logrotate-cis.j2
Normal file
@@ -0,0 +1,14 @@
|
||||
# {{ ansible_managed }}
|
||||
/var/log/messages /var/log/secure /var/log/maillog /var/log/cron /var/log/spooler {
|
||||
weekly
|
||||
rotate 12
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
create 0640 root root
|
||||
sharedscripts
|
||||
postrotate
|
||||
/usr/bin/systemctl kill -s HUP rsyslog.service >/dev/null 2>&1 || true
|
||||
endscript
|
||||
}
|
||||
3
roles/logging_rsyslog/vars/Debian.yml
Normal file
3
roles/logging_rsyslog/vars/Debian.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
logging_rsyslog_ca_cert_dest: /usr/local/share/ca-certificates/siem-ca.crt
|
||||
logging_rsyslog_ca_trust_update_cmd: update-ca-certificates
|
||||
3
roles/logging_rsyslog/vars/RedHat.yml
Normal file
3
roles/logging_rsyslog/vars/RedHat.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
logging_rsyslog_ca_cert_dest: /etc/pki/ca-trust/source/anchors/siem-ca.crt
|
||||
logging_rsyslog_ca_trust_update_cmd: update-ca-trust extract
|
||||
63
roles/pam_hardening/tasks/main.yml
Normal file
63
roles/pam_hardening/tasks/main.yml
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
- name: Include OS-family-specific variables
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "{{ ansible_facts['os_family'] }}.yml"
|
||||
- "default.yml"
|
||||
tags: [pam_hardening, always]
|
||||
|
||||
- name: Ensure pwquality library is installed
|
||||
ansible.builtin.package:
|
||||
name: "{{ pam_hardening_pwquality_package }}"
|
||||
state: present
|
||||
tags: [pam_hardening]
|
||||
|
||||
- name: Set password complexity requirements
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/security/pwquality.conf
|
||||
regexp: "^#?\\s*{{ item.key }}\\s*="
|
||||
line: "{{ item.key }} = {{ item.value }}"
|
||||
loop:
|
||||
- { key: "minlen", value: "{{ pam_pwquality_minlen }}" }
|
||||
- { key: "dcredit", value: "{{ pam_pwquality_dcredit }}" }
|
||||
- { key: "ucredit", value: "{{ pam_pwquality_ucredit }}" }
|
||||
- { key: "lcredit", value: "{{ pam_pwquality_lcredit }}" }
|
||||
- { key: "ocredit", value: "{{ pam_pwquality_ocredit }}" }
|
||||
- { key: "retry", value: "{{ pam_pwquality_retry }}" }
|
||||
tags: [pam_hardening]
|
||||
|
||||
- name: Enable pam_faillock via authselect (RedHat family)
|
||||
ansible.builtin.command: authselect enable-feature with-faillock
|
||||
register: pam_hardening_faillock_enable
|
||||
changed_when: "'already enabled' not in pam_hardening_faillock_enable.stderr"
|
||||
failed_when: false
|
||||
when: ansible_facts['os_family'] == "RedHat"
|
||||
tags: [pam_hardening]
|
||||
|
||||
- name: Check whether pam_faillock is already enabled (Debian family)
|
||||
ansible.builtin.command: grep -q pam_faillock.so /etc/pam.d/common-auth
|
||||
register: pam_hardening_faillock_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: ansible_facts['os_family'] == "Debian"
|
||||
tags: [pam_hardening]
|
||||
|
||||
- name: Enable pam_faillock via pam-auth-update (Debian family)
|
||||
ansible.builtin.command: pam-auth-update --enable faillock
|
||||
register: pam_hardening_faillock_debian
|
||||
changed_when: true
|
||||
failed_when: false
|
||||
when:
|
||||
- ansible_facts['os_family'] == "Debian"
|
||||
- pam_hardening_faillock_check.rc != 0
|
||||
tags: [pam_hardening]
|
||||
|
||||
- name: Set account lockout thresholds
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/security/faillock.conf
|
||||
regexp: "^#?\\s*{{ item.key }}\\s*="
|
||||
line: "{{ item.key }} = {{ item.value }}"
|
||||
loop:
|
||||
- { key: "deny", value: "{{ pam_faillock_deny }}" }
|
||||
- { key: "unlock_time", value: "{{ pam_faillock_unlock_time }}" }
|
||||
tags: [pam_hardening]
|
||||
2
roles/pam_hardening/vars/Debian.yml
Normal file
2
roles/pam_hardening/vars/Debian.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
---
|
||||
pam_hardening_pwquality_package: libpam-pwquality
|
||||
2
roles/pam_hardening/vars/RedHat.yml
Normal file
2
roles/pam_hardening/vars/RedHat.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
---
|
||||
pam_hardening_pwquality_package: libpwquality
|
||||
90
roles/patch_mgmt/tasks/main.yml
Normal file
90
roles/patch_mgmt/tasks/main.yml
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
- name: Include OS-family-specific variables
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "{{ ansible_facts['os_family'] }}.yml"
|
||||
- "default.yml"
|
||||
tags: [patch_mgmt, always]
|
||||
|
||||
- name: Install patch management packages
|
||||
ansible.builtin.package:
|
||||
name: "{{ patch_mgmt_packages }}"
|
||||
state: present
|
||||
tags: [patch_mgmt]
|
||||
|
||||
- name: Deploy dnf-automatic configuration (RedHat family)
|
||||
ansible.builtin.template:
|
||||
src: automatic.conf.j2
|
||||
dest: /etc/dnf/automatic.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: ansible_facts['os_family'] == "RedHat"
|
||||
tags: [patch_mgmt]
|
||||
|
||||
- name: Enable dnf-automatic download timer (RedHat family)
|
||||
ansible.builtin.systemd:
|
||||
name: dnf-automatic.timer
|
||||
enabled: true
|
||||
state: started
|
||||
when: ansible_facts['os_family'] == "RedHat"
|
||||
tags: [patch_mgmt]
|
||||
|
||||
- name: Deploy unattended-upgrades allowed-origins configuration (Debian family)
|
||||
ansible.builtin.template:
|
||||
src: 50unattended-upgrades.j2
|
||||
dest: /etc/apt/apt.conf.d/50unattended-upgrades
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: ansible_facts['os_family'] == "Debian"
|
||||
tags: [patch_mgmt]
|
||||
|
||||
- name: Deploy apt periodic update/upgrade configuration (Debian family)
|
||||
ansible.builtin.template:
|
||||
src: 20auto-upgrades.j2
|
||||
dest: /etc/apt/apt.conf.d/20auto-upgrades
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: ansible_facts['os_family'] == "Debian"
|
||||
tags: [patch_mgmt]
|
||||
|
||||
- name: Enable apt-daily timers (Debian family)
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: true
|
||||
state: started
|
||||
loop:
|
||||
- apt-daily.timer
|
||||
- apt-daily-upgrade.timer
|
||||
when: ansible_facts['os_family'] == "Debian"
|
||||
tags: [patch_mgmt]
|
||||
|
||||
- name: Deploy reboot-required check script
|
||||
ansible.builtin.template:
|
||||
src: check-reboot-needed.sh.j2
|
||||
dest: /usr/local/sbin/check-reboot-needed.sh
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
tags: [patch_mgmt]
|
||||
|
||||
- name: Schedule daily reboot-required check
|
||||
ansible.builtin.cron:
|
||||
name: "Check for pending reboot after patching"
|
||||
special_time: daily
|
||||
job: "/usr/local/sbin/check-reboot-needed.sh"
|
||||
user: root
|
||||
tags: [patch_mgmt]
|
||||
|
||||
- name: Schedule automatic reboot in maintenance window (non-critical systems only)
|
||||
ansible.builtin.cron:
|
||||
name: "Automatic reboot in maintenance window"
|
||||
weekday: "{{ patch_reboot_day }}"
|
||||
hour: "{{ patch_reboot_time.split(':')[0] }}"
|
||||
minute: "{{ patch_reboot_time.split(':')[1] }}"
|
||||
job: "/usr/local/sbin/check-reboot-needed.sh --auto-reboot"
|
||||
user: root
|
||||
when: inventory_hostname in patch_reboot_window_hosts
|
||||
tags: [patch_mgmt]
|
||||
5
roles/patch_mgmt/templates/20auto-upgrades.j2
Normal file
5
roles/patch_mgmt/templates/20auto-upgrades.j2
Normal file
@@ -0,0 +1,5 @@
|
||||
// {{ ansible_managed }}
|
||||
APT::Periodic::Update-Package-Lists "1";
|
||||
APT::Periodic::Download-Upgradeable-Packages "1";
|
||||
APT::Periodic::Unattended-Upgrade "{{ '1' if dnf_automatic_apply_security else '0' }}";
|
||||
APT::Periodic::AutocleanInterval "7";
|
||||
11
roles/patch_mgmt/templates/50unattended-upgrades.j2
Normal file
11
roles/patch_mgmt/templates/50unattended-upgrades.j2
Normal file
@@ -0,0 +1,11 @@
|
||||
// {{ ansible_managed }}
|
||||
Unattended-Upgrade::Allowed-Origins {
|
||||
"${distro_id}:${distro_codename}-security";
|
||||
{% if dnf_automatic_apply_other %}
|
||||
"${distro_id}:${distro_codename}-updates";
|
||||
{% endif %}
|
||||
};
|
||||
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
||||
Unattended-Upgrade::Automatic-Reboot "false";
|
||||
Unattended-Upgrade::Mail "root";
|
||||
Unattended-Upgrade::MailReport "on-change";
|
||||
17
roles/patch_mgmt/templates/automatic.conf.j2
Normal file
17
roles/patch_mgmt/templates/automatic.conf.j2
Normal file
@@ -0,0 +1,17 @@
|
||||
# {{ ansible_managed }}
|
||||
[commands]
|
||||
upgrade_type = {{ 'security' if not dnf_automatic_apply_other else 'default' }}
|
||||
random_sleep = 360
|
||||
download_updates = yes
|
||||
apply_updates = {{ 'yes' if dnf_automatic_apply_security else 'no' }}
|
||||
|
||||
[emitters]
|
||||
emit_via = stdio
|
||||
|
||||
[email]
|
||||
email_from = dnf-automatic@{{ ansible_domain | default('example.corp') }}
|
||||
email_to = root
|
||||
email_host = localhost
|
||||
|
||||
[base]
|
||||
debuglevel = 1
|
||||
25
roles/patch_mgmt/templates/check-reboot-needed.sh.j2
Normal file
25
roles/patch_mgmt/templates/check-reboot-needed.sh.j2
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
# {{ ansible_managed }}
|
||||
set -euo pipefail
|
||||
|
||||
{% if ansible_facts['os_family'] == "RedHat" %}
|
||||
reboot_required() {
|
||||
! /usr/bin/needs-restarting -r >/dev/null 2>&1
|
||||
}
|
||||
{% else %}
|
||||
reboot_required() {
|
||||
test -f /var/run/reboot-required
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
if reboot_required; then
|
||||
logger -t check-reboot-needed "Reboot required on {{ inventory_hostname }} after security update."
|
||||
echo "Reboot required on {{ inventory_hostname }}." | mail -s "Reboot required: {{ inventory_hostname }}" root || true
|
||||
|
||||
if [[ "${1:-}" == "--auto-reboot" ]]; then
|
||||
logger -t check-reboot-needed "Performing automatic reboot (scheduled maintenance window)."
|
||||
/usr/sbin/shutdown -r +1 "Automatic reboot after security update (maintenance window)"
|
||||
fi
|
||||
else
|
||||
logger -t check-reboot-needed "No reboot required on {{ inventory_hostname }}."
|
||||
fi
|
||||
4
roles/patch_mgmt/vars/Debian.yml
Normal file
4
roles/patch_mgmt/vars/Debian.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
patch_mgmt_packages:
|
||||
- unattended-upgrades
|
||||
- apt-listchanges
|
||||
4
roles/patch_mgmt/vars/RedHat.yml
Normal file
4
roles/patch_mgmt/vars/RedHat.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
patch_mgmt_packages:
|
||||
- dnf-automatic
|
||||
- dnf-utils
|
||||
122
roles/repo_management/tasks/main.yml
Normal file
122
roles/repo_management/tasks/main.yml
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
- name: Ensure python3-debian is installed (required by deb822_repository)
|
||||
ansible.builtin.package:
|
||||
name: python3-debian
|
||||
state: present
|
||||
when: ansible_facts['os_family'] == "Debian"
|
||||
tags: [repo_management]
|
||||
|
||||
- name: Configure internal/custom yum repositories (RedHat family)
|
||||
ansible.builtin.yum_repository:
|
||||
name: "{{ item.name }}"
|
||||
description: "{{ item.description | default(item.name) }}"
|
||||
baseurl: "{{ item.baseurl }}"
|
||||
gpgcheck: "{{ item.gpgcheck | default(true) }}"
|
||||
gpgkey: "{{ item.gpgkey | default(omit) }}"
|
||||
enabled: "{{ item.enabled | default(true) }}"
|
||||
loop: "{{ custom_yum_repos }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
when: ansible_facts['os_family'] == "RedHat" and custom_yum_repos | length > 0
|
||||
tags: [repo_management]
|
||||
|
||||
- name: Configure internal/custom apt repositories (Debian family)
|
||||
ansible.builtin.deb822_repository:
|
||||
name: "{{ item.name }}"
|
||||
types: deb
|
||||
uris: "{{ item.uris }}"
|
||||
suites: "{{ item.suites }}"
|
||||
components: "{{ item.components | default(omit) }}"
|
||||
signed_by: "{{ item.signed_by | default(omit) }}"
|
||||
loop: "{{ custom_apt_repos }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
when: ansible_facts['os_family'] == "Debian" and custom_apt_repos | length > 0
|
||||
tags: [repo_management]
|
||||
|
||||
- name: Probe internet connectivity
|
||||
ansible.builtin.uri:
|
||||
url: "{{ repo_mgmt_internet_check_url }}"
|
||||
method: HEAD
|
||||
timeout: 5
|
||||
status_code: [200, 301, 302, 403]
|
||||
register: repo_management_internet_probe
|
||||
failed_when: false
|
||||
when: repo_mgmt_check_internet | bool
|
||||
tags: [repo_management]
|
||||
|
||||
- name: Set internet availability fact
|
||||
ansible.builtin.set_fact:
|
||||
repo_management_internet_available: "{{ (repo_management_internet_probe is defined) and (repo_management_internet_probe.status is defined) and (repo_management_internet_probe.status | default(0) in [200, 301, 302, 403]) }}"
|
||||
tags: [repo_management]
|
||||
|
||||
- name: Report internet availability
|
||||
ansible.builtin.debug:
|
||||
msg: "Internet access on {{ inventory_hostname }}: {{ 'available' if repo_management_internet_available else 'not available / check disabled' }}"
|
||||
tags: [repo_management]
|
||||
|
||||
- name: Enable CRB repository (RedHat family, internet access required)
|
||||
community.general.dnf_config_manager:
|
||||
name: crb
|
||||
state: enabled
|
||||
when:
|
||||
- ansible_facts['os_family'] == "RedHat"
|
||||
- repo_management_internet_available | default(false)
|
||||
- crb_enabled | bool
|
||||
tags: [repo_management]
|
||||
|
||||
- name: Install EPEL release package (RedHat family, internet access required)
|
||||
ansible.builtin.dnf:
|
||||
name: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ epel_major_version }}.noarch.rpm"
|
||||
state: present
|
||||
disable_gpg_check: false
|
||||
when:
|
||||
- ansible_facts['os_family'] == "RedHat"
|
||||
- repo_management_internet_available | default(false)
|
||||
- epel_enabled | bool
|
||||
tags: [repo_management]
|
||||
|
||||
- name: Warn if EPEL/CRB requested but no internet access
|
||||
ansible.builtin.debug:
|
||||
msg: "EPEL/CRB requested but no internet access detected on {{ inventory_hostname }}. Add an internal mirror via custom_yum_repos instead."
|
||||
when:
|
||||
- ansible_facts['os_family'] == "RedHat"
|
||||
- not (repo_management_internet_available | default(false))
|
||||
- (epel_enabled | bool) or (crb_enabled | bool)
|
||||
tags: [repo_management]
|
||||
|
||||
- name: Enable universe/multiverse components (Ubuntu, internet access required)
|
||||
ansible.builtin.deb822_repository:
|
||||
name: ubuntu-universe-multiverse
|
||||
types: deb
|
||||
uris: "http://archive.ubuntu.com/ubuntu"
|
||||
suites:
|
||||
- "{{ ansible_facts['distribution_release'] }}"
|
||||
- "{{ ansible_facts['distribution_release'] }}-updates"
|
||||
components:
|
||||
- universe
|
||||
- multiverse
|
||||
when:
|
||||
- ansible_facts['distribution'] == "Ubuntu"
|
||||
- repo_management_internet_available | default(false)
|
||||
- ubuntu_universe_enabled | bool
|
||||
tags: [repo_management]
|
||||
|
||||
- name: Enable backports component (Debian, internet access required)
|
||||
ansible.builtin.deb822_repository:
|
||||
name: debian-backports
|
||||
types: deb
|
||||
uris: "http://deb.debian.org/debian"
|
||||
suites: "{{ ansible_facts['distribution_release'] }}-backports"
|
||||
components: main
|
||||
when:
|
||||
- ansible_facts['distribution'] == "Debian"
|
||||
- repo_management_internet_available | default(false)
|
||||
- debian_backports_enabled | bool
|
||||
tags: [repo_management]
|
||||
|
||||
- name: Refresh apt cache after repository changes (Debian family)
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
when: ansible_facts['os_family'] == "Debian"
|
||||
tags: [repo_management]
|
||||
8
roles/selinux_config/defaults/main.yml
Normal file
8
roles/selinux_config/defaults/main.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
selinux_config_booleans: []
|
||||
# selinux_config_booleans:
|
||||
# - { name: "httpd_can_network_connect", state: true }
|
||||
|
||||
selinux_config_fcontexts: []
|
||||
# selinux_config_fcontexts:
|
||||
# - { target: "/opt/myapp/data(/.*)?", setype: "httpd_sys_rw_content_t" }
|
||||
2
roles/selinux_config/handlers/main.yml
Normal file
2
roles/selinux_config/handlers/main.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
---
|
||||
# No handlers needed - restorecon is applied directly in tasks/main.yml
|
||||
58
roles/selinux_config/tasks/main.yml
Normal file
58
roles/selinux_config/tasks/main.yml
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
- name: Verify this role only runs on RedHat-family hosts
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts['os_family'] == "RedHat"
|
||||
fail_msg: "selinux_config only supports RedHat-family hosts. Use apparmor_config for Debian family."
|
||||
tags: [selinux_config, always]
|
||||
|
||||
- name: Install SELinux management tools
|
||||
ansible.builtin.dnf:
|
||||
name:
|
||||
- policycoreutils-python-utils
|
||||
- setroubleshoot-server
|
||||
- checkpolicy
|
||||
state: present
|
||||
tags: [selinux_config]
|
||||
|
||||
- name: Set SELinux mode and policy
|
||||
ansible.posix.selinux:
|
||||
state: "{{ selinux_state }}"
|
||||
policy: "{{ selinux_policy }}"
|
||||
tags: [selinux_config]
|
||||
|
||||
- name: Set application-specific SELinux booleans
|
||||
ansible.posix.seboolean:
|
||||
name: "{{ item.name }}"
|
||||
state: "{{ item.state }}"
|
||||
persistent: true
|
||||
loop: "{{ selinux_config_booleans }}"
|
||||
when: selinux_config_booleans | length > 0
|
||||
tags: [selinux_config]
|
||||
|
||||
- name: Set application-specific file contexts
|
||||
community.general.sefcontext:
|
||||
target: "{{ item.target }}"
|
||||
setype: "{{ item.setype }}"
|
||||
state: present
|
||||
loop: "{{ selinux_config_fcontexts }}"
|
||||
register: selinux_config_fcontext_result
|
||||
tags: [selinux_config]
|
||||
|
||||
- name: Apply restorecon to changed file context paths
|
||||
ansible.builtin.command: "restorecon -Rv {{ item.item.target | regex_replace('\\(/\\.\\*\\)\\?$', '') }}"
|
||||
loop: "{{ selinux_config_fcontext_result.results }}"
|
||||
when: selinux_config_fcontext_result.changed and item.changed
|
||||
changed_when: true
|
||||
tags: [selinux_config]
|
||||
|
||||
- name: Query current SELinux status
|
||||
ansible.builtin.command: getenforce
|
||||
register: selinux_config_current
|
||||
changed_when: false
|
||||
tags: [selinux_config]
|
||||
|
||||
- name: Report SELinux status
|
||||
ansible.builtin.debug:
|
||||
msg: "SELinux status on {{ inventory_hostname }}: {{ selinux_config_current.stdout }}"
|
||||
tags: [selinux_config]
|
||||
5
roles/ssh_hardening/handlers/main.yml
Normal file
5
roles/ssh_hardening/handlers/main.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: restart sshd
|
||||
ansible.builtin.systemd:
|
||||
name: sshd
|
||||
state: restarted
|
||||
27
roles/ssh_hardening/tasks/main.yml
Normal file
27
roles/ssh_hardening/tasks/main.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
- name: Deploy sshd_config
|
||||
ansible.builtin.template:
|
||||
src: sshd_config.j2
|
||||
dest: /etc/ssh/sshd_config
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
validate: "/usr/sbin/sshd -t -f %s"
|
||||
notify: restart sshd
|
||||
tags: [ssh_hardening]
|
||||
|
||||
- name: Create sshd_config.d drop-in directory for future exceptions
|
||||
ansible.builtin.file:
|
||||
path: /etc/ssh/sshd_config.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
tags: [ssh_hardening]
|
||||
|
||||
- name: Enable and start sshd
|
||||
ansible.builtin.systemd:
|
||||
name: sshd
|
||||
enabled: true
|
||||
state: started
|
||||
tags: [ssh_hardening]
|
||||
34
roles/ssh_hardening/templates/sshd_config.j2
Normal file
34
roles/ssh_hardening/templates/sshd_config.j2
Normal file
@@ -0,0 +1,34 @@
|
||||
# {{ ansible_managed }}
|
||||
Port {{ ssh_port }}
|
||||
Protocol 2
|
||||
|
||||
PermitRootLogin no
|
||||
PasswordAuthentication no
|
||||
PubkeyAuthentication yes
|
||||
ChallengeResponseAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
UsePAM yes
|
||||
MaxAuthTries {{ ssh_max_auth_tries }}
|
||||
AllowGroups {{ ssh_allow_groups }}
|
||||
|
||||
ClientAliveInterval {{ ssh_client_alive_interval }}
|
||||
ClientAliveCountMax {{ ssh_client_alive_count_max }}
|
||||
LoginGraceTime 30
|
||||
MaxSessions 4
|
||||
MaxStartups 10:30:60
|
||||
|
||||
X11Forwarding no
|
||||
AllowTcpForwarding no
|
||||
AllowAgentForwarding no
|
||||
PermitTunnel no
|
||||
GatewayPorts no
|
||||
|
||||
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
|
||||
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
|
||||
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512
|
||||
|
||||
Banner /etc/issue.net
|
||||
LogLevel VERBOSE
|
||||
SyslogFacility AUTHPRIV
|
||||
|
||||
Include /etc/ssh/sshd_config.d/*.conf
|
||||
4
roles/sudo_rbac/handlers/main.yml
Normal file
4
roles/sudo_rbac/handlers/main.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
- name: reload auditd rules
|
||||
ansible.builtin.command: augenrules --load
|
||||
changed_when: true
|
||||
31
roles/sudo_rbac/tasks/main.yml
Normal file
31
roles/sudo_rbac/tasks/main.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
- name: Deploy sudoers.d drop-in file per AD group
|
||||
ansible.builtin.template:
|
||||
src: sudoers_group.j2
|
||||
dest: "/etc/sudoers.d/10-{{ item.group | lower }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0440"
|
||||
validate: "visudo -cf %s"
|
||||
loop: "{{ sudo_rbac_rules }}"
|
||||
loop_control:
|
||||
label: "{{ item.group }}"
|
||||
tags: [sudo_rbac]
|
||||
|
||||
- name: Restrict direct root console login
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/securetty
|
||||
state: absent
|
||||
regexp: "^tty[1-9]$"
|
||||
tags: [sudo_rbac, cis_hardening]
|
||||
|
||||
- name: Audit rule for privileged command usage
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/audit/rules.d/50-privileged.rules
|
||||
create: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
line: "-a exit,always -F arch=b64 -C euid!=uid -F auid!=unset -S execve -k privileged"
|
||||
notify: reload auditd rules
|
||||
tags: [sudo_rbac, cis_hardening]
|
||||
2
roles/sudo_rbac/templates/sudoers_group.j2
Normal file
2
roles/sudo_rbac/templates/sudoers_group.j2
Normal file
@@ -0,0 +1,2 @@
|
||||
# {{ ansible_managed }} - do not edit manually, changes will be overwritten
|
||||
%{{ item.group }} ALL=(ALL) {{ 'NOPASSWD:' if item.nopasswd else '' }} {{ item.commands }}
|
||||
Reference in New Issue
Block a user