Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| deb12a0b4f | |||
| 02a51f72f9 | |||
| 2fd9c0b5ef | |||
| 75a11f7650 |
@@ -1,5 +1,24 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- CIS-inspired Ansible hardening automation:
|
||||||
|
- RHEL 9 role and playbook.
|
||||||
|
- Debian 13 / Ubuntu 26.04 role and playbook.
|
||||||
|
- IBM AIX 7 role and playbook.
|
||||||
|
- Shared sanitized Ansible inventory defaults for Linux and AIX examples.
|
||||||
|
- Role-level task structure covering pre-checks, SSH, sudo, auditing, logging, services, filesystem controls, platform-specific settings, handlers, and post-check validation.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated repository, `infra-run`, and Ansible README files to describe the new hardening automation instead of placeholder-only Ansible structure.
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- Hardening content is CIS-inspired and intended for portfolio/lab use; production use requires environment-specific review and validation.
|
||||||
|
|
||||||
## [Initial Version]
|
## [Initial Version]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ flowchart TD
|
|||||||
B --> B2["docs"]
|
B --> B2["docs"]
|
||||||
B --> B3["runbooks"]
|
B --> B3["runbooks"]
|
||||||
B --> B4["scripts"]
|
B --> B4["scripts"]
|
||||||
|
B1 --> B11["hardening roles"]
|
||||||
B4 --> B41["bash"]
|
B4 --> B41["bash"]
|
||||||
B4 --> B42["python"]
|
B4 --> B42["python"]
|
||||||
C --> C1["storage"]
|
C --> C1["storage"]
|
||||||
@@ -65,6 +66,12 @@ Veritas VxVM and VCS storage expansion workflow covering new LUN detection, VxVM
|
|||||||
|
|
||||||
GPFS / IBM Spectrum Scale filesystem expansion workflow covering cluster validation, candidate disk discovery, NSD stanza planning, NSD creation, filesystem expansion, optional rebalance, post-checks, and change reporting.
|
GPFS / IBM Spectrum Scale filesystem expansion workflow covering cluster validation, candidate disk discovery, NSD stanza planning, NSD creation, filesystem expansion, optional rebalance, post-checks, and change reporting.
|
||||||
|
|
||||||
|
### Ansible Hardening Toolkit
|
||||||
|
|
||||||
|
[infra-run/ansible/](./infra-run/ansible/)
|
||||||
|
|
||||||
|
CIS-inspired Ansible automation for repeatable operating system hardening across RHEL 9, Debian 13 / Ubuntu 26.04, and IBM AIX 7 targets. The roles are organized around pre-checks, configurable safeguards, SSH and sudo policy, auditing, logging, services, filesystem controls, platform-specific system settings, handlers, and post-change validation.
|
||||||
|
|
||||||
## Repository Structure
|
## Repository Structure
|
||||||
|
|
||||||
- `infra-run` - core operational automation, scripts, runbooks, and infrastructure operations examples.
|
- `infra-run` - core operational automation, scripts, runbooks, and infrastructure operations examples.
|
||||||
@@ -77,6 +84,7 @@ GPFS / IBM Spectrum Scale filesystem expansion workflow covering cluster validat
|
|||||||
- Pre-check, change, and post-check workflow.
|
- Pre-check, change, and post-check workflow.
|
||||||
- Real-world scenarios, not tutorials.
|
- Real-world scenarios, not tutorials.
|
||||||
- Minimal but practical tooling.
|
- Minimal but practical tooling.
|
||||||
|
- Configurable automation with sanitized defaults and explicit overrides.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|||||||
+9
-2
@@ -16,12 +16,19 @@ flowchart TD
|
|||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
- `ansible` - placeholder structure for infrastructure automation and testing.
|
- `ansible` - infrastructure automation with CIS-inspired hardening roles and playbooks.
|
||||||
- `docs` - supporting technical notes and written documentation.
|
- `docs` - supporting technical notes and written documentation.
|
||||||
- `runbooks` - procedural operational guides.
|
- `runbooks` - procedural operational guides.
|
||||||
- `scripts` - executable tooling for operations and diagnostics.
|
- `scripts` - executable tooling for operations and diagnostics.
|
||||||
|
|
||||||
|
## Current Automation
|
||||||
|
|
||||||
|
- RHEL 9 CIS-inspired hardening role and playbook.
|
||||||
|
- Debian 13 / Ubuntu 26.04 CIS-inspired hardening role and playbook.
|
||||||
|
- IBM AIX 7 CIS-inspired hardening role and playbook.
|
||||||
|
- Shared sanitized inventory defaults for Linux and AIX examples.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- This folder reflects the structure of a production-oriented operations repository.
|
- This folder reflects the structure of a production-oriented operations repository.
|
||||||
- Current implementation is strongest in the Bash tooling under `scripts/bash`.
|
- Current implementation includes Bash operational toolkits and Ansible hardening automation.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# infra-run/ansible
|
# infra-run/ansible
|
||||||
|
|
||||||
This directory reserves the Ansible automation area for future infrastructure-as-code content. It is organized around the standard separation of inventory, roles, playbooks, collections, and tests.
|
This directory contains Ansible automation for infrastructure operations and OS hardening. It is organized around the standard separation of inventory, roles, playbooks, collections, and tests.
|
||||||
|
|
||||||
## Diagram
|
## Diagram
|
||||||
|
|
||||||
@@ -17,13 +17,20 @@ flowchart TD
|
|||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
- `collections` - vendored or custom Ansible collections.
|
- `collections` - collection requirements for supported automation targets.
|
||||||
- `inventory` - environment inventory definitions and variables.
|
- `inventory` - sanitized Linux and AIX inventory examples with shared defaults.
|
||||||
- `playbooks` - executable playbooks for repeatable operations.
|
- `playbooks` - executable CIS-inspired hardening playbooks.
|
||||||
- `roles` - reusable automation roles.
|
- `roles` - reusable hardening roles for supported operating systems.
|
||||||
- `tests` - validation and test harnesses for Ansible content.
|
- `tests` - validation and test harnesses for Ansible content.
|
||||||
|
|
||||||
|
## Hardening Coverage
|
||||||
|
|
||||||
|
- `cis-rhel9-hardening` - RHEL 9 baseline tasks for packages, services, SSH, sudo, sysctl, auditing, logging, filesystem controls, and validation.
|
||||||
|
- `cis-debian-ubuntu-hardening` - Debian 13 and Ubuntu 26.04 baseline tasks for apt packages, services, SSH, sudo, sysctl, auditing, logging, filesystem controls, and validation.
|
||||||
|
- `cis-aix7-hardening` - IBM AIX 7 baseline tasks for SSH, sudo, audit, logging, cron, users, password policy, network settings, filesystem controls, services, and validation.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- The directory layout is already prepared for growth even where content is still placeholder-only.
|
- Roles are CIS-inspired examples intended for portfolio and lab use, not a drop-in compliance certification.
|
||||||
- This keeps the repository ready for automation expansion alongside the existing script toolkits.
|
- Defaults are sanitized and configurable through inventory or `--extra-vars`.
|
||||||
|
- Run platform-specific playbooks against appropriate test hosts before adapting them to production environments.
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[defaults]
|
||||||
|
inventory = inventory/hosts.yml
|
||||||
|
roles_path = roles
|
||||||
|
host_key_checking = False
|
||||||
|
retry_files_enabled = False
|
||||||
|
stdout_callback = yaml
|
||||||
|
|
||||||
|
[privilege_escalation]
|
||||||
|
become = True
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
collections:
|
||||||
|
- name: ansible.posix
|
||||||
|
- name: community.general
|
||||||
@@ -16,8 +16,15 @@ flowchart TD
|
|||||||
|
|
||||||
- `group_vars` - variables applied at group or environment level.
|
- `group_vars` - variables applied at group or environment level.
|
||||||
- `host_vars` - variables tailored to individual nodes.
|
- `host_vars` - variables tailored to individual nodes.
|
||||||
|
- `hosts.yml` - sanitized example groups for Linux and AIX hardening targets.
|
||||||
|
|
||||||
|
## Current Inventory Shape
|
||||||
|
|
||||||
|
- `linux` - local example host for Linux hardening playbooks.
|
||||||
|
- `aix` - empty sanitized group ready for AIX host definitions.
|
||||||
|
- `group_vars/all.yml` - shared hardening defaults such as NTP servers, SSH behavior, audit/logging toggles, sysctl hardening, and optional mount management.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- The structure is present even though the repository currently keeps this area sanitized and mostly empty.
|
- Inventory values are intentionally sanitized.
|
||||||
- This is the natural companion to future playbooks and roles under `infra-run/ansible`.
|
- Override defaults per host, per group, or per run before applying any hardening playbook.
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
timezone: UTC
|
||||||
|
|
||||||
|
cis_ntp_servers:
|
||||||
|
- 0.rhel.pool.ntp.org
|
||||||
|
- 1.rhel.pool.ntp.org
|
||||||
|
- 2.rhel.pool.ntp.org
|
||||||
|
- 3.rhel.pool.ntp.org
|
||||||
|
|
||||||
|
# Operational defaults. Override per run with --extra-vars or inventory when needed.
|
||||||
|
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
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
linux:
|
||||||
|
hosts:
|
||||||
|
localhost:
|
||||||
|
ansible_connection: local
|
||||||
|
|
||||||
|
aix:
|
||||||
|
hosts: {}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# infra-run/ansible/playbooks
|
# infra-run/ansible/playbooks
|
||||||
|
|
||||||
This directory is intended for executable Ansible playbooks that coordinate roles, inventories, and operational tasks. In the current portfolio state it acts as a prepared entry point for future automation runs.
|
This directory contains executable Ansible playbooks that coordinate roles, inventories, and operational hardening tasks.
|
||||||
|
|
||||||
## Diagram
|
## Diagram
|
||||||
|
|
||||||
@@ -14,5 +14,7 @@ flowchart TD
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Playbooks belong here when the repository expands beyond script-first operations.
|
- `cis-rhel9-hardening.yml` applies the RHEL 9 CIS-inspired hardening role to Linux inventory targets.
|
||||||
- The directory currently contains only placeholder content.
|
- `cis-debian-ubuntu-hardening.yml` applies the Debian 13 / Ubuntu 26.04 CIS-inspired hardening role to Linux inventory targets.
|
||||||
|
- `cis-aix7-hardening.yml` applies the IBM AIX 7 CIS-inspired hardening role to AIX inventory targets.
|
||||||
|
- Use the sanitized inventory under `../inventory/` as a starting point and override defaults per environment.
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
- name: Apply CIS-inspired IBM AIX 7 hardening controls
|
||||||
|
hosts: aix
|
||||||
|
become: true
|
||||||
|
gather_facts: true
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: cis-aix7-hardening
|
||||||
|
tags:
|
||||||
|
- cis
|
||||||
|
- aix7
|
||||||
|
- hardening
|
||||||
|
|
||||||
|
post_tasks:
|
||||||
|
- name: Show AIX hardening validation summary
|
||||||
|
ansible.builtin.debug:
|
||||||
|
var: cis_aix_validation_summary
|
||||||
|
when: cis_aix_validation_summary is defined
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
|
- postcheck
|
||||||
@@ -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,20 @@
|
|||||||
|
---
|
||||||
|
- name: Apply CIS-inspired RHEL 9 hardening controls
|
||||||
|
hosts: linux
|
||||||
|
become: true
|
||||||
|
gather_facts: true
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: cis-rhel9-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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# infra-run/ansible/roles
|
# infra-run/ansible/roles
|
||||||
|
|
||||||
This folder is reserved for reusable Ansible roles. Roles make it possible to organize configuration logic into predictable, testable units that can be shared across playbooks.
|
This folder contains reusable Ansible roles. Roles organize configuration logic into predictable, testable units that can be shared across playbooks.
|
||||||
|
|
||||||
## Diagram
|
## Diagram
|
||||||
|
|
||||||
@@ -10,9 +10,18 @@ flowchart TD
|
|||||||
A --> C["monitoring"]
|
A --> C["monitoring"]
|
||||||
A --> D["storage"]
|
A --> D["storage"]
|
||||||
A --> E["security"]
|
A --> E["security"]
|
||||||
|
E --> E1["cis-rhel9-hardening"]
|
||||||
|
E --> E2["cis-debian-ubuntu-hardening"]
|
||||||
|
E --> E3["cis-aix7-hardening"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Current Roles
|
||||||
|
|
||||||
|
- `cis-rhel9-hardening` - CIS-inspired RHEL 9 baseline with package, service, SSH, sudo, sysctl, audit, logging, filesystem, and validation tasks.
|
||||||
|
- `cis-debian-ubuntu-hardening` - CIS-inspired Debian 13 and Ubuntu 26.04 baseline with apt, service, SSH, sudo, sysctl, audit, logging, filesystem, and validation tasks.
|
||||||
|
- `cis-aix7-hardening` - CIS-inspired IBM AIX 7 baseline with SSH, sudo, audit, logging, cron, user, password, network, filesystem, service, and validation tasks.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- The role layout is not yet populated, but the structure is in place for future automation modules.
|
- Each role includes defaults, task includes, handlers where needed, and role-specific README guidance.
|
||||||
- Keeping a README here documents intent even before role code exists.
|
- The hardening content is sanitized for portfolio use and should be reviewed against site policy before production use.
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# cis-aix7-hardening
|
||||||
|
|
||||||
|
Operational IBM AIX 7.x hardening role inspired by CIS Benchmark 1.2.0 and common enterprise Unix security practices.
|
||||||
|
|
||||||
|
Reference: https://www.cisecurity.org/benchmark/aix
|
||||||
|
|
||||||
|
This role is intended for infrastructure and security operations teams that manage production AIX estates. It favors readable, conservative controls over broad benchmark coverage.
|
||||||
|
|
||||||
|
## Supported OS
|
||||||
|
|
||||||
|
- IBM AIX 7.x
|
||||||
|
|
||||||
|
## Implemented Areas
|
||||||
|
|
||||||
|
- Platform prechecks for AIX 7.x, SRC, SSH, audit tooling, required commands, disk safety, and baseline security state.
|
||||||
|
- SSH daemon hardening in `/etc/ssh/sshd_config` with validation through `sshd -t`.
|
||||||
|
- Account and password controls through AIX-native `lssec`, `chsec`, and `pwdadm`.
|
||||||
|
- Network tunable validation and optional hardening through `no`, with optional `nfso` support.
|
||||||
|
- SRC-aware service checks and safe inetd legacy service disablement.
|
||||||
|
- Filesystem review for JFS2, world-writable directories, and invalid owners or groups.
|
||||||
|
- Syslog and audit validation, with audit enablement disabled by default.
|
||||||
|
- Cron and at permission hardening under `/var/adm/cron`.
|
||||||
|
- Sudo defaults with validation through `visudo -cf` when sudo is present.
|
||||||
|
- Postcheck reporting for SSH, services, network values, and password policy.
|
||||||
|
|
||||||
|
## AIX Operational Notes
|
||||||
|
|
||||||
|
AIX is not Linux. This role does not assume systemd, sysctl, Linux package managers, or Linux service paths. Service operations use SRC commands such as `lssrc`, `startsrc`, `stopsrc`, and `refresh`.
|
||||||
|
|
||||||
|
AIX environments vary heavily between enterprises. Filesystem layout, OpenSSH source, sudo packaging, audit classes, NFS tuning, and security policy ownership should be validated before production rollout.
|
||||||
|
|
||||||
|
## Safety Philosophy
|
||||||
|
|
||||||
|
- Defaults are conservative.
|
||||||
|
- Audit enablement is opt-in with `cis_enable_audit`.
|
||||||
|
- Filesystem mount option management is opt-in with `cis_manage_mount_options`.
|
||||||
|
- SSH password authentication is not disabled by default.
|
||||||
|
- Native AIX security files are updated with targeted `chsec` calls instead of wholesale replacement.
|
||||||
|
- Check mode is supported where practical, though AIX command modules may still need read-only probes for validation.
|
||||||
|
|
||||||
|
## Check Mode Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook playbooks/cis-aix7-hardening.yml --check
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook playbooks/cis-aix7-hardening.yml --check --tags precheck,ssh,postcheck
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tag Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook playbooks/cis-aix7-hardening.yml --tags precheck
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook playbooks/cis-aix7-hardening.yml --tags ssh,password_policy,network
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook playbooks/cis-aix7-hardening.yml --tags audit -e cis_enable_audit=true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Warning
|
||||||
|
|
||||||
|
This is not a full CIS certification implementation and does not implement the entire CIS AIX benchmark. It is a practical CIS-inspired baseline that should be reviewed by infrastructure, security, and application owners before production enforcement.
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
---
|
||||||
|
cis_benchmark_version: "1.2.0"
|
||||||
|
|
||||||
|
cis_disable_root_login: true
|
||||||
|
cis_disable_password_auth: false
|
||||||
|
cis_enable_network_hardening: true
|
||||||
|
cis_enable_password_policy: true
|
||||||
|
cis_enable_audit: false
|
||||||
|
cis_manage_mount_options: 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_ssh_config_path: /etc/ssh/sshd_config
|
||||||
|
cis_sshd_test_command: sshd -t
|
||||||
|
|
||||||
|
cis_min_root_free_mb: 1024
|
||||||
|
|
||||||
|
cis_password_minlen: 14
|
||||||
|
cis_password_histsize: 10
|
||||||
|
cis_password_maxage_weeks: 12
|
||||||
|
cis_password_minalpha: 1
|
||||||
|
cis_password_minother: 1
|
||||||
|
cis_password_maxrepeats: 2
|
||||||
|
cis_password_minage_weeks: 1
|
||||||
|
cis_login_retries: 5
|
||||||
|
cis_login_lockout: 30
|
||||||
|
|
||||||
|
cis_required_commands:
|
||||||
|
- lsattr
|
||||||
|
- chdev
|
||||||
|
- lssrc
|
||||||
|
- chsec
|
||||||
|
- lssec
|
||||||
|
- pwdadm
|
||||||
|
- "no"
|
||||||
|
- audit
|
||||||
|
- cron
|
||||||
|
|
||||||
|
cis_ssh_candidate_paths:
|
||||||
|
- /usr/sbin/sshd
|
||||||
|
- /usr/bin/sshd
|
||||||
|
- /opt/freeware/sbin/sshd
|
||||||
|
- /opt/freeware/bin/sshd
|
||||||
|
|
||||||
|
cis_network_no_settings:
|
||||||
|
ipforwarding: "0"
|
||||||
|
ipsendredirects: "0"
|
||||||
|
ipignoreredirects: "1"
|
||||||
|
ipsrcrouteforward: "0"
|
||||||
|
clean_partial_conns: "1"
|
||||||
|
tcp_pmtu_discover: "0"
|
||||||
|
|
||||||
|
cis_network_nfso_settings: {}
|
||||||
|
|
||||||
|
cis_legacy_inetd_services:
|
||||||
|
- telnet
|
||||||
|
- shell
|
||||||
|
- login
|
||||||
|
- exec
|
||||||
|
- comsat
|
||||||
|
- talk
|
||||||
|
- ntalk
|
||||||
|
- tftp
|
||||||
|
- uucp
|
||||||
|
- finger
|
||||||
|
|
||||||
|
cis_src_subsystems:
|
||||||
|
- sshd
|
||||||
|
- inetd
|
||||||
|
- syslogd
|
||||||
|
- audit
|
||||||
|
|
||||||
|
cis_mount_option_targets:
|
||||||
|
- path: /tmp
|
||||||
|
options:
|
||||||
|
- nosuid
|
||||||
|
- path: /var/tmp
|
||||||
|
options:
|
||||||
|
- nosuid
|
||||||
|
|
||||||
|
cis_manage_sudo: true
|
||||||
|
cis_sudoers_path: /etc/sudoers
|
||||||
|
cis_sudo_logfile: /var/log/sudo.log
|
||||||
|
cis_sudo_use_pty: true
|
||||||
|
|
||||||
|
cis_cron_allow_path: /var/adm/cron/cron.allow
|
||||||
|
cis_cron_deny_path: /var/adm/cron/cron.deny
|
||||||
|
cis_at_allow_path: /var/adm/cron/at.allow
|
||||||
|
cis_at_deny_path: /var/adm/cron/at.deny
|
||||||
|
cis_cron_directories:
|
||||||
|
- /var/adm/cron
|
||||||
|
- /var/spool/cron
|
||||||
|
- /var/spool/cron/crontabs
|
||||||
|
|
||||||
|
cis_syslog_config_path: /etc/syslog.conf
|
||||||
|
cis_audit_config_path: /etc/security/audit/config
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
- name: Validate sshd configuration
|
||||||
|
ansible.builtin.command: "{{ cis_sshd_test_command }}"
|
||||||
|
changed_when: false
|
||||||
|
listen: validate sshd
|
||||||
|
|
||||||
|
- name: Restart sshd using SRC
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
set -o pipefail
|
||||||
|
if lssrc -s sshd >/dev/null 2>&1; then
|
||||||
|
stopsrc -s sshd >/dev/null 2>&1 || true
|
||||||
|
startsrc -s sshd
|
||||||
|
fi
|
||||||
|
args:
|
||||||
|
executable: /bin/ksh
|
||||||
|
changed_when: true
|
||||||
|
listen: restart sshd
|
||||||
|
|
||||||
|
- name: Refresh inetd
|
||||||
|
ansible.builtin.command: refresh -s inetd
|
||||||
|
changed_when: true
|
||||||
|
failed_when: false
|
||||||
|
listen: refresh inetd
|
||||||
|
|
||||||
|
- name: Refresh syslog
|
||||||
|
ansible.builtin.command: refresh -s syslogd
|
||||||
|
changed_when: true
|
||||||
|
failed_when: false
|
||||||
|
listen: refresh syslog
|
||||||
|
|
||||||
|
- name: Restart audit subsystem
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
set -o pipefail
|
||||||
|
if lssrc -s audit >/dev/null 2>&1; then
|
||||||
|
stopsrc -s audit >/dev/null 2>&1 || true
|
||||||
|
startsrc -s audit
|
||||||
|
else
|
||||||
|
audit start
|
||||||
|
fi
|
||||||
|
args:
|
||||||
|
executable: /bin/ksh
|
||||||
|
changed_when: true
|
||||||
|
when: cis_enable_audit | bool
|
||||||
|
listen: restart audit
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
- name: Validate AIX audit configuration file
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ cis_audit_config_path }}"
|
||||||
|
register: cis_aix_audit_config
|
||||||
|
|
||||||
|
- name: Collect AIX audit query status
|
||||||
|
ansible.builtin.command: audit query
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_audit_status
|
||||||
|
|
||||||
|
- name: Enable AIX audit subsystem when explicitly configured
|
||||||
|
ansible.builtin.command: audit start
|
||||||
|
changed_when: true
|
||||||
|
when:
|
||||||
|
- cis_enable_audit | bool
|
||||||
|
- cis_aix_audit_config.stat.exists
|
||||||
|
- cis_aix_audit_status.rc != 0 or 'auditing off' in (cis_aix_audit_status.stdout | default('') | lower)
|
||||||
|
notify: restart audit
|
||||||
|
|
||||||
|
- name: Report audit status
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- >-
|
||||||
|
{{ 'OK: AIX audit configuration file exists.'
|
||||||
|
if cis_aix_audit_config.stat.exists else 'WARNING: AIX audit configuration file was not found.' }}
|
||||||
|
- >-
|
||||||
|
{{ 'OK: Audit enablement is explicitly allowed by cis_enable_audit.'
|
||||||
|
if cis_enable_audit | bool else 'WARNING: Audit enablement is disabled by default; validation only was performed.' }}
|
||||||
|
- "OK: audit query rc={{ cis_aix_audit_status.rc }} output={{ cis_aix_audit_status.stdout | default('') }}"
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
- name: Ensure cron and at control files exist with safe ownership
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: touch
|
||||||
|
owner: root
|
||||||
|
group: cron
|
||||||
|
mode: "0600"
|
||||||
|
modification_time: preserve
|
||||||
|
access_time: preserve
|
||||||
|
loop:
|
||||||
|
- "{{ cis_cron_allow_path }}"
|
||||||
|
- "{{ cis_at_allow_path }}"
|
||||||
|
|
||||||
|
- name: Ensure deny files are not world readable when present
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
owner: root
|
||||||
|
group: cron
|
||||||
|
mode: "0600"
|
||||||
|
loop:
|
||||||
|
- "{{ cis_cron_deny_path }}"
|
||||||
|
- "{{ cis_at_deny_path }}"
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Secure cron directories when present
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: cron
|
||||||
|
mode: "0750"
|
||||||
|
loop: "{{ cis_cron_directories }}"
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Validate cron SRC state
|
||||||
|
ansible.builtin.command: lssrc -s cron
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_cron_state
|
||||||
|
|
||||||
|
- name: Report cron and at hardening status
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "OK: cron.allow and at.allow ownership and permissions are managed."
|
||||||
|
- >-
|
||||||
|
{{ 'OK: cron SRC subsystem exists.'
|
||||||
|
if cis_aix_cron_state.rc == 0 else 'WARNING: cron SRC subsystem was not found.' }}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
- name: Build mounted filesystem list from gathered facts
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_aix_mount_points: "{{ ansible_mounts | map(attribute='mount') | list }}"
|
||||||
|
|
||||||
|
- name: Validate JFS2 filesystems
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
set -o pipefail
|
||||||
|
lsfs -q | awk '/vfs[[:space:]]*=[[:space:]]*jfs2/{print prev} {prev=$0}'
|
||||||
|
args:
|
||||||
|
executable: /bin/ksh
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_jfs2_filesystems
|
||||||
|
|
||||||
|
- name: Review configured mount option targets
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
OK: Mount option management is disabled by default.
|
||||||
|
Review target {{ item.path }} for options {{ item.options | join(', ') }} before production rollout.
|
||||||
|
loop: "{{ cis_mount_option_targets }}"
|
||||||
|
when: not cis_manage_mount_options | bool
|
||||||
|
|
||||||
|
- name: Apply configured mount options only when explicitly enabled
|
||||||
|
ansible.builtin.command: "chfs -a options={{ item.options | join(',') }} {{ item.path }}"
|
||||||
|
changed_when: true
|
||||||
|
loop: "{{ cis_mount_option_targets }}"
|
||||||
|
when:
|
||||||
|
- cis_manage_mount_options | bool
|
||||||
|
- item.path in cis_aix_mount_points
|
||||||
|
|
||||||
|
- name: Identify world-writable directories on local filesystems
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
set -o pipefail
|
||||||
|
find / -xdev -type d -perm -0002 -print 2>/dev/null | head -200
|
||||||
|
args:
|
||||||
|
executable: /bin/ksh
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_world_writable_dirs
|
||||||
|
|
||||||
|
- name: Identify files without valid owner or group on local filesystems
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
set -o pipefail
|
||||||
|
find / -xdev \( -nouser -o -nogroup \) -print 2>/dev/null | head -200
|
||||||
|
args:
|
||||||
|
executable: /bin/ksh
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_unowned_files
|
||||||
|
|
||||||
|
- name: Report filesystem review findings
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "OK: JFS2 filesystem review completed."
|
||||||
|
- "WARNING: World-writable directories found: {{ cis_aix_world_writable_dirs.stdout_lines | default([]) }}"
|
||||||
|
- "WARNING: Files without valid owner/group found: {{ cis_aix_unowned_files.stdout_lines | default([]) }}"
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
- name: Collect syslog SRC state
|
||||||
|
ansible.builtin.command: lssrc -s syslogd
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_syslog_state
|
||||||
|
|
||||||
|
- name: Ensure syslog configuration exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ cis_syslog_config_path }}"
|
||||||
|
register: cis_aix_syslog_config
|
||||||
|
|
||||||
|
- name: Start syslogd when installed but inactive
|
||||||
|
ansible.builtin.command: startsrc -s syslogd
|
||||||
|
changed_when: true
|
||||||
|
when:
|
||||||
|
- cis_aix_syslog_state.rc == 0
|
||||||
|
- "'active' not in cis_aix_syslog_state.stdout"
|
||||||
|
|
||||||
|
- name: Validate syslog configuration has active entries
|
||||||
|
ansible.builtin.shell: "awk 'NF && $1 !~ /^#/ {found=1} END {exit found ? 0 : 1}' {{ cis_syslog_config_path }}"
|
||||||
|
args:
|
||||||
|
executable: /bin/ksh
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_syslog_has_rules
|
||||||
|
when: cis_aix_syslog_config.stat.exists
|
||||||
|
|
||||||
|
- name: Report logging status
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- >-
|
||||||
|
{{ 'OK: syslogd SRC subsystem exists.'
|
||||||
|
if cis_aix_syslog_state.rc == 0 else 'WARNING: syslogd SRC subsystem was not found.' }}
|
||||||
|
- >-
|
||||||
|
{{ 'OK: syslog configuration has active rules.'
|
||||||
|
if cis_aix_syslog_has_rules.rc | default(1) == 0
|
||||||
|
else 'WARNING: syslog configuration has no active rules or could not be validated.' }}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
- name: Run AIX platform safety prechecks
|
||||||
|
ansible.builtin.import_tasks: precheck.yml
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
|
- precheck
|
||||||
|
|
||||||
|
- name: Harden AIX SSH daemon configuration
|
||||||
|
ansible.builtin.import_tasks: ssh.yml
|
||||||
|
tags:
|
||||||
|
- ssh
|
||||||
|
|
||||||
|
- name: Apply AIX user account controls
|
||||||
|
ansible.builtin.import_tasks: users.yml
|
||||||
|
tags:
|
||||||
|
- users
|
||||||
|
|
||||||
|
- name: Apply AIX password policy controls
|
||||||
|
ansible.builtin.import_tasks: password_policy.yml
|
||||||
|
when: cis_enable_password_policy | bool
|
||||||
|
tags:
|
||||||
|
- password_policy
|
||||||
|
|
||||||
|
- name: Apply AIX network hardening controls
|
||||||
|
ansible.builtin.import_tasks: network.yml
|
||||||
|
when: cis_enable_network_hardening | bool
|
||||||
|
tags:
|
||||||
|
- network
|
||||||
|
|
||||||
|
- name: Manage AIX baseline services
|
||||||
|
ansible.builtin.import_tasks: services.yml
|
||||||
|
tags:
|
||||||
|
- services
|
||||||
|
|
||||||
|
- name: Review AIX filesystem controls
|
||||||
|
ansible.builtin.import_tasks: filesystem.yml
|
||||||
|
tags:
|
||||||
|
- filesystem
|
||||||
|
|
||||||
|
- name: Validate AIX logging controls
|
||||||
|
ansible.builtin.import_tasks: logging.yml
|
||||||
|
tags:
|
||||||
|
- logging
|
||||||
|
|
||||||
|
- name: Validate AIX audit controls
|
||||||
|
ansible.builtin.import_tasks: audit.yml
|
||||||
|
tags:
|
||||||
|
- audit
|
||||||
|
|
||||||
|
- name: Harden AIX cron and at controls
|
||||||
|
ansible.builtin.import_tasks: cron.yml
|
||||||
|
tags:
|
||||||
|
- cron
|
||||||
|
|
||||||
|
- name: Harden sudo configuration
|
||||||
|
ansible.builtin.import_tasks: sudo.yml
|
||||||
|
when: cis_manage_sudo | bool
|
||||||
|
tags:
|
||||||
|
- sudo
|
||||||
|
|
||||||
|
- name: Run AIX validation postchecks
|
||||||
|
ansible.builtin.import_tasks: postcheck.yml
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
|
- postcheck
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
- name: Collect current AIX network tunables
|
||||||
|
ansible.builtin.command: no -a
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_no_current
|
||||||
|
|
||||||
|
- name: Query configured AIX network tunables
|
||||||
|
ansible.builtin.command: "no -o {{ item.key }}"
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
loop: "{{ cis_network_no_settings | dict2items }}"
|
||||||
|
register: cis_aix_no_query
|
||||||
|
|
||||||
|
- name: Apply configured AIX network tunables
|
||||||
|
ansible.builtin.command: "no -p -o {{ item.item.key }}={{ item.item.value }}"
|
||||||
|
changed_when: true
|
||||||
|
loop: "{{ cis_aix_no_query.results }}"
|
||||||
|
when:
|
||||||
|
- item.rc == 0
|
||||||
|
- item.stdout is not search('=\\s*' ~ (item.item.value | string) ~ '\\b')
|
||||||
|
|
||||||
|
- name: Warn about unsupported AIX network tunables
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "WARNING: AIX network tunable {{ item.item.key }} is not supported on this host."
|
||||||
|
loop: "{{ cis_aix_no_query.results }}"
|
||||||
|
when: item.rc != 0
|
||||||
|
|
||||||
|
- name: Check nfso availability
|
||||||
|
ansible.builtin.shell: "command -v nfso >/dev/null 2>&1 || whence nfso >/dev/null 2>&1"
|
||||||
|
args:
|
||||||
|
executable: /bin/ksh
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_nfso_available
|
||||||
|
|
||||||
|
- name: Query configured AIX NFS tunables
|
||||||
|
ansible.builtin.command: "nfso -o {{ item.key }}"
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
loop: "{{ cis_network_nfso_settings | dict2items }}"
|
||||||
|
register: cis_aix_nfso_query
|
||||||
|
when:
|
||||||
|
- cis_aix_nfso_available.rc == 0
|
||||||
|
- cis_network_nfso_settings | length > 0
|
||||||
|
|
||||||
|
- name: Apply configured AIX NFS tunables
|
||||||
|
ansible.builtin.command: "nfso -p -o {{ item.item.key }}={{ item.item.value }}"
|
||||||
|
changed_when: true
|
||||||
|
loop: "{{ cis_aix_nfso_query.results | default([]) }}"
|
||||||
|
when:
|
||||||
|
- item.rc == 0
|
||||||
|
- item.stdout is not search('=\\s*' ~ (item.item.value | string) ~ '\\b')
|
||||||
|
|
||||||
|
- name: Report network hardening status
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "OK: AIX network tunables were validated before changes."
|
||||||
|
- >-
|
||||||
|
{{ 'OK: nfso is available for optional NFS network tunables.'
|
||||||
|
if cis_aix_nfso_available.rc == 0 else 'WARNING: nfso was not found; NFS tunables were skipped.' }}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
- name: Collect current default password policy
|
||||||
|
ansible.builtin.command: lssec -f /etc/security/user -s default -a minlen histsize maxage minage minalpha minother maxrepeats loginretries
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_password_policy_current
|
||||||
|
|
||||||
|
- name: Collect current default login policy
|
||||||
|
ansible.builtin.command: lssec -f /etc/security/login.cfg -s usw -a logindisable logininterval loginreenable
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_login_policy_current
|
||||||
|
|
||||||
|
- name: Manage default password security attributes
|
||||||
|
ansible.builtin.command: "chsec -f /etc/security/user -s default -a {{ item.key }}={{ item.value }}"
|
||||||
|
changed_when: true
|
||||||
|
loop:
|
||||||
|
- key: minlen
|
||||||
|
value: "{{ cis_password_minlen }}"
|
||||||
|
- key: histsize
|
||||||
|
value: "{{ cis_password_histsize }}"
|
||||||
|
- key: maxage
|
||||||
|
value: "{{ cis_password_maxage_weeks }}"
|
||||||
|
- key: minage
|
||||||
|
value: "{{ cis_password_minage_weeks }}"
|
||||||
|
- key: minalpha
|
||||||
|
value: "{{ cis_password_minalpha }}"
|
||||||
|
- key: minother
|
||||||
|
value: "{{ cis_password_minother }}"
|
||||||
|
- key: maxrepeats
|
||||||
|
value: "{{ cis_password_maxrepeats }}"
|
||||||
|
- key: loginretries
|
||||||
|
value: "{{ cis_login_retries }}"
|
||||||
|
when: >-
|
||||||
|
(item.key ~ '=' ~ (item.value | string))
|
||||||
|
not in (cis_aix_password_policy_current.stdout | default(''))
|
||||||
|
|
||||||
|
- name: Manage login lockout interval
|
||||||
|
ansible.builtin.command: "chsec -f /etc/security/login.cfg -s usw -a loginreenable={{ cis_login_lockout }}"
|
||||||
|
changed_when: true
|
||||||
|
when: >-
|
||||||
|
('loginreenable=' ~ (cis_login_lockout | string))
|
||||||
|
not in (cis_aix_login_policy_current.stdout | default(''))
|
||||||
|
|
||||||
|
- name: Collect updated default password policy
|
||||||
|
ansible.builtin.command: lssec -f /etc/security/user -s default -a minlen histsize maxage minage minalpha minother maxrepeats loginretries
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_password_policy_updated
|
||||||
|
|
||||||
|
- name: Validate password database state
|
||||||
|
ansible.builtin.command: pwdadm -q root
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_pwdadm_root
|
||||||
|
|
||||||
|
- name: Report password policy status
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "OK: Password policy managed through AIX chsec defaults, without replacing security files."
|
||||||
|
- "OK: Current default policy: {{ cis_aix_password_policy_updated.stdout | default('unavailable') }}"
|
||||||
|
- "OK: pwdadm root status: {{ cis_aix_pwdadm_root.stdout | default('unavailable') }}"
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
- name: Validate sshd configuration after hardening
|
||||||
|
ansible.builtin.command: "{{ cis_sshd_test_command }}"
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_post_sshd
|
||||||
|
|
||||||
|
- name: Show selected AIX network security values
|
||||||
|
ansible.builtin.command: "no -o {{ item.key }}"
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
loop: "{{ cis_network_no_settings | dict2items }}"
|
||||||
|
register: cis_aix_post_network
|
||||||
|
|
||||||
|
- name: Show key SRC service states
|
||||||
|
ansible.builtin.command: "lssrc -s {{ item }}"
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
loop:
|
||||||
|
- sshd
|
||||||
|
- syslogd
|
||||||
|
- audit
|
||||||
|
register: cis_aix_post_services
|
||||||
|
|
||||||
|
- name: Show password policy summary
|
||||||
|
ansible.builtin.command: lssec -f /etc/security/user -s default -a minlen histsize maxage minage minalpha minother loginretries
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_post_password
|
||||||
|
|
||||||
|
- name: Build AIX hardening validation summary
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_aix_validation_summary:
|
||||||
|
oslevel: "{{ cis_aix_oslevel.stdout | default('unavailable') }}"
|
||||||
|
sshd_config_valid: "{{ cis_aix_post_sshd.rc == 0 }}"
|
||||||
|
sshd_validation_output: "{{ cis_aix_post_sshd.stderr | default(cis_aix_post_sshd.stdout | default('')) }}"
|
||||||
|
network_values: "{{ cis_aix_post_network.results | map(attribute='stdout') | list }}"
|
||||||
|
service_states: "{{ cis_aix_post_services.results | map(attribute='stdout') | list }}"
|
||||||
|
password_policy: "{{ cis_aix_post_password.stdout | default('unavailable') }}"
|
||||||
|
recommendations:
|
||||||
|
- "Validate SSH access from a second privileged session before enforcing passwordless-only access."
|
||||||
|
- "Review audit classes and events with security operations before setting cis_enable_audit=true."
|
||||||
|
- "Keep cis_manage_mount_options=false until filesystem owners approve remount or chfs behavior."
|
||||||
|
|
||||||
|
- name: Print AIX operational postcheck recommendations
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- >-
|
||||||
|
{{ 'OK: sshd configuration validates.'
|
||||||
|
if cis_aix_post_sshd.rc == 0 else 'CRITICAL: sshd validation failed; review SSH config before restarting sessions.' }}
|
||||||
|
- "OK: Service states: {{ cis_aix_validation_summary.service_states }}"
|
||||||
|
- "OK: Password policy summary: {{ cis_aix_validation_summary.password_policy }}"
|
||||||
|
- "WARNING: This role is CIS-inspired and does not represent a complete CIS certification implementation."
|
||||||
|
- "{{ cis_aix_validation_summary.recommendations }}"
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
---
|
||||||
|
- name: Determine root filesystem free space
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_aix_root_mount: "{{ ansible_mounts | selectattr('mount', 'equalto', '/') | list | first | default({}) }}"
|
||||||
|
|
||||||
|
- name: Calculate root filesystem free space in MB
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_aix_root_free_mb: "{{ ((cis_aix_root_mount.size_available | default(0) | int) / 1024 / 1024) | round(0, 'floor') | int }}"
|
||||||
|
|
||||||
|
- name: Collect AIX maintenance level
|
||||||
|
ansible.builtin.command: oslevel -s
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_oslevel
|
||||||
|
|
||||||
|
- name: Check required AIX commands
|
||||||
|
ansible.builtin.shell: "command -v {{ item | quote }} >/dev/null 2>&1 || whence {{ item | quote }} >/dev/null 2>&1"
|
||||||
|
args:
|
||||||
|
executable: /bin/ksh
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
loop: "{{ cis_required_commands }}"
|
||||||
|
register: cis_aix_required_command_checks
|
||||||
|
|
||||||
|
- name: Build missing required command list
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_aix_missing_required_commands: >-
|
||||||
|
{{
|
||||||
|
cis_aix_required_command_checks.results
|
||||||
|
| selectattr('rc', 'ne', 0)
|
||||||
|
| map(attribute='item')
|
||||||
|
| list
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Locate sshd binary
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ item }}"
|
||||||
|
loop: "{{ cis_ssh_candidate_paths }}"
|
||||||
|
register: cis_aix_sshd_path_checks
|
||||||
|
|
||||||
|
- name: Store detected sshd binary
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_aix_sshd_path: >-
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
cis_aix_sshd_path_checks.results
|
||||||
|
| selectattr('stat.exists')
|
||||||
|
| map(attribute='item')
|
||||||
|
| list
|
||||||
|
| first
|
||||||
|
)
|
||||||
|
| default('')
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Validate SRC subsystem availability
|
||||||
|
ansible.builtin.command: lssrc -a
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_src_summary
|
||||||
|
|
||||||
|
- name: Validate audit subsystem availability
|
||||||
|
ansible.builtin.command: audit query
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_audit_query
|
||||||
|
|
||||||
|
- name: Collect LPAR summary when available
|
||||||
|
ansible.builtin.shell: "command -v lparstat >/dev/null 2>&1 && lparstat -i || true"
|
||||||
|
args:
|
||||||
|
executable: /bin/ksh
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_lparstat
|
||||||
|
|
||||||
|
- name: Collect current network tunable summary
|
||||||
|
ansible.builtin.command: no -a
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_network_summary
|
||||||
|
|
||||||
|
- name: Collect default AIX user security summary
|
||||||
|
ansible.builtin.command: lssec -f /etc/security/user -s default -a ALL
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_security_user_summary
|
||||||
|
|
||||||
|
- name: Report AIX precheck status
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- >-
|
||||||
|
OK: Facts gathered for {{ ansible_distribution | default(ansible_system | default('unknown')) }}
|
||||||
|
{{ ansible_distribution_version | default(ansible_kernel | default('unknown')) }}.
|
||||||
|
- "OK: oslevel -s reports {{ cis_aix_oslevel.stdout | default('unavailable') }}."
|
||||||
|
- "OK: Root filesystem free space is {{ cis_aix_root_free_mb }} MB."
|
||||||
|
- >-
|
||||||
|
{{ 'OK: sshd binary detected at ' ~ cis_aix_sshd_path
|
||||||
|
if cis_aix_sshd_path | length > 0 else 'CRITICAL: sshd binary was not found in expected AIX paths.' }}
|
||||||
|
- >-
|
||||||
|
{{ 'OK: SRC subsystem commands are functional.'
|
||||||
|
if cis_aix_src_summary.rc == 0 else 'CRITICAL: lssrc failed; SRC is unavailable or not usable.' }}
|
||||||
|
- >-
|
||||||
|
{{ 'OK: AIX audit subsystem responded to audit query.'
|
||||||
|
if cis_aix_audit_query.rc == 0 else 'WARNING: audit query did not complete; audit may be disabled or unconfigured.' }}
|
||||||
|
- >-
|
||||||
|
{{ 'OK: Required commands are present.'
|
||||||
|
if cis_aix_missing_required_commands | length == 0
|
||||||
|
else 'CRITICAL: Missing required commands: ' ~ (cis_aix_missing_required_commands | join(', ')) }}
|
||||||
|
|
||||||
|
- name: Fail when operating system is unsupported
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- ansible_system | default(ansible_distribution | default('')) == 'AIX'
|
||||||
|
- ansible_distribution_version | default('') is match('^7\\.')
|
||||||
|
fail_msg: >-
|
||||||
|
CRITICAL: This role supports IBM AIX 7.x only.
|
||||||
|
Detected {{ ansible_distribution | default(ansible_system | default('unknown')) }}
|
||||||
|
{{ ansible_distribution_version | default('unknown') }}.
|
||||||
|
success_msg: "OK: Supported IBM AIX 7.x platform detected."
|
||||||
|
|
||||||
|
- name: Fail when root filesystem free space is below safety threshold
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- cis_aix_root_free_mb | int >= cis_min_root_free_mb | int
|
||||||
|
fail_msg: >-
|
||||||
|
CRITICAL: Root filesystem has {{ cis_aix_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."
|
||||||
|
|
||||||
|
- name: Fail when critical AIX commands are missing
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- cis_aix_missing_required_commands | length == 0
|
||||||
|
- cis_aix_src_summary.rc == 0
|
||||||
|
- cis_aix_sshd_path | length > 0
|
||||||
|
fail_msg: >-
|
||||||
|
CRITICAL: Required AIX hardening prerequisites are missing.
|
||||||
|
Missing commands={{ cis_aix_missing_required_commands | join(', ') | default('none', true) }},
|
||||||
|
SRC rc={{ cis_aix_src_summary.rc }},
|
||||||
|
sshd={{ cis_aix_sshd_path | default('not found', true) }}.
|
||||||
|
success_msg: "OK: Critical AIX hardening prerequisites are available."
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
- name: Collect SRC subsystem states
|
||||||
|
ansible.builtin.command: "lssrc -s {{ item }}"
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
loop: "{{ cis_src_subsystems }}"
|
||||||
|
register: cis_aix_src_service_states
|
||||||
|
|
||||||
|
- name: Validate inetd configuration exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /etc/inetd.conf
|
||||||
|
register: cis_aix_inetd_config
|
||||||
|
|
||||||
|
- name: Read inetd configuration
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: /etc/inetd.conf
|
||||||
|
register: cis_aix_inetd_conf_content
|
||||||
|
when: cis_aix_inetd_config.stat.exists
|
||||||
|
|
||||||
|
- name: Disable insecure inetd services when present
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /etc/inetd.conf
|
||||||
|
regexp: '^(?!#)({{ item }})\s+'
|
||||||
|
line: '# \1 disabled by cis-aix7-hardening'
|
||||||
|
backrefs: true
|
||||||
|
backup: true
|
||||||
|
loop: "{{ cis_legacy_inetd_services }}"
|
||||||
|
when: cis_aix_inetd_config.stat.exists
|
||||||
|
notify: refresh inetd
|
||||||
|
|
||||||
|
- name: Report inetd configuration status
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- >-
|
||||||
|
{{ 'OK: /etc/inetd.conf exists and legacy entries were reviewed.'
|
||||||
|
if cis_aix_inetd_config.stat.exists else 'WARNING: /etc/inetd.conf was not found; inetd review skipped.' }}
|
||||||
|
- "OK: SRC states collected for {{ cis_src_subsystems | join(', ') }}."
|
||||||
|
|
||||||
|
- name: Stop inactive legacy SRC subsystems when present
|
||||||
|
ansible.builtin.command: "stopsrc -s {{ item }}"
|
||||||
|
changed_when: true
|
||||||
|
failed_when: false
|
||||||
|
loop:
|
||||||
|
- routed
|
||||||
|
- gated
|
||||||
|
- named
|
||||||
|
when: >-
|
||||||
|
cis_aix_src_summary.stdout is defined
|
||||||
|
and item in cis_aix_src_summary.stdout
|
||||||
|
and 'active' in cis_aix_src_summary.stdout
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
- name: Ensure sshd configuration exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ cis_ssh_config_path }}"
|
||||||
|
register: cis_aix_sshd_config
|
||||||
|
|
||||||
|
- name: Fail when sshd configuration is missing
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- cis_aix_sshd_config.stat.exists
|
||||||
|
fail_msg: "CRITICAL: {{ cis_ssh_config_path }} was not found; refusing to manage SSH hardening."
|
||||||
|
success_msg: "OK: {{ cis_ssh_config_path }} exists."
|
||||||
|
|
||||||
|
- name: Set sshd validation command from detected binary
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_sshd_test_command: "{{ cis_aix_sshd_path }} -t"
|
||||||
|
when: cis_aix_sshd_path is defined and cis_aix_sshd_path | length > 0
|
||||||
|
|
||||||
|
- name: Apply managed AIX sshd hardening block
|
||||||
|
ansible.builtin.blockinfile:
|
||||||
|
path: "{{ cis_ssh_config_path }}"
|
||||||
|
marker: "# {mark} ANSIBLE MANAGED BLOCK cis-aix7-hardening"
|
||||||
|
owner: root
|
||||||
|
group: system
|
||||||
|
mode: "0600"
|
||||||
|
backup: true
|
||||||
|
validate: "{{ cis_sshd_test_command }} -f %s"
|
||||||
|
block: |
|
||||||
|
PermitRootLogin {{ 'no' if cis_disable_root_login | bool else 'prohibit-password' }}
|
||||||
|
PermitEmptyPasswords no
|
||||||
|
PasswordAuthentication {{ 'no' if cis_disable_password_auth | bool else 'yes' }}
|
||||||
|
MaxAuthTries {{ cis_ssh_max_auth_tries }}
|
||||||
|
LoginGraceTime {{ cis_ssh_login_grace_time }}
|
||||||
|
ClientAliveInterval {{ cis_ssh_client_alive_interval }}
|
||||||
|
ClientAliveCountMax {{ cis_ssh_client_alive_count_max }}
|
||||||
|
notify:
|
||||||
|
- validate sshd
|
||||||
|
- restart sshd
|
||||||
|
|
||||||
|
- name: Validate effective sshd configuration
|
||||||
|
ansible.builtin.command: "{{ cis_sshd_test_command }}"
|
||||||
|
changed_when: false
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
- name: Check sudoers file availability
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ cis_sudoers_path }}"
|
||||||
|
register: cis_aix_sudoers
|
||||||
|
|
||||||
|
- name: Check visudo availability
|
||||||
|
ansible.builtin.shell: "command -v visudo >/dev/null 2>&1 || whence visudo >/dev/null 2>&1"
|
||||||
|
args:
|
||||||
|
executable: /bin/ksh
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_visudo_available
|
||||||
|
|
||||||
|
- name: Manage sudo use_pty default when supported
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ cis_sudoers_path }}"
|
||||||
|
regexp: '^Defaults\s+use_pty\b'
|
||||||
|
line: "Defaults use_pty"
|
||||||
|
validate: "visudo -cf %s"
|
||||||
|
when:
|
||||||
|
- cis_sudo_use_pty | bool
|
||||||
|
- cis_aix_sudoers.stat.exists
|
||||||
|
- cis_aix_visudo_available.rc == 0
|
||||||
|
|
||||||
|
- name: Manage sudo logfile default
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ cis_sudoers_path }}"
|
||||||
|
regexp: '^Defaults\s+logfile='
|
||||||
|
line: 'Defaults logfile="{{ cis_sudo_logfile }}"'
|
||||||
|
validate: "visudo -cf %s"
|
||||||
|
when:
|
||||||
|
- cis_aix_sudoers.stat.exists
|
||||||
|
- cis_aix_visudo_available.rc == 0
|
||||||
|
|
||||||
|
- name: Validate sudoers syntax
|
||||||
|
ansible.builtin.command: "visudo -cf {{ cis_sudoers_path }}"
|
||||||
|
changed_when: false
|
||||||
|
when:
|
||||||
|
- cis_aix_sudoers.stat.exists
|
||||||
|
- cis_aix_visudo_available.rc == 0
|
||||||
|
|
||||||
|
- name: Report sudo hardening status
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- >-
|
||||||
|
{{ 'OK: sudoers exists and visudo validation is available.'
|
||||||
|
if cis_aix_sudoers.stat.exists and cis_aix_visudo_available.rc == 0
|
||||||
|
else 'WARNING: sudo or visudo was not found; sudo controls were skipped.' }}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
- name: Collect root account security attributes
|
||||||
|
ansible.builtin.command: lssec -f /etc/security/user -s root -a account_locked login rlogin su sugroups
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_root_security
|
||||||
|
|
||||||
|
- name: Collect accounts with administrative UID
|
||||||
|
ansible.builtin.shell: "awk -F: '$3 == 0 {print $1}' /etc/passwd"
|
||||||
|
args:
|
||||||
|
executable: /bin/ksh
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_uid_zero_accounts
|
||||||
|
|
||||||
|
- name: Report administrative account review
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- >-
|
||||||
|
{{ 'OK: Only root has UID 0.'
|
||||||
|
if cis_aix_uid_zero_accounts.stdout_lines | default([]) | length == 1
|
||||||
|
else 'WARNING: Multiple UID 0 accounts detected: ' ~ (cis_aix_uid_zero_accounts.stdout_lines | default([]) | join(', ')) }}
|
||||||
|
- "OK: Root security attributes: {{ cis_aix_root_security.stdout | default('unavailable') }}"
|
||||||
|
|
||||||
|
- name: Ensure root remote login is disabled when requested
|
||||||
|
ansible.builtin.command: chsec -f /etc/security/user -s root -a rlogin=false
|
||||||
|
changed_when: true
|
||||||
|
when:
|
||||||
|
- cis_disable_root_login | bool
|
||||||
|
- "'rlogin=false' not in (cis_aix_root_security.stdout | default(''))"
|
||||||
|
|
||||||
|
- name: Collect locked or administratively disabled accounts
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
set -o pipefail
|
||||||
|
awk -F: '{print $1}' /etc/passwd | while read user; do
|
||||||
|
lsuser -a account_locked "$user" 2>/dev/null
|
||||||
|
done
|
||||||
|
args:
|
||||||
|
executable: /bin/ksh
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
register: cis_aix_account_lock_summary
|
||||||
|
|
||||||
|
- name: Report account lock summary
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "OK: Collected account lock status for local users."
|
||||||
|
- "{{ cis_aix_account_lock_summary.stdout_lines | default([]) }}"
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# CIS-Inspired RHEL 9 Hardening Role
|
||||||
|
|
||||||
|
This role provides a practical, production-style hardening baseline for RHEL 9 and Oracle Linux 9 systems. It is inspired by CIS Benchmark controls for Red Hat Enterprise Linux 9 version 2.0.0, but it is intentionally scoped to common operational controls that infrastructure and security operations teams frequently automate.
|
||||||
|
|
||||||
|
This is not a full CIS certification implementation.
|
||||||
|
|
||||||
|
## Supported Platforms
|
||||||
|
|
||||||
|
- Red Hat Enterprise Linux 9
|
||||||
|
- Oracle Linux 9
|
||||||
|
|
||||||
|
The role fails safely on unsupported operating systems or unsupported major versions.
|
||||||
|
|
||||||
|
## Implemented Controls
|
||||||
|
|
||||||
|
- SSH daemon hardening for root login, empty passwords, password authentication, retry limits, login grace time, and client keepalive behavior.
|
||||||
|
- Removal of selected legacy network packages such as telnet, rsh-server, and ypbind.
|
||||||
|
- Optional installation and enablement of chrony, auditd, and rsyslog.
|
||||||
|
- CIS-inspired IPv4 network sysctl settings.
|
||||||
|
- Service enablement for chronyd, auditd, and rsyslog.
|
||||||
|
- Safe disabling of known legacy services when they are present.
|
||||||
|
- Basic audit backlog and audit rule examples.
|
||||||
|
- Sudo defaults for `use_pty` and a configurable sudo logfile.
|
||||||
|
- Rsyslog service validation and journald configuration presence checks.
|
||||||
|
- Optional filesystem mount option persistence for selected paths.
|
||||||
|
|
||||||
|
## Safety Philosophy
|
||||||
|
|
||||||
|
The defaults are conservative. The role supports Ansible check mode and avoids destructive production behavior by default. Filesystem mount option management is disabled unless `cis_manage_mount_options` is explicitly enabled, and even then the role persists configured options without remounting live filesystems.
|
||||||
|
|
||||||
|
Review variables before using this role in production.
|
||||||
|
|
||||||
|
## Common 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Check Mode
|
||||||
|
|
||||||
|
Run a full safety preview:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook playbooks/cis-rhel9-hardening.yml --check --diff
|
||||||
|
```
|
||||||
|
|
||||||
|
Run only SSH controls in check mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook playbooks/cis-rhel9-hardening.yml --check --diff --tags ssh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tags
|
||||||
|
|
||||||
|
Useful tags include:
|
||||||
|
|
||||||
|
- `precheck`
|
||||||
|
- `packages`
|
||||||
|
- `ssh`
|
||||||
|
- `sysctl`
|
||||||
|
- `services`
|
||||||
|
- `audit`
|
||||||
|
- `sudo`
|
||||||
|
- `logging`
|
||||||
|
- `filesystem`
|
||||||
|
- `postcheck`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook playbooks/cis-rhel9-hardening.yml --tags precheck,ssh,postcheck
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production Rollout Notes
|
||||||
|
|
||||||
|
This role is a hardening starting point for internal infrastructure teams. It should be reviewed against local access patterns, break-glass procedures, compliance requirements, monitoring expectations, and host build standards before rollout.
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
cis_benchmark_version: "2.0.0"
|
||||||
|
|
||||||
|
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_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_ssh_dropin_path: /etc/ssh/sshd_config.d/50-cis-rhel9-hardening.conf
|
||||||
|
|
||||||
|
cis_min_root_free_mb: 1024
|
||||||
|
|
||||||
|
cis_legacy_packages:
|
||||||
|
- telnet
|
||||||
|
- rsh-server
|
||||||
|
- ypbind
|
||||||
|
|
||||||
|
cis_legacy_services:
|
||||||
|
- telnet.socket
|
||||||
|
- rsh.socket
|
||||||
|
- rexec.socket
|
||||||
|
- rlogin.socket
|
||||||
|
- ypbind.service
|
||||||
|
|
||||||
|
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-rhel9-hardening.conf
|
||||||
|
|
||||||
|
cis_audit_rules_path: /etc/audit/rules.d/50-cis-rhel9-hardening.rules
|
||||||
|
cis_audit_backlog_limit: 8192
|
||||||
|
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"
|
||||||
|
- "-a always,exit -F arch=b64 -S adjtimex,settimeofday,clock_settime -k time-change"
|
||||||
|
|
||||||
|
cis_sudoers_dropin_path: /etc/sudoers.d/50-cis-rhel9-hardening
|
||||||
|
cis_sudo_logfile: /var/log/sudo.log
|
||||||
|
|
||||||
|
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,24 @@
|
|||||||
|
---
|
||||||
|
- name: Validate sshd configuration
|
||||||
|
ansible.builtin.command: sshd -t
|
||||||
|
changed_when: false
|
||||||
|
listen: validate sshd
|
||||||
|
|
||||||
|
- name: Reload sshd
|
||||||
|
ansible.builtin.service:
|
||||||
|
name: sshd
|
||||||
|
state: reloaded
|
||||||
|
listen: reload sshd
|
||||||
|
|
||||||
|
- 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
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
- name: Ensure audit rules directory exists
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /etc/audit/rules.d
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0750"
|
||||||
|
|
||||||
|
- name: Configure audit backlog limit
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /etc/audit/audit.rules
|
||||||
|
regexp: '^-b\s+'
|
||||||
|
line: "-b {{ cis_audit_backlog_limit }}"
|
||||||
|
create: true
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0640"
|
||||||
|
notify: restart auditd
|
||||||
|
|
||||||
|
- name: Install baseline audit rules
|
||||||
|
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 }}"
|
||||||
|
notify: restart auditd
|
||||||
|
|
||||||
|
- name: Ensure auditd is enabled and running
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: auditd
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
@@ -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,24 @@
|
|||||||
|
---
|
||||||
|
- name: Ensure rsyslog is installed
|
||||||
|
ansible.builtin.package:
|
||||||
|
name: rsyslog
|
||||||
|
state: present
|
||||||
|
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
|
||||||
|
|
||||||
|
- 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,24 @@
|
|||||||
|
---
|
||||||
|
- name: Remove legacy network packages
|
||||||
|
ansible.builtin.package:
|
||||||
|
name: "{{ cis_legacy_packages }}"
|
||||||
|
state: absent
|
||||||
|
when: cis_remove_legacy_packages | bool
|
||||||
|
|
||||||
|
- name: Install chrony when enabled
|
||||||
|
ansible.builtin.package:
|
||||||
|
name: chrony
|
||||||
|
state: present
|
||||||
|
when: cis_enable_chrony | bool
|
||||||
|
|
||||||
|
- name: Install auditd when enabled
|
||||||
|
ansible.builtin.package:
|
||||||
|
name: audit
|
||||||
|
state: present
|
||||||
|
when: cis_install_auditd | bool
|
||||||
|
|
||||||
|
- name: Install rsyslog when enabled
|
||||||
|
ansible.builtin.package:
|
||||||
|
name: rsyslog
|
||||||
|
state: present
|
||||||
|
when: cis_enable_rsyslog | bool
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
- name: Validate sshd 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
|
||||||
|
|
||||||
|
- name: Gather final service facts
|
||||||
|
ansible.builtin.service_facts:
|
||||||
|
|
||||||
|
- name: Build service state summary
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cis_service_state_summary:
|
||||||
|
chronyd: "{{ ansible_facts.services['chronyd.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 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
|
||||||
|
|
||||||
|
- 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 RHEL 9 Benchmark {{ cis_benchmark_version }} inspired controls"
|
||||||
|
sshd_config: "{{ 'OK' if cis_sshd_validate.rc == 0 else 'CRITICAL' }}"
|
||||||
|
services: "{{ cis_service_state_summary }}"
|
||||||
|
sysctl: "{{ cis_sysctl_validation_summary | default({}) }}"
|
||||||
|
mount_option_updates: "{{ cis_mount_option_summary | default([]) }}"
|
||||||
|
applied_controls:
|
||||||
|
- ssh
|
||||||
|
- packages
|
||||||
|
- sysctl
|
||||||
|
- services
|
||||||
|
- audit
|
||||||
|
- sudo
|
||||||
|
- logging
|
||||||
|
- filesystem
|
||||||
|
|
||||||
|
- name: Show service states
|
||||||
|
ansible.builtin.debug:
|
||||||
|
var: cis_service_state_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,54 @@
|
|||||||
|
---
|
||||||
|
- 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: 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."
|
||||||
|
- >-
|
||||||
|
{{ 'WARNING: Containerized environment detected; service and kernel controls may be limited.'
|
||||||
|
if cis_container_detected else 'OK: No containerized runtime detected from Ansible facts.' }}
|
||||||
|
- >-
|
||||||
|
{{ 'OK: systemd service manager detected.'
|
||||||
|
if ansible_service_mgr == 'systemd' else 'CRITICAL: systemd service manager is required.' }}
|
||||||
|
|
||||||
|
- name: Fail when operating system is unsupported
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- ansible_distribution in cis_supported_distributions
|
||||||
|
- ansible_distribution_major_version == cis_supported_major_version
|
||||||
|
fail_msg: >-
|
||||||
|
CRITICAL: This role supports only RHEL 9 / Oracle Linux 9 compatible systems.
|
||||||
|
Detected {{ ansible_distribution }} {{ ansible_distribution_version }}.
|
||||||
|
success_msg: "OK: Supported RHEL 9 compatible 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 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,36 @@
|
|||||||
|
---
|
||||||
|
- name: Enable chronyd service
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: chronyd
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
when: cis_enable_chrony | bool
|
||||||
|
|
||||||
|
- name: Enable rsyslog service
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: rsyslog
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
when: cis_enable_rsyslog | bool
|
||||||
|
|
||||||
|
- name: Enable auditd service
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: auditd
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
when: cis_install_auditd | bool
|
||||||
|
|
||||||
|
- name: Gather service facts
|
||||||
|
ansible.builtin.service_facts:
|
||||||
|
|
||||||
|
- name: Disable unnecessary legacy services when present
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "{{ item }}"
|
||||||
|
enabled: false
|
||||||
|
state: stopped
|
||||||
|
loop: "{{ cis_legacy_services }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item }}"
|
||||||
|
when:
|
||||||
|
- cis_remove_legacy_packages | bool
|
||||||
|
- item in ansible_facts.services
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
---
|
||||||
|
- 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: 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 sshd
|
||||||
|
- reload sshd
|
||||||
|
|
||||||
|
- 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 sshd
|
||||||
|
- reload sshd
|
||||||
|
|
||||||
|
- 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 sshd
|
||||||
|
- reload sshd
|
||||||
|
|
||||||
|
- 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 sshd
|
||||||
|
- reload sshd
|
||||||
|
|
||||||
|
- 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 sshd
|
||||||
|
- reload sshd
|
||||||
|
|
||||||
|
- 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 sshd
|
||||||
|
- reload sshd
|
||||||
|
|
||||||
|
- 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 sshd
|
||||||
|
- reload sshd
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
- 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:
|
||||||
|
- regexp: '^Defaults\s+use_pty'
|
||||||
|
line: "Defaults use_pty"
|
||||||
|
- regexp: '^Defaults\s+logfile='
|
||||||
|
line: 'Defaults logfile="{{ cis_sudo_logfile }}"'
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.line }}"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
- 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 }}"
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
cis_supported_distributions:
|
||||||
|
- RedHat
|
||||||
|
- OracleLinux
|
||||||
|
|
||||||
|
cis_supported_major_version: "9"
|
||||||
Reference in New Issue
Block a user