Add Debian 13 and Ubuntu 26.04 CIS-inspired hardening playbook
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
- name: Apply CIS-inspired Debian and Ubuntu hardening controls
|
||||||
|
hosts: linux
|
||||||
|
become: true
|
||||||
|
gather_facts: true
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: cis-debian-ubuntu-hardening
|
||||||
|
tags:
|
||||||
|
- cis
|
||||||
|
- hardening
|
||||||
|
|
||||||
|
post_tasks:
|
||||||
|
- name: Show validation summary
|
||||||
|
ansible.builtin.debug:
|
||||||
|
var: cis_validation_summary
|
||||||
|
when: cis_validation_summary is defined
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
|
- postcheck
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# CIS-Inspired Debian and Ubuntu Hardening
|
||||||
|
|
||||||
|
This role applies a small, practical set of CIS-inspired operational hardening controls for Debian and Ubuntu servers. It is intentionally readable, conservative, and suitable as a baseline for production environments that still need local review.
|
||||||
|
|
||||||
|
## Supported OS
|
||||||
|
|
||||||
|
- Debian 13 Trixie
|
||||||
|
- Ubuntu Server 26.04 LTS
|
||||||
|
|
||||||
|
Unsupported distributions and versions fail during precheck before hardening tasks run.
|
||||||
|
|
||||||
|
## Implemented Areas
|
||||||
|
|
||||||
|
- SSH daemon hardening with a validated drop-in configuration
|
||||||
|
- Legacy network package removal
|
||||||
|
- Optional installation and enablement of `auditd`, `chrony`, `rsyslog`, and `sudo`
|
||||||
|
- Kernel network sysctl hardening
|
||||||
|
- Basic audit rule examples, disabled by default
|
||||||
|
- Sudo `use_pty` and optional sudo logfile configuration
|
||||||
|
- Logging service checks without replacing existing logging configuration
|
||||||
|
- Filesystem mount option recommendations, disabled by default
|
||||||
|
|
||||||
|
## Safety Philosophy
|
||||||
|
|
||||||
|
The defaults are intended to be operationally safe:
|
||||||
|
|
||||||
|
- Check mode is supported.
|
||||||
|
- SSH password authentication remains enabled by default.
|
||||||
|
- Filesystem mount option management is disabled by default.
|
||||||
|
- Audit rules are not written unless explicitly enabled.
|
||||||
|
- Services are enabled only when the matching feature is enabled and the service exists.
|
||||||
|
- Existing logging configuration is not replaced.
|
||||||
|
|
||||||
|
This role does not implement the full CIS benchmark and is not a CIS certification implementation.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Run in check mode first:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook playbooks/cis-debian-ubuntu-hardening.yml --check --diff
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply the full baseline:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook playbooks/cis-debian-ubuntu-hardening.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
Run only selected areas:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook playbooks/cis-debian-ubuntu-hardening.yml --tags precheck,ssh,postcheck
|
||||||
|
ansible-playbook playbooks/cis-debian-ubuntu-hardening.yml --tags packages,services
|
||||||
|
ansible-playbook playbooks/cis-debian-ubuntu-hardening.yml --tags sudo,logging
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Variables
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
cis_disable_root_login: true
|
||||||
|
cis_disable_password_auth: false
|
||||||
|
cis_install_auditd: true
|
||||||
|
cis_enable_chrony: true
|
||||||
|
cis_enable_rsyslog: true
|
||||||
|
cis_remove_legacy_packages: true
|
||||||
|
cis_enable_sysctl_hardening: true
|
||||||
|
cis_manage_mount_options: false
|
||||||
|
cis_manage_audit_rules: false
|
||||||
|
|
||||||
|
cis_ssh_max_auth_tries: 4
|
||||||
|
cis_ssh_login_grace_time: 60
|
||||||
|
cis_ssh_client_alive_interval: 300
|
||||||
|
cis_ssh_client_alive_count_max: 3
|
||||||
|
|
||||||
|
cis_sudo_use_pty: true
|
||||||
|
cis_sudo_logfile: /var/log/sudo.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable audit rules only after reviewing the examples:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
cis_manage_audit_rules: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable mount option persistence only after reviewing each filesystem target:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
cis_manage_mount_options: true
|
||||||
|
```
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
cis_disable_root_login: true
|
||||||
|
cis_disable_password_auth: false
|
||||||
|
cis_install_auditd: true
|
||||||
|
cis_enable_chrony: true
|
||||||
|
cis_enable_rsyslog: true
|
||||||
|
cis_remove_legacy_packages: true
|
||||||
|
cis_enable_sysctl_hardening: true
|
||||||
|
cis_manage_mount_options: false
|
||||||
|
cis_manage_audit_rules: false
|
||||||
|
|
||||||
|
cis_ssh_max_auth_tries: 4
|
||||||
|
cis_ssh_login_grace_time: 60
|
||||||
|
cis_ssh_client_alive_interval: 300
|
||||||
|
cis_ssh_client_alive_count_max: 3
|
||||||
|
|
||||||
|
cis_sudo_use_pty: true
|
||||||
|
cis_sudo_logfile: /var/log/sudo.log
|
||||||
|
|
||||||
|
cis_min_root_free_mb: 1024
|
||||||
|
|
||||||
|
cis_supported_debian_major_version: "13"
|
||||||
|
cis_supported_ubuntu_version: "26.04"
|
||||||
|
|
||||||
|
cis_ssh_service_name: ssh
|
||||||
|
cis_ssh_dropin_path: /etc/ssh/sshd_config.d/50-cis-debian-ubuntu-hardening.conf
|
||||||
|
cis_ssh_main_config_path: /etc/ssh/sshd_config
|
||||||
|
|
||||||
|
cis_hardening_packages:
|
||||||
|
- chrony
|
||||||
|
- rsyslog
|
||||||
|
- sudo
|
||||||
|
|
||||||
|
cis_audit_packages:
|
||||||
|
- auditd
|
||||||
|
- audispd-plugins
|
||||||
|
|
||||||
|
cis_legacy_packages:
|
||||||
|
- telnet
|
||||||
|
- rsh-client
|
||||||
|
- rsh-server
|
||||||
|
- talk
|
||||||
|
- talkd
|
||||||
|
- nis
|
||||||
|
|
||||||
|
cis_sysctl_settings:
|
||||||
|
net.ipv4.ip_forward: 0
|
||||||
|
net.ipv4.conf.all.send_redirects: 0
|
||||||
|
net.ipv4.conf.default.send_redirects: 0
|
||||||
|
net.ipv4.conf.all.accept_source_route: 0
|
||||||
|
net.ipv4.conf.default.accept_source_route: 0
|
||||||
|
net.ipv4.conf.all.accept_redirects: 0
|
||||||
|
net.ipv4.conf.default.accept_redirects: 0
|
||||||
|
net.ipv4.tcp_syncookies: 1
|
||||||
|
|
||||||
|
cis_sysctl_config_file: /etc/sysctl.d/60-cis-debian-ubuntu-hardening.conf
|
||||||
|
|
||||||
|
cis_audit_rules_path: /etc/audit/rules.d/50-cis-debian-ubuntu-hardening.rules
|
||||||
|
cis_audit_rules:
|
||||||
|
- "-w /etc/passwd -p wa -k identity"
|
||||||
|
- "-w /etc/shadow -p wa -k identity"
|
||||||
|
- "-w /etc/group -p wa -k identity"
|
||||||
|
- "-w /etc/gshadow -p wa -k identity"
|
||||||
|
- "-w /etc/sudoers -p wa -k scope"
|
||||||
|
- "-w /etc/sudoers.d/ -p wa -k scope"
|
||||||
|
|
||||||
|
cis_sudoers_dropin_path: /etc/sudoers.d/50-cis-debian-ubuntu-hardening
|
||||||
|
|
||||||
|
cis_mount_option_targets:
|
||||||
|
- path: /tmp
|
||||||
|
options:
|
||||||
|
- nodev
|
||||||
|
- nosuid
|
||||||
|
- noexec
|
||||||
|
- path: /var/tmp
|
||||||
|
options:
|
||||||
|
- nodev
|
||||||
|
- nosuid
|
||||||
|
- noexec
|
||||||
|
- path: /home
|
||||||
|
options:
|
||||||
|
- nodev
|
||||||
|
|
||||||
|
cis_container_virtualization_types:
|
||||||
|
- container
|
||||||
|
- docker
|
||||||
|
- lxc
|
||||||
|
- podman
|
||||||
|
- containerd
|
||||||
|
- systemd-nspawn
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
- name: Validate ssh configuration
|
||||||
|
ansible.builtin.command: sshd -t
|
||||||
|
changed_when: false
|
||||||
|
listen: validate ssh
|
||||||
|
|
||||||
|
- name: Restart ssh service safely
|
||||||
|
ansible.builtin.service:
|
||||||
|
name: "{{ cis_ssh_service_name }}"
|
||||||
|
state: restarted
|
||||||
|
listen: restart ssh
|
||||||
|
|
||||||
|
- name: Restart auditd
|
||||||
|
ansible.builtin.service:
|
||||||
|
name: auditd
|
||||||
|
state: restarted
|
||||||
|
use: service
|
||||||
|
listen: restart auditd
|
||||||
|
|
||||||
|
- name: Restart rsyslog
|
||||||
|
ansible.builtin.service:
|
||||||
|
name: rsyslog
|
||||||
|
state: restarted
|
||||||
|
listen: restart rsyslog
|
||||||
|
|
||||||
|
- name: Restart chrony
|
||||||
|
ansible.builtin.service:
|
||||||
|
name: chrony
|
||||||
|
state: restarted
|
||||||
|
listen: restart chrony
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
- name: Ensure audit rules directory exists
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /etc/audit/rules.d
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0750"
|
||||||
|
|
||||||
|
- name: Report audit rules management mode
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
{{ 'OK: Baseline audit rule management is enabled.'
|
||||||
|
if cis_manage_audit_rules | bool
|
||||||
|
else 'WARNING: Audit rules are not managed because cis_manage_audit_rules is false.' }}
|
||||||
|
|
||||||
|
- name: Install baseline audit rules when explicitly enabled
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ cis_audit_rules_path }}"
|
||||||
|
line: "{{ item }}"
|
||||||
|
create: true
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0640"
|
||||||
|
loop: "{{ cis_audit_rules }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item }}"
|
||||||
|
when: cis_manage_audit_rules | bool
|
||||||
|
notify: restart auditd
|
||||||
|
|
||||||
|
- name: Ensure auditd is enabled and running
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: auditd
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
when:
|
||||||
|
- cis_install_auditd | bool
|
||||||
|
- "'auditd.service' in ansible_facts.services"
|
||||||
|
- not cis_container_detected | default(false) | bool
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
- name: Gather current mount facts
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_current_mount_paths: "{{ ansible_mounts | map(attribute='mount') | list }}"
|
||||||
|
|
||||||
|
- name: Report filesystem mount option mode
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
{{ 'OK: Mount option management is enabled for configured targets.'
|
||||||
|
if cis_manage_mount_options | bool
|
||||||
|
else 'WARNING: Mount option management is disabled. No production filesystems will be remounted.' }}
|
||||||
|
|
||||||
|
- name: Show configured mount option recommendations
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "Review {{ item.path }} for options: {{ item.options | join(',') }}"
|
||||||
|
loop: "{{ cis_mount_option_targets }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.path }}"
|
||||||
|
when: not cis_manage_mount_options | bool
|
||||||
|
|
||||||
|
- name: Persist configured mount options without remounting
|
||||||
|
ansible.posix.mount:
|
||||||
|
path: "{{ item.path }}"
|
||||||
|
src: "{{ cis_mount_fact.device }}"
|
||||||
|
fstype: "{{ cis_mount_fact.fstype }}"
|
||||||
|
state: present
|
||||||
|
opts: "{{ ((cis_mount_fact.options | default('defaults')).split(',') + item.options) | unique | join(',') }}"
|
||||||
|
loop: "{{ cis_mount_option_targets }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.path }}"
|
||||||
|
vars:
|
||||||
|
cis_mount_fact: "{{ ansible_mounts | selectattr('mount', 'equalto', item.path) | list | first | default({}) }}"
|
||||||
|
when:
|
||||||
|
- cis_manage_mount_options | bool
|
||||||
|
- item.path in cis_current_mount_paths
|
||||||
|
register: cis_mount_option_results
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
- name: Ensure rsyslog is installed
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: rsyslog
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
cache_valid_time: 3600
|
||||||
|
when: cis_enable_rsyslog | bool
|
||||||
|
|
||||||
|
- name: Ensure rsyslog is enabled and running
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: rsyslog
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
when:
|
||||||
|
- cis_enable_rsyslog | bool
|
||||||
|
- not cis_container_detected | default(false) | bool
|
||||||
|
|
||||||
|
- name: Validate journald configuration file presence
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /etc/systemd/journald.conf
|
||||||
|
register: cis_journald_conf
|
||||||
|
|
||||||
|
- name: Report journald configuration status
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
{{ 'OK: /etc/systemd/journald.conf is present.'
|
||||||
|
if cis_journald_conf.stat.exists else 'WARNING: /etc/systemd/journald.conf was not found.' }}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
- name: Run platform safety prechecks
|
||||||
|
ansible.builtin.import_tasks: precheck.yml
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
|
- precheck
|
||||||
|
|
||||||
|
- name: Manage packages
|
||||||
|
ansible.builtin.import_tasks: packages.yml
|
||||||
|
tags:
|
||||||
|
- packages
|
||||||
|
|
||||||
|
- name: Harden SSH daemon configuration
|
||||||
|
ansible.builtin.import_tasks: ssh.yml
|
||||||
|
tags:
|
||||||
|
- ssh
|
||||||
|
|
||||||
|
- name: Apply kernel network hardening
|
||||||
|
ansible.builtin.import_tasks: sysctl.yml
|
||||||
|
when: cis_enable_sysctl_hardening | bool
|
||||||
|
tags:
|
||||||
|
- sysctl
|
||||||
|
|
||||||
|
- name: Manage baseline services
|
||||||
|
ansible.builtin.import_tasks: services.yml
|
||||||
|
tags:
|
||||||
|
- services
|
||||||
|
|
||||||
|
- name: Configure Linux audit controls
|
||||||
|
ansible.builtin.import_tasks: audit.yml
|
||||||
|
when: cis_install_auditd | bool
|
||||||
|
tags:
|
||||||
|
- audit
|
||||||
|
|
||||||
|
- name: Configure sudo controls
|
||||||
|
ansible.builtin.import_tasks: sudo.yml
|
||||||
|
tags:
|
||||||
|
- sudo
|
||||||
|
|
||||||
|
- name: Configure logging controls
|
||||||
|
ansible.builtin.import_tasks: logging.yml
|
||||||
|
tags:
|
||||||
|
- logging
|
||||||
|
|
||||||
|
- name: Review filesystem mount options
|
||||||
|
ansible.builtin.import_tasks: filesystem.yml
|
||||||
|
tags:
|
||||||
|
- filesystem
|
||||||
|
|
||||||
|
- name: Run validation postchecks
|
||||||
|
ansible.builtin.import_tasks: postcheck.yml
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
|
- postcheck
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
- name: Remove legacy network packages
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: "{{ cis_legacy_packages }}"
|
||||||
|
state: absent
|
||||||
|
purge: false
|
||||||
|
when: cis_remove_legacy_packages | bool
|
||||||
|
|
||||||
|
- name: Build enabled hardening package list
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_enabled_hardening_packages: >-
|
||||||
|
{{
|
||||||
|
['sudo']
|
||||||
|
+ (['chrony'] if cis_enable_chrony | bool else [])
|
||||||
|
+ (['rsyslog'] if cis_enable_rsyslog | bool else [])
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Install baseline hardening packages
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: "{{ cis_enabled_hardening_packages }}"
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
cache_valid_time: 3600
|
||||||
|
|
||||||
|
- name: Install auditd when enabled
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: auditd
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
cache_valid_time: 3600
|
||||||
|
when: cis_install_auditd | bool
|
||||||
|
|
||||||
|
- name: Install audispd plugins when available
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: audispd-plugins
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
cache_valid_time: 3600
|
||||||
|
register: cis_audispd_plugins_install
|
||||||
|
failed_when: false
|
||||||
|
when: cis_install_auditd | bool
|
||||||
|
|
||||||
|
- name: Report audispd plugins availability
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "WARNING: audispd-plugins was not installed; package may be unavailable for this release."
|
||||||
|
when:
|
||||||
|
- cis_install_auditd | bool
|
||||||
|
- cis_audispd_plugins_install is failed
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
- name: Validate ssh effective configuration syntax
|
||||||
|
ansible.builtin.command: sshd -t
|
||||||
|
register: cis_sshd_validate
|
||||||
|
changed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
- name: Read sysctl values for validation
|
||||||
|
ansible.builtin.command: "sysctl -n {{ item.key }}"
|
||||||
|
loop: "{{ cis_sysctl_settings | dict2items }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.key }}"
|
||||||
|
register: cis_sysctl_validation
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
when:
|
||||||
|
- cis_enable_sysctl_hardening | bool
|
||||||
|
- not cis_container_detected | default(false) | bool
|
||||||
|
|
||||||
|
- name: Gather installed package facts
|
||||||
|
ansible.builtin.package_facts:
|
||||||
|
manager: auto
|
||||||
|
|
||||||
|
- name: Gather final service facts
|
||||||
|
ansible.builtin.service_facts:
|
||||||
|
|
||||||
|
- name: Build service state summary
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_service_state_summary:
|
||||||
|
ssh: "{{ ansible_facts.services['ssh.service'].state | default('not-found') }}"
|
||||||
|
chrony: "{{ ansible_facts.services['chrony.service'].state | default('not-found') }}"
|
||||||
|
auditd: "{{ ansible_facts.services['auditd.service'].state | default('not-found') }}"
|
||||||
|
rsyslog: "{{ ansible_facts.services['rsyslog.service'].state | default('not-found') }}"
|
||||||
|
|
||||||
|
- name: Build package validation summary
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_package_validation_summary:
|
||||||
|
legacy_absent: "{{ cis_legacy_packages | difference(ansible_facts.packages.keys() | list) }}"
|
||||||
|
hardening_present: "{{ (cis_enabled_hardening_packages | default(cis_hardening_packages)) | intersect(ansible_facts.packages.keys() | list) }}"
|
||||||
|
audit_present: "{{ cis_audit_packages | intersect(ansible_facts.packages.keys() | list) }}"
|
||||||
|
|
||||||
|
- name: Build sysctl validation summary
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_sysctl_validation_summary: "{{ cis_sysctl_validation_summary | default({}) | combine({item.item.key: item.stdout | default('unreadable')}) }}"
|
||||||
|
loop: "{{ cis_sysctl_validation.results | default([]) }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.item.key }}"
|
||||||
|
when:
|
||||||
|
- cis_enable_sysctl_hardening | bool
|
||||||
|
- not cis_container_detected | default(false) | bool
|
||||||
|
|
||||||
|
- name: Build mount option change summary
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_mount_option_summary: >-
|
||||||
|
{{
|
||||||
|
cis_mount_option_results.results
|
||||||
|
| default([])
|
||||||
|
| selectattr('changed', 'defined')
|
||||||
|
| selectattr('changed')
|
||||||
|
| map(attribute='item.path')
|
||||||
|
| list
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Publish validation summary
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_validation_summary:
|
||||||
|
benchmark: "CIS-inspired controls for Debian 13 Trixie and Ubuntu Server 26.04 LTS"
|
||||||
|
sshd_config: "{{ 'OK' if cis_sshd_validate.rc == 0 else 'CRITICAL' }}"
|
||||||
|
services: "{{ cis_service_state_summary }}"
|
||||||
|
packages: "{{ cis_package_validation_summary }}"
|
||||||
|
sysctl: "{{ cis_sysctl_validation_summary | default({}) }}"
|
||||||
|
mount_option_updates: "{{ cis_mount_option_summary | default([]) }}"
|
||||||
|
audit_rules_managed: "{{ cis_manage_audit_rules | bool }}"
|
||||||
|
applied_controls:
|
||||||
|
- ssh
|
||||||
|
- packages
|
||||||
|
- sysctl
|
||||||
|
- services
|
||||||
|
- audit
|
||||||
|
- sudo
|
||||||
|
- logging
|
||||||
|
- filesystem
|
||||||
|
|
||||||
|
- name: Show service states
|
||||||
|
ansible.builtin.debug:
|
||||||
|
var: cis_service_state_summary
|
||||||
|
|
||||||
|
- name: Show package validation
|
||||||
|
ansible.builtin.debug:
|
||||||
|
var: cis_package_validation_summary
|
||||||
|
|
||||||
|
- name: Show changed mount options
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
{{ cis_mount_option_summary | default([]) if cis_mount_option_summary | default([]) | length > 0
|
||||||
|
else 'OK: No mount option changes were applied.' }}
|
||||||
|
|
||||||
|
- name: Show applied control summary
|
||||||
|
ansible.builtin.debug:
|
||||||
|
var: cis_validation_summary
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
- name: Determine root filesystem free space
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_root_mount: "{{ ansible_mounts | selectattr('mount', 'equalto', '/') | list | first | default({}) }}"
|
||||||
|
|
||||||
|
- name: Calculate root filesystem free space in MB
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_root_free_mb: "{{ ((cis_root_mount.size_available | default(0) | int) / 1024 / 1024) | round(0, 'floor') | int }}"
|
||||||
|
|
||||||
|
- name: Detect containerized runtime
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_container_detected: >-
|
||||||
|
{{
|
||||||
|
ansible_virtualization_type | default('') in cis_container_virtualization_types
|
||||||
|
or ansible_env.container | default('') | length > 0
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Check for apt
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /usr/bin/apt-get
|
||||||
|
register: cis_apt_check
|
||||||
|
|
||||||
|
- name: Report platform precheck status
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "OK: Facts gathered for {{ ansible_distribution }} {{ ansible_distribution_version }}."
|
||||||
|
- "OK: Root filesystem free space is {{ cis_root_free_mb }} MB."
|
||||||
|
- >-
|
||||||
|
{{ 'OK: apt package manager detected.'
|
||||||
|
if cis_apt_check.stat.exists else 'CRITICAL: apt package manager was not found.' }}
|
||||||
|
- >-
|
||||||
|
{{ 'OK: systemd service manager detected.'
|
||||||
|
if ansible_service_mgr == 'systemd' else 'CRITICAL: systemd service manager is required.' }}
|
||||||
|
- >-
|
||||||
|
{{ 'WARNING: Containerized environment detected; service and kernel controls may be limited.'
|
||||||
|
if cis_container_detected else 'OK: No containerized runtime detected from Ansible facts.' }}
|
||||||
|
|
||||||
|
- name: Fail when operating system is unsupported
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- >-
|
||||||
|
(ansible_distribution == 'Debian'
|
||||||
|
and ansible_distribution_major_version == cis_supported_debian_major_version)
|
||||||
|
or
|
||||||
|
(ansible_distribution == 'Ubuntu'
|
||||||
|
and ansible_distribution_version is version(cis_supported_ubuntu_version, '=='))
|
||||||
|
fail_msg: >-
|
||||||
|
CRITICAL: This role supports only Debian 13 / Trixie and Ubuntu Server 26.04 LTS.
|
||||||
|
Detected {{ ansible_distribution }} {{ ansible_distribution_version }}.
|
||||||
|
success_msg: "OK: Supported Debian/Ubuntu platform detected."
|
||||||
|
|
||||||
|
- name: Fail when systemd is unavailable
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- ansible_service_mgr == 'systemd'
|
||||||
|
fail_msg: "CRITICAL: systemd is required for this operational hardening role."
|
||||||
|
success_msg: "OK: systemd is available."
|
||||||
|
|
||||||
|
- name: Fail when apt is unavailable
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- cis_apt_check.stat.exists
|
||||||
|
fail_msg: "CRITICAL: apt-get is required for this Debian/Ubuntu hardening role."
|
||||||
|
success_msg: "OK: apt-get is available."
|
||||||
|
|
||||||
|
- name: Fail when root filesystem free space is below safety threshold
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- cis_root_free_mb | int >= cis_min_root_free_mb | int
|
||||||
|
fail_msg: >-
|
||||||
|
CRITICAL: Root filesystem has {{ cis_root_free_mb }} MB free.
|
||||||
|
Minimum required free space is {{ cis_min_root_free_mb }} MB.
|
||||||
|
success_msg: "OK: Root filesystem free space meets the safety threshold."
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
- name: Gather service facts
|
||||||
|
ansible.builtin.service_facts:
|
||||||
|
|
||||||
|
- name: Enable chrony service when present and enabled
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: chrony
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
when:
|
||||||
|
- cis_enable_chrony | bool
|
||||||
|
- "'chrony.service' in ansible_facts.services"
|
||||||
|
|
||||||
|
- name: Enable rsyslog service when present and enabled
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: rsyslog
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
when:
|
||||||
|
- cis_enable_rsyslog | bool
|
||||||
|
- "'rsyslog.service' in ansible_facts.services"
|
||||||
|
|
||||||
|
- name: Enable auditd service when present and enabled
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: auditd
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
when:
|
||||||
|
- cis_install_auditd | bool
|
||||||
|
- "'auditd.service' in ansible_facts.services"
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
- name: Ensure sshd drop-in directory exists
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ cis_ssh_dropin_path | dirname }}"
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Ensure sshd hardening drop-in exists
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ cis_ssh_dropin_path }}"
|
||||||
|
state: touch
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
modification_time: preserve
|
||||||
|
access_time: preserve
|
||||||
|
|
||||||
|
- name: Ensure sshd drop-in directory is included
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ cis_ssh_main_config_path }}"
|
||||||
|
regexp: '^Include\s+/etc/ssh/sshd_config\.d/\*\.conf'
|
||||||
|
line: "Include /etc/ssh/sshd_config.d/*.conf"
|
||||||
|
insertbefore: BOF
|
||||||
|
validate: sshd -t -f %s
|
||||||
|
notify:
|
||||||
|
- validate ssh
|
||||||
|
- restart ssh
|
||||||
|
|
||||||
|
- name: Configure SSH root login
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ cis_ssh_dropin_path }}"
|
||||||
|
regexp: '^PermitRootLogin\s+'
|
||||||
|
line: "PermitRootLogin {{ 'no' if cis_disable_root_login | bool else 'prohibit-password' }}"
|
||||||
|
validate: sshd -t -f %s
|
||||||
|
notify:
|
||||||
|
- validate ssh
|
||||||
|
- restart ssh
|
||||||
|
|
||||||
|
- name: Configure SSH empty password restriction
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ cis_ssh_dropin_path }}"
|
||||||
|
regexp: '^PermitEmptyPasswords\s+'
|
||||||
|
line: "PermitEmptyPasswords no"
|
||||||
|
validate: sshd -t -f %s
|
||||||
|
notify:
|
||||||
|
- validate ssh
|
||||||
|
- restart ssh
|
||||||
|
|
||||||
|
- name: Configure SSH password authentication
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ cis_ssh_dropin_path }}"
|
||||||
|
regexp: '^PasswordAuthentication\s+'
|
||||||
|
line: "PasswordAuthentication {{ 'no' if cis_disable_password_auth | bool else 'yes' }}"
|
||||||
|
validate: sshd -t -f %s
|
||||||
|
notify:
|
||||||
|
- validate ssh
|
||||||
|
- restart ssh
|
||||||
|
|
||||||
|
- name: Configure SSH MaxAuthTries
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ cis_ssh_dropin_path }}"
|
||||||
|
regexp: '^MaxAuthTries\s+'
|
||||||
|
line: "MaxAuthTries {{ cis_ssh_max_auth_tries }}"
|
||||||
|
validate: sshd -t -f %s
|
||||||
|
notify:
|
||||||
|
- validate ssh
|
||||||
|
- restart ssh
|
||||||
|
|
||||||
|
- name: Configure SSH LoginGraceTime
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ cis_ssh_dropin_path }}"
|
||||||
|
regexp: '^LoginGraceTime\s+'
|
||||||
|
line: "LoginGraceTime {{ cis_ssh_login_grace_time }}"
|
||||||
|
validate: sshd -t -f %s
|
||||||
|
notify:
|
||||||
|
- validate ssh
|
||||||
|
- restart ssh
|
||||||
|
|
||||||
|
- name: Configure SSH ClientAliveInterval
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ cis_ssh_dropin_path }}"
|
||||||
|
regexp: '^ClientAliveInterval\s+'
|
||||||
|
line: "ClientAliveInterval {{ cis_ssh_client_alive_interval }}"
|
||||||
|
validate: sshd -t -f %s
|
||||||
|
notify:
|
||||||
|
- validate ssh
|
||||||
|
- restart ssh
|
||||||
|
|
||||||
|
- name: Configure SSH ClientAliveCountMax
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ cis_ssh_dropin_path }}"
|
||||||
|
regexp: '^ClientAliveCountMax\s+'
|
||||||
|
line: "ClientAliveCountMax {{ cis_ssh_client_alive_count_max }}"
|
||||||
|
validate: sshd -t -f %s
|
||||||
|
notify:
|
||||||
|
- validate ssh
|
||||||
|
- restart ssh
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
- name: Build sudo hardening directives
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_sudo_directives: >-
|
||||||
|
{{
|
||||||
|
([{'regexp': '^Defaults\s+use_pty', 'line': 'Defaults use_pty'}]
|
||||||
|
if cis_sudo_use_pty | bool else [])
|
||||||
|
+ [{'regexp': '^Defaults\s+logfile=', 'line': 'Defaults logfile="' ~ cis_sudo_logfile ~ '"'}]
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Configure sudo hardening drop-in
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ cis_sudoers_dropin_path }}"
|
||||||
|
regexp: "{{ item.regexp }}"
|
||||||
|
line: "{{ item.line }}"
|
||||||
|
create: true
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0440"
|
||||||
|
validate: /usr/sbin/visudo -cf %s
|
||||||
|
loop: "{{ cis_sudo_directives }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.line }}"
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
- name: Apply CIS-inspired sysctl settings
|
||||||
|
ansible.posix.sysctl:
|
||||||
|
name: "{{ item.key }}"
|
||||||
|
value: "{{ item.value }}"
|
||||||
|
sysctl_file: "{{ cis_sysctl_config_file }}"
|
||||||
|
state: present
|
||||||
|
reload: true
|
||||||
|
loop: "{{ cis_sysctl_settings | dict2items }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.key }}"
|
||||||
|
when: not cis_container_detected | default(false) | bool
|
||||||
|
|
||||||
|
- name: Report skipped sysctl hardening inside containers
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "WARNING: Sysctl hardening skipped because a containerized environment was detected."
|
||||||
|
when: cis_container_detected | default(false) | bool
|
||||||
Reference in New Issue
Block a user