feat(cis): add selectable profile and per-rule hardening toggles
This commit is contained in:
@@ -1,100 +1,13 @@
|
||||
---
|
||||
# User-facing API: override via top-level `cis` dict in inventory.
|
||||
# Merged with these defaults in _normalize.yml -> cis_cfg.
|
||||
cis_defaults:
|
||||
modules_blacklist:
|
||||
- freevxfs
|
||||
- jffs2
|
||||
- hfs
|
||||
- hfsplus
|
||||
- cramfs
|
||||
- udf
|
||||
- usb-storage
|
||||
- dccp
|
||||
- sctp
|
||||
- rds
|
||||
- tipc
|
||||
- firewire-core
|
||||
- firewire-sbp2
|
||||
- thunderbolt
|
||||
sysctl:
|
||||
fs.suid_dumpable: 0
|
||||
kernel.dmesg_restrict: 1
|
||||
kernel.kptr_restrict: 2
|
||||
kernel.perf_event_paranoid: 3
|
||||
kernel.unprivileged_bpf_disabled: 1
|
||||
kernel.yama.ptrace_scope: 2
|
||||
kernel.randomize_va_space: 2
|
||||
net.ipv4.ip_forward: 0
|
||||
net.ipv4.tcp_syncookies: 1
|
||||
net.ipv4.icmp_echo_ignore_broadcasts: 1
|
||||
net.ipv4.icmp_ignore_bogus_error_responses: 1
|
||||
net.ipv4.conf.all.log_martians: 1
|
||||
net.ipv4.conf.all.rp_filter: 1
|
||||
net.ipv4.conf.all.secure_redirects: 0
|
||||
net.ipv4.conf.all.send_redirects: 0
|
||||
net.ipv4.conf.all.accept_redirects: 0
|
||||
net.ipv4.conf.all.accept_source_route: 0
|
||||
net.ipv4.conf.all.arp_ignore: 1
|
||||
net.ipv4.conf.all.arp_announce: 2
|
||||
net.ipv4.conf.default.log_martians: 1
|
||||
net.ipv4.conf.default.rp_filter: 1
|
||||
net.ipv4.conf.default.secure_redirects: 0
|
||||
net.ipv4.conf.default.send_redirects: 0
|
||||
net.ipv4.conf.default.accept_redirects: 0
|
||||
net.ipv6.conf.all.accept_redirects: 0
|
||||
net.ipv6.conf.all.disable_ipv6: 1
|
||||
net.ipv6.conf.default.accept_redirects: 0
|
||||
net.ipv6.conf.default.disable_ipv6: 1
|
||||
net.ipv6.conf.lo.disable_ipv6: 1
|
||||
sshd_options:
|
||||
- { option: LogLevel, value: VERBOSE }
|
||||
- { option: LoginGraceTime, value: "60" }
|
||||
- { option: PermitRootLogin, value: "no" }
|
||||
- { option: StrictModes, value: "yes" }
|
||||
- { option: MaxAuthTries, value: "4" }
|
||||
- { option: MaxSessions, value: "10" }
|
||||
- { option: MaxStartups, value: "10:30:60" }
|
||||
- { option: PubkeyAuthentication, value: "yes" }
|
||||
- { option: HostbasedAuthentication, value: "no" }
|
||||
- { option: IgnoreRhosts, value: "yes" }
|
||||
- { option: PasswordAuthentication, value: "no" }
|
||||
- { option: PermitEmptyPasswords, value: "no" }
|
||||
- { option: KerberosAuthentication, value: "no" }
|
||||
- { option: GSSAPIAuthentication, value: "no" }
|
||||
- { option: AllowAgentForwarding, value: "no" }
|
||||
- { option: AllowTcpForwarding, value: "no" }
|
||||
- { option: KbdInteractiveAuthentication, value: "no" }
|
||||
- { option: GatewayPorts, value: "no" }
|
||||
- { option: X11Forwarding, value: "no" }
|
||||
- { option: PermitUserEnvironment, value: "no" }
|
||||
- { option: ClientAliveInterval, value: "300" }
|
||||
- { option: ClientAliveCountMax, value: "1" }
|
||||
- { option: PermitTunnel, value: "no" }
|
||||
- { option: Banner, value: /etc/issue.net }
|
||||
pwquality_minlen: 14
|
||||
tmout: 900
|
||||
umask: "077"
|
||||
umask_profile: "027"
|
||||
faillock_deny: 5
|
||||
faillock_unlock_time: 900
|
||||
password_remember: 5
|
||||
|
||||
# Platform-specific binary names for CIS permission targets
|
||||
cis_fusermount_binary: "{{ 'fusermount3' if is_rhel | default(false) | bool else 'fusermount' }}"
|
||||
cis_write_binary: "{{ 'write' if is_rhel | default(false) | bool else 'wall' }}"
|
||||
|
||||
cis: {}
|
||||
|
||||
cis_permission_targets:
|
||||
- { path: "/mnt/etc/ssh/sshd_config", mode: "0600" }
|
||||
- { path: "/mnt/etc/cron.hourly", mode: "0700" }
|
||||
- { path: "/mnt/etc/cron.daily", mode: "0700" }
|
||||
- { path: "/mnt/etc/cron.weekly", mode: "0700" }
|
||||
- { path: "/mnt/etc/cron.monthly", mode: "0700" }
|
||||
- { path: "/mnt/etc/cron.d", mode: "0700" }
|
||||
- { path: "/mnt/etc/crontab", mode: "0600" }
|
||||
- { path: "/mnt/etc/logrotate.conf", mode: "0644" }
|
||||
- { path: "/mnt/usr/sbin/pppd", mode: "0754" }
|
||||
- { path: "/mnt/usr/bin/{{ cis_fusermount_binary }}", mode: "0755" }
|
||||
- { path: "/mnt/usr/bin/{{ cis_write_binary }}", mode: "0755" }
|
||||
- {path: "/mnt/etc/ssh/sshd_config", mode: "0600"}
|
||||
- {path: "/mnt/etc/cron.hourly", mode: "0700"}
|
||||
- {path: "/mnt/etc/cron.daily", mode: "0700"}
|
||||
- {path: "/mnt/etc/cron.weekly", mode: "0700"}
|
||||
- {path: "/mnt/etc/cron.monthly", mode: "0700"}
|
||||
- {path: "/mnt/etc/cron.d", mode: "0700"}
|
||||
- {path: "/mnt/etc/crontab", mode: "0600"}
|
||||
- {path: "/mnt/etc/logrotate.conf", mode: "0644"}
|
||||
- {path: "/mnt/usr/sbin/pppd", mode: "0754"}
|
||||
- {path: "/mnt/usr/bin/{{ cis_fusermount_binary }}", mode: "0755"}
|
||||
- {path: "/mnt/usr/bin/{{ cis_write_binary }}", mode: "0755"}
|
||||
|
||||
@@ -1,10 +1,25 @@
|
||||
---
|
||||
- name: Normalize CIS input
|
||||
- name: Determine CIS profile
|
||||
ansible.builtin.set_fact:
|
||||
cis_enabled: "{{ cis is defined and (cis is mapping or cis | bool) }}"
|
||||
cis_input: "{{ cis if cis is mapping else {} }}"
|
||||
cis_profile: "{{ system_cfg.features.cis.profile | default('default') }}"
|
||||
|
||||
- name: Normalize CIS configuration
|
||||
when: cis_enabled and cis_cfg is not defined
|
||||
- name: Validate CIS profile selection
|
||||
ansible.builtin.assert:
|
||||
that: cis_profile in cis_profiles
|
||||
fail_msg: >-
|
||||
system.features.cis.profile '{{ cis_profile }}' is unknown
|
||||
(valid: {{ cis_profiles.keys() | list | join(', ') }}).
|
||||
quiet: true
|
||||
|
||||
- name: Resolve CIS rules and parameters
|
||||
vars:
|
||||
_cis: "{{ system_cfg.features.cis | default({}) }}"
|
||||
ansible.builtin.set_fact:
|
||||
cis_cfg: "{{ cis_defaults | combine(cis_input, recursive=True) }}"
|
||||
cis_effective_rules: "{{ cis_profiles[cis_profile] | combine(_cis.rules | default({})) }}"
|
||||
cis_cfg: >-
|
||||
{{ cis_param_defaults
|
||||
| combine(cis_profile_params[cis_profile] | default({}), recursive=True)
|
||||
| combine(_cis.params | default({}), recursive=True) }}
|
||||
# l1/l2 add the stricter CIS-server controls on top of the legacy `default`
|
||||
# baseline; gate those tasks on this so `default` stays byte-for-byte unchanged.
|
||||
cis_strict: "{{ cis_profile in ['l1', 'l2'] }}"
|
||||
|
||||
42
roles/cis/tasks/aide.yml
Normal file
42
roles/cis/tasks/aide.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
- name: Install AIDE
|
||||
when: cis_effective_rules.aide | default(false)
|
||||
# Debian's aideinit lives in aide-common (only Recommended, so absent under
|
||||
# the installer's --no-install-recommends); pull it explicitly.
|
||||
ansible.builtin.command: "{{ cis_pkg_install }} {{ 'aide aide-common' if is_debian | bool else 'aide' }}"
|
||||
register: cis_aide_install
|
||||
changed_when: cis_aide_install.rc == 0
|
||||
|
||||
- name: Initialize the AIDE database
|
||||
when: cis_effective_rules.aide | default(false)
|
||||
# Absolute path: arch-chroot's PATH omits /usr/sbin, so bare aide/aideinit is rc127.
|
||||
# Debian's aideinit assembles its split config; RHEL/Arch run --init on /etc/aide.conf.
|
||||
ansible.builtin.command: "{{ chroot_command }} {{ '/usr/sbin/aideinit -y -f' if is_debian | bool else '/usr/sbin/aide --init' }}"
|
||||
register: cis_aide_init
|
||||
changed_when: cis_aide_init.rc == 0
|
||||
|
||||
- name: Locate the freshly built AIDE database
|
||||
when: cis_effective_rules.aide | default(false)
|
||||
ansible.builtin.find:
|
||||
paths: /mnt/var/lib/aide
|
||||
patterns: "aide.db.new*"
|
||||
register: cis_aide_newdb
|
||||
|
||||
- name: Activate the AIDE database
|
||||
when:
|
||||
- cis_effective_rules.aide | default(false)
|
||||
- cis_aide_newdb.files | length > 0
|
||||
ansible.builtin.copy:
|
||||
src: "{{ cis_aide_newdb.files[0].path }}"
|
||||
dest: "{{ cis_aide_newdb.files[0].path | regex_replace('\\.new', '') }}"
|
||||
remote_src: true
|
||||
mode: "0600"
|
||||
|
||||
- name: Schedule the daily AIDE integrity check
|
||||
when: cis_effective_rules.aide | default(false)
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/cron.d/cis-aide
|
||||
mode: "0644"
|
||||
content: |
|
||||
PATH=/usr/sbin:/usr/bin:/sbin:/bin
|
||||
{{ cis_cfg.aide_cron_minute }} {{ cis_cfg.aide_cron_hour }} * * * root aide --check
|
||||
42
roles/cis/tasks/auditd.yml
Normal file
42
roles/cis/tasks/auditd.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
- name: Install the audit daemon
|
||||
when: cis_effective_rules.auditd | default(false)
|
||||
ansible.builtin.command: "{{ cis_pkg_install }} {{ 'auditd' if is_debian | bool else 'audit' }}"
|
||||
register: cis_auditd_install
|
||||
changed_when: cis_auditd_install.rc == 0
|
||||
|
||||
- name: Deploy the CIS audit rule set
|
||||
when: cis_effective_rules.auditd | default(false)
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/audit/rules.d/cis.rules
|
||||
mode: "0640"
|
||||
content: |
|
||||
## CIS baseline audit rules
|
||||
-D
|
||||
-b 8192
|
||||
-f 1
|
||||
-a always,exit -F arch=b64 -S adjtimex,settimeofday,clock_settime -k time-change
|
||||
-w /etc/localtime -p wa -k time-change
|
||||
-w /etc/group -p wa -k identity
|
||||
-w /etc/passwd -p wa -k identity
|
||||
-w /etc/shadow -p wa -k identity
|
||||
-w /etc/gshadow -p wa -k identity
|
||||
-w /etc/security/opasswd -p wa -k identity
|
||||
-a always,exit -F arch=b64 -S sethostname,setdomainname -k system-locale
|
||||
-w /etc/hosts -p wa -k system-locale
|
||||
-w /var/log/lastlog -p wa -k logins
|
||||
-w /var/run/faillock -p wa -k logins
|
||||
-w /var/run/utmp -p wa -k session
|
||||
-w /var/log/wtmp -p wa -k session
|
||||
-w /var/log/btmp -p wa -k session
|
||||
-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=4294967295 -k perm_mod
|
||||
-w /etc/sudoers -p wa -k scope
|
||||
-w /etc/sudoers.d -p wa -k scope
|
||||
-a always,exit -F arch=b64 -S init_module,delete_module -k modules
|
||||
-e 2
|
||||
|
||||
- name: Enable the audit daemon
|
||||
when: cis_effective_rules.auditd | default(false)
|
||||
ansible.builtin.command: "{{ chroot_command }} systemctl enable auditd"
|
||||
register: cis_auditd_enable
|
||||
changed_when: "'Created symlink' in cis_auditd_enable.stderr"
|
||||
@@ -1,12 +1,33 @@
|
||||
---
|
||||
- name: Ensure the Default UMASK is Set Correctly
|
||||
when: cis_effective_rules.umask_default | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: "/mnt/etc/profile"
|
||||
regexp: "^(\\s*)umask\\s+\\d+"
|
||||
line: "umask {{ cis_cfg.umask_profile }}"
|
||||
|
||||
- name: Set the login.defs UMASK (CIS L1+)
|
||||
when:
|
||||
- cis_effective_rules.umask_default | default(false)
|
||||
- cis_strict | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/login.defs
|
||||
regexp: '^\s*#?\s*UMASK\b'
|
||||
line: "UMASK\t\t{{ cis_cfg.umask_profile }}"
|
||||
|
||||
# authselect regenerates system-auth from the profile, so a direct edit is lost
|
||||
# on the next apply; without-nullok is the supported way to drop nullok there.
|
||||
- name: Prevent Login to Accounts With Empty Password (authselect)
|
||||
when:
|
||||
- cis_effective_rules.empty_password_login | default(false)
|
||||
- is_authselect | bool
|
||||
ansible.builtin.command: "{{ chroot_command }} authselect enable-feature without-nullok"
|
||||
register: cis_nullok_result
|
||||
changed_when: cis_nullok_result.rc == 0
|
||||
|
||||
# Non-RHEL/non-Debian distros: loop evaluates to [] (intentional skip)
|
||||
- name: Prevent Login to Accounts With Empty Password
|
||||
when: cis_effective_rules.empty_password_login | default(false)
|
||||
ansible.builtin.replace:
|
||||
dest: "{{ item }}"
|
||||
regexp: "\\s*nullok"
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
# Fedora ships its own crypto-policies preset and update-crypto-policies
|
||||
# behaves differently; applying DEFAULT:NO-SHA1 can break package signing.
|
||||
- name: Configure System Cryptography Policy
|
||||
when: os in (os_family_rhel | difference(['fedora']))
|
||||
when:
|
||||
- cis_effective_rules.crypto_policy | default(false)
|
||||
- os in (os_family_rhel | difference(['fedora']))
|
||||
ansible.builtin.command: "{{ chroot_command }} /usr/bin/update-crypto-policies --set DEFAULT:NO-SHA1"
|
||||
register: cis_crypto_policy_result
|
||||
changed_when: "'Setting system-wide crypto-policies to' in cis_crypto_policy_result.stdout"
|
||||
|
||||
- name: Mask Systemd Services
|
||||
when: cis_effective_rules.mask_services | default(false)
|
||||
ansible.builtin.command: >
|
||||
{{ chroot_command }} systemctl mask {{ 'nftables' if system_cfg.features.firewall.toolkit == 'iptables' else 'iptables' }} bluetooth rpcbind
|
||||
register: cis_mask_services_result
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
- name: Ensure files exist
|
||||
- name: Ensure cron and at access files exist
|
||||
when: cis_effective_rules.cron_at_access | default(false)
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: touch
|
||||
@@ -7,10 +8,19 @@
|
||||
loop:
|
||||
- /mnt/etc/at.allow
|
||||
- /mnt/etc/cron.allow
|
||||
|
||||
- name: Ensure TCP wrapper files exist
|
||||
when: cis_effective_rules.tcp_wrappers | default(false)
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: touch
|
||||
mode: "0600"
|
||||
loop:
|
||||
- /mnt/etc/hosts.allow
|
||||
- /mnt/etc/hosts.deny
|
||||
|
||||
- name: Ensure files do not exist
|
||||
- name: Ensure cron and at deny files do not exist
|
||||
when: cis_effective_rules.cron_at_access | default(false)
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
|
||||
31
roles/cis/tasks/grub_password.yml
Normal file
31
roles/cis/tasks/grub_password.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
# Opt-in only: a GRUB superuser password blocks unattended menu edits; the default entry still boots.
|
||||
- name: Assert a GRUB password hash is supplied
|
||||
when: cis_effective_rules.grub_password | default(false)
|
||||
ansible.builtin.assert:
|
||||
that: cis_cfg.grub_password_hash | length > 0
|
||||
fail_msg: >-
|
||||
system.features.cis.rules.grub_password is enabled but
|
||||
system.features.cis.params.grub_password_hash is empty. Generate one with
|
||||
grub2-mkpasswd-pbkdf2 and set it there.
|
||||
quiet: true
|
||||
|
||||
- name: Deploy the GRUB superuser password
|
||||
when: cis_effective_rules.grub_password | default(false)
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/grub.d/01_cis_password
|
||||
mode: "0755"
|
||||
content: |
|
||||
#!/bin/sh
|
||||
cat <<'EOF'
|
||||
set superusers="root"
|
||||
password_pbkdf2 root {{ cis_cfg.grub_password_hash }}
|
||||
EOF
|
||||
|
||||
- name: Regenerate the GRUB configuration
|
||||
when: cis_effective_rules.grub_password | default(false)
|
||||
ansible.builtin.command: >-
|
||||
{{ chroot_command }}
|
||||
{{ 'grub2-mkconfig -o /boot/grub2/grub.cfg' if is_rhel | bool else 'grub-mkconfig -o /boot/grub/grub.cfg' }}
|
||||
register: cis_grub_regen
|
||||
changed_when: cis_grub_regen.rc == 0
|
||||
@@ -3,7 +3,6 @@
|
||||
ansible.builtin.import_tasks: _normalize.yml
|
||||
|
||||
- name: Apply CIS hardening
|
||||
when: cis_enabled
|
||||
block:
|
||||
- name: Include CIS hardening tasks
|
||||
ansible.builtin.include_tasks: "{{ cis_task }}"
|
||||
@@ -16,5 +15,11 @@
|
||||
- security_lines.yml
|
||||
- permissions.yml
|
||||
- sshd.yml
|
||||
- warning_banners.yml
|
||||
- password_expiry.yml
|
||||
- aide.yml
|
||||
- auditd.yml
|
||||
- packages.yml
|
||||
- grub_password.yml
|
||||
loop_control:
|
||||
loop_var: cis_task
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
- name: Disable Kernel Modules
|
||||
when: cis_effective_rules.module_blacklist | default(false)
|
||||
vars:
|
||||
# Ubuntu uses squashfs for snap packages - blacklisting it breaks snap entirely
|
||||
cis_modules_squashfs: "{{ [] if os in ['ubuntu', 'ubuntu-lts'] else ['squashfs'] }}"
|
||||
@@ -14,11 +15,13 @@
|
||||
{% endfor %}
|
||||
|
||||
- name: Remove old USB rules file
|
||||
when: cis_effective_rules.usb_lockdown | default(false)
|
||||
ansible.builtin.file:
|
||||
path: /mnt/etc/udev/rules.d/10-cis_usb_devices.sh
|
||||
state: absent
|
||||
|
||||
- name: Create USB rules
|
||||
when: cis_effective_rules.usb_lockdown | default(false)
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/udev/rules.d/10-cis_usb_devices.rules
|
||||
mode: "0644"
|
||||
|
||||
29
roles/cis/tasks/packages.yml
Normal file
29
roles/cis/tasks/packages.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
# CIS L1 names legacy cleartext clients (telnet) for removal. They are absent on
|
||||
# a fresh minimal install; query first and remove only when present so the run
|
||||
# stays idempotent (a chroot package-manager remove cannot use the package module).
|
||||
- name: Check for insecure cleartext clients
|
||||
when: cis_strict | default(false)
|
||||
ansible.builtin.command: >-
|
||||
{{ chroot_command }}
|
||||
{{ 'dpkg -s' if is_debian | bool else 'pacman -Q' if os == 'archlinux' else 'rpm -q' }}
|
||||
{{ item }}
|
||||
loop: "{{ cis_cfg.insecure_packages }}"
|
||||
register: cis_insecure_present
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
|
||||
- name: Remove insecure cleartext clients (CIS L1+)
|
||||
when:
|
||||
- cis_strict | default(false)
|
||||
- item.rc == 0
|
||||
ansible.builtin.command: >-
|
||||
{{ chroot_command }}
|
||||
{{ 'apt-get remove -y' if is_debian | bool else 'pacman -R --noconfirm' if os == 'archlinux' else 'dnf remove -y' }}
|
||||
{{ item.item }}
|
||||
loop: "{{ cis_insecure_present.results | default([]) }}"
|
||||
changed_when: true
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
22
roles/cis/tasks/password_expiry.yml
Normal file
22
roles/cis/tasks/password_expiry.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
# login.defs sets policy for future accounts; existing service accounts are intentionally not chage-aged.
|
||||
- name: Configure password aging defaults
|
||||
when: cis_effective_rules.password_expiry | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/login.defs
|
||||
regexp: '^#?\s*{{ item.key }}\b'
|
||||
line: "{{ item.key }}\t{{ item.value }}"
|
||||
loop:
|
||||
- {key: PASS_MAX_DAYS, value: "{{ cis_cfg.pass_max_days }}"}
|
||||
- {key: PASS_MIN_DAYS, value: "{{ cis_cfg.pass_min_days }}"}
|
||||
- {key: PASS_WARN_AGE, value: "{{ cis_cfg.pass_warn_age }}"}
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
|
||||
# account_disable_post_pw_expiration: lock accounts INACTIVE days after expiry.
|
||||
- name: Set the default account inactivity lock period
|
||||
when: cis_effective_rules.password_expiry | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/default/useradd
|
||||
regexp: '^\s*#?\s*INACTIVE\s*='
|
||||
line: "INACTIVE={{ cis_cfg.pass_inactive }}"
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
- name: Check CIS permission targets
|
||||
when: cis_effective_rules.file_permissions | default(false)
|
||||
ansible.builtin.stat:
|
||||
path: "{{ item.path }}"
|
||||
loop: "{{ cis_permission_targets }}"
|
||||
@@ -9,12 +10,14 @@
|
||||
changed_when: false
|
||||
|
||||
- name: Set permissions for existing targets
|
||||
when:
|
||||
- cis_effective_rules.file_permissions | default(false)
|
||||
- item.stat.exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.item.path }}"
|
||||
owner: "{{ item.item.owner | default(omit) }}"
|
||||
group: "{{ item.item.group | default(omit) }}"
|
||||
mode: "{{ item.item.mode }}"
|
||||
loop: "{{ cis_permission_stats.results }}"
|
||||
loop: "{{ cis_permission_stats.results | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.path }}"
|
||||
when: item.stat.exists
|
||||
|
||||
@@ -1,31 +1,138 @@
|
||||
---
|
||||
- name: Add Security related lines into config files
|
||||
- name: Restrict core dumps
|
||||
when: cis_effective_rules.core_dumps | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/security/limits.conf
|
||||
regexp: '^\*\s+hard\s+core\s+'
|
||||
line: "* hard core 0"
|
||||
|
||||
- name: Ensure the systemd coredump drop-in directory exists (CIS L1+)
|
||||
when:
|
||||
- cis_effective_rules.core_dumps | default(false)
|
||||
- cis_strict | default(false)
|
||||
ansible.builtin.file:
|
||||
path: /mnt/etc/systemd/coredump.conf.d
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Disable systemd core dump storage and backtraces (CIS L1+)
|
||||
when:
|
||||
- cis_effective_rules.core_dumps | default(false)
|
||||
- cis_strict | default(false)
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/systemd/coredump.conf.d/10-cis.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Coredump]
|
||||
Storage=none
|
||||
ProcessSizeMax=0
|
||||
|
||||
- name: Set password quality requirements
|
||||
when: cis_effective_rules.pwquality | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/security/pwquality.conf
|
||||
regexp: "{{ item.regexp }}"
|
||||
line: "{{ item.line }}"
|
||||
loop:
|
||||
- {regexp: '^\s*#?\s*minlen\s*=', line: "minlen = {{ cis_cfg.pwquality_minlen }}"}
|
||||
- {regexp: '^\s*#?\s*dcredit\s*=', line: "dcredit = -1"}
|
||||
- {regexp: '^\s*#?\s*ucredit\s*=', line: "ucredit = -1"}
|
||||
- {regexp: '^\s*#?\s*ocredit\s*=', line: "ocredit = -1"}
|
||||
- {regexp: '^\s*#?\s*lcredit\s*=', line: "lcredit = -1"}
|
||||
loop_control:
|
||||
label: "{{ item.line }}"
|
||||
|
||||
# Stricter complexity SSG cis_server_l1 checks; affects only new-password changes.
|
||||
- name: Set strict password quality requirements (CIS L1+)
|
||||
when:
|
||||
- cis_effective_rules.pwquality | default(false)
|
||||
- cis_strict | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/security/pwquality.conf
|
||||
regexp: "{{ item.regexp }}"
|
||||
line: "{{ item.line }}"
|
||||
loop:
|
||||
- {regexp: '^\s*#?\s*difok\s*=', line: "difok = {{ cis_cfg.pwquality_difok }}"}
|
||||
- {regexp: '^\s*#?\s*maxrepeat\s*=', line: "maxrepeat = {{ cis_cfg.pwquality_maxrepeat }}"}
|
||||
- {regexp: '^\s*#?\s*maxsequence\s*=', line: "maxsequence = {{ cis_cfg.pwquality_maxsequence }}"}
|
||||
- {regexp: '^\s*#?\s*minclass\s*=', line: "minclass = {{ cis_cfg.pwquality_minclass }}"}
|
||||
- {regexp: '^\s*#?\s*dictcheck\s*=', line: "dictcheck = {{ cis_cfg.pwquality_dictcheck }}"}
|
||||
- {regexp: '^\s*#?\s*enforce_for_root\b', line: "enforce_for_root"}
|
||||
loop_control:
|
||||
label: "{{ item.line }}"
|
||||
|
||||
- name: Set the default shell umask
|
||||
when: cis_effective_rules.umask_default | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}'
|
||||
regexp: '^\s*umask\s+\d+'
|
||||
line: "umask {{ cis_cfg.umask }}"
|
||||
|
||||
- name: Set the shell idle timeout
|
||||
when: cis_effective_rules.shell_timeout | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}'
|
||||
regexp: '^\s*(export\s+)?TMOUT='
|
||||
line: "export TMOUT={{ cis_cfg.tmout }}"
|
||||
|
||||
# A drop-in survives systemd upgrades; the RHEL vendor journald.conf does not.
|
||||
- name: Ensure the journald drop-in directory exists
|
||||
when: cis_effective_rules.journald_persistent | default(false)
|
||||
ansible.builtin.file:
|
||||
path: /mnt/etc/systemd/journald.conf.d
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Enable persistent journald storage
|
||||
when: cis_effective_rules.journald_persistent | default(false)
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/systemd/journald.conf.d/10-cis.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Journal]
|
||||
Storage=persistent
|
||||
|
||||
- name: Compress large journald log files (CIS L1+)
|
||||
when:
|
||||
- cis_effective_rules.journald_persistent | default(false)
|
||||
- cis_strict | default(false)
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/systemd/journald.conf.d/20-cis-compress.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Journal]
|
||||
Compress=yes
|
||||
|
||||
- name: Log sudo commands
|
||||
when: cis_effective_rules.sudo_logfile | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/sudoers
|
||||
regexp: '^\s*Defaults\s+logfile='
|
||||
line: 'Defaults logfile="/var/log/sudo.log"'
|
||||
|
||||
- name: Require a pty for sudo (CIS L1+)
|
||||
when:
|
||||
- cis_effective_rules.sudo_logfile | default(false)
|
||||
- cis_strict | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/sudoers
|
||||
regexp: '^\s*Defaults\s+use_pty\b'
|
||||
line: "Defaults use_pty"
|
||||
|
||||
- name: Restrict su to the wheel group
|
||||
when: cis_effective_rules.su_restriction | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/pam.d/su
|
||||
regexp: '^\s*#?\s*auth\s+required\s+pam_wheel\.so'
|
||||
line: auth required pam_wheel.so
|
||||
|
||||
- name: Configure account lockout
|
||||
when: cis_effective_rules.faillock | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ item.path }}"
|
||||
regexp: "{{ item.regexp }}"
|
||||
line: "{{ item.content }}"
|
||||
line: "{{ item.line }}"
|
||||
loop:
|
||||
- { path: /mnt/etc/security/limits.conf, regexp: '^\*\s+hard\s+core\s+', content: "* hard core 0" }
|
||||
- { path: /mnt/etc/security/pwquality.conf, regexp: '^\s*#?\s*minlen\s*=', content: "minlen = {{ cis_cfg.pwquality_minlen }}" }
|
||||
- { path: /mnt/etc/security/pwquality.conf, regexp: '^\s*#?\s*dcredit\s*=', content: dcredit = -1 }
|
||||
- { path: /mnt/etc/security/pwquality.conf, regexp: '^\s*#?\s*ucredit\s*=', content: ucredit = -1 }
|
||||
- { path: /mnt/etc/security/pwquality.conf, regexp: '^\s*#?\s*ocredit\s*=', content: ocredit = -1 }
|
||||
- { path: /mnt/etc/security/pwquality.conf, regexp: '^\s*#?\s*lcredit\s*=', content: lcredit = -1 }
|
||||
- path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}'
|
||||
regexp: '^\s*umask\s+\d+'
|
||||
content: "umask {{ cis_cfg.umask }}"
|
||||
- path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}'
|
||||
regexp: '^\s*(export\s+)?TMOUT='
|
||||
content: "export TMOUT={{ cis_cfg.tmout }}"
|
||||
- path: '/mnt/{{ "usr/lib/systemd/journald.conf" if is_rhel | bool else "etc/systemd/journald.conf" }}'
|
||||
regexp: '^\s*#?\s*Storage='
|
||||
content: Storage=persistent
|
||||
- path: /mnt/etc/sudoers
|
||||
regexp: '^\s*Defaults\s+logfile='
|
||||
content: Defaults logfile="/var/log/sudo.log"
|
||||
- path: /mnt/etc/pam.d/su
|
||||
regexp: '^\s*#?\s*auth\s+required\s+pam_wheel\.so'
|
||||
content: auth required pam_wheel.so
|
||||
- path: >-
|
||||
/mnt/etc/{{
|
||||
"pam.d/common-auth"
|
||||
@@ -35,7 +142,7 @@
|
||||
else "pam.d/system-auth"
|
||||
}}
|
||||
regexp: '^\s*auth\s+required\s+pam_faillock\.so'
|
||||
content: >-
|
||||
line: >-
|
||||
auth required pam_faillock.so onerr=fail audit silent deny={{ cis_cfg.faillock_deny }} unlock_time={{ cis_cfg.faillock_unlock_time }}
|
||||
- path: >-
|
||||
/mnt/etc/{{
|
||||
@@ -46,17 +153,53 @@
|
||||
else "pam.d/system-auth"
|
||||
}}
|
||||
regexp: '^\s*account\s+required\s+pam_faillock\.so'
|
||||
content: account required pam_faillock.so
|
||||
- path: >-
|
||||
/mnt/etc/pam.d/{{
|
||||
"common-password"
|
||||
if is_debian | bool
|
||||
else "passwd"
|
||||
}}
|
||||
regexp: '^\s*password\s+\[success=1.*\]\s+pam_unix\.so'
|
||||
content: >-
|
||||
password [success=1 default=ignore] pam_unix.so obscure sha512 remember={{ cis_cfg.password_remember }}
|
||||
- { path: /mnt/etc/hosts.deny, regexp: '^ALL:\s*ALL', content: "ALL: ALL" }
|
||||
- { path: /mnt/etc/hosts.allow, regexp: '^sshd:\s*ALL', content: "sshd: ALL" }
|
||||
line: account required pam_faillock.so
|
||||
loop_control:
|
||||
label: "{{ item.content }}"
|
||||
label: "{{ item.regexp }}"
|
||||
|
||||
- name: Enforce password history
|
||||
when: cis_effective_rules.password_history | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: >-
|
||||
/mnt/etc/pam.d/{{
|
||||
"common-password"
|
||||
if is_debian | bool
|
||||
else "passwd"
|
||||
}}
|
||||
regexp: '^\s*password\s+\[success=1.*\]\s+pam_unix\.so'
|
||||
line: >-
|
||||
password [success=1 default=ignore] pam_unix.so obscure sha512 remember={{ cis_cfg.password_remember }}
|
||||
|
||||
# SSG cis_server_l1 checks pam_pwhistory (not pam_unix remember) in the auth-stack
|
||||
# files; affects only password changes, so no login-lockout risk. EL9 has no
|
||||
# authselect path here (same direct-edit the faillock rule above uses).
|
||||
- name: Enforce password reuse limit via pam_pwhistory (CIS L1+)
|
||||
when:
|
||||
- cis_effective_rules.password_history | default(false)
|
||||
- cis_strict | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ item }}"
|
||||
regexp: '^\s*password\s+(requisite|required)\s+pam_pwhistory\.so'
|
||||
line: "password requisite pam_pwhistory.so use_authtok remember={{ cis_cfg.pwhistory_remember }} enforce_for_root"
|
||||
insertbefore: '^\s*password\s+.*pam_unix\.so'
|
||||
loop: >-
|
||||
{{
|
||||
['/mnt/etc/pam.d/system-auth', '/mnt/etc/pam.d/password-auth']
|
||||
if is_rhel | bool
|
||||
else (['/mnt/etc/pam.d/common-password'] if is_debian | bool else [])
|
||||
}}
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
|
||||
|
||||
- name: Configure TCP wrappers
|
||||
when: cis_effective_rules.tcp_wrappers | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ item.path }}"
|
||||
regexp: "{{ item.regexp }}"
|
||||
line: "{{ item.line }}"
|
||||
loop:
|
||||
- {path: /mnt/etc/hosts.deny, regexp: '^ALL:\s*ALL', line: "ALL: ALL"}
|
||||
- {path: /mnt/etc/hosts.allow, regexp: '^sshd:\s*ALL', line: "sshd: ALL"}
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
- name: Adjust SSHD config
|
||||
when: cis_effective_rules.sshd_hardening | default(false)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/ssh/sshd_config
|
||||
regexp: ^\s*#?{{ item.option }}\s+.*$
|
||||
@@ -9,6 +10,7 @@
|
||||
label: "{{ item.option }}"
|
||||
|
||||
- name: Detect target OpenSSH version
|
||||
when: cis_effective_rules.sshd_hardening | default(false)
|
||||
ansible.builtin.shell: >-
|
||||
set -o pipefail && {{ chroot_command }} ssh -V 2>&1 | grep -oP 'OpenSSH_\K[0-9]+\.[0-9]+'
|
||||
args:
|
||||
@@ -18,6 +20,7 @@
|
||||
failed_when: false
|
||||
|
||||
- name: Append CIS specific configurations to sshd_config
|
||||
when: cis_effective_rules.sshd_hardening | default(false)
|
||||
vars:
|
||||
cis_sshd_has_mlkem: "{{ (cis_sshd_openssh_version.stdout | default('0.0') is version('9.9', '>=')) }}"
|
||||
cis_sshd_kex: >-
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
---
|
||||
- name: Create a consolidated sysctl configuration file
|
||||
when: cis_effective_rules.sysctl_hardening | default(false)
|
||||
vars:
|
||||
# ipv6_disable is a separate rule: when off, drop the disable_ipv6 keys but keep the rest.
|
||||
_cis_sysctl: >-
|
||||
{{ cis_cfg.sysctl
|
||||
if (cis_effective_rules.ipv6_disable | default(false))
|
||||
else (cis_cfg.sysctl | dict2items | rejectattr('key', 'search', 'disable_ipv6') | items2dict) }}
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/sysctl.d/10-cis.conf
|
||||
# 99- so CIS wins: a 10- name loses to vendor /usr/lib/sysctl.d/10-default-yama-scope.conf
|
||||
# (later basename applies last), which reset kernel.yama.ptrace_scope back to 0.
|
||||
dest: /mnt/etc/sysctl.d/99-cis.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
## CIS Sysctl configurations
|
||||
{% for key, value in cis_cfg.sysctl | dictsort %}
|
||||
{% for key, value in _cis_sysctl | dictsort %}
|
||||
{{ key }}={{ value }}
|
||||
{% endfor %}
|
||||
|
||||
11
roles/cis/tasks/warning_banners.yml
Normal file
11
roles/cis/tasks/warning_banners.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Set login warning banners
|
||||
when: cis_effective_rules.warning_banners | default(false)
|
||||
ansible.builtin.copy:
|
||||
dest: "/mnt/etc/{{ item }}"
|
||||
content: "{{ cis_cfg.banner_text }}\n"
|
||||
mode: "0644"
|
||||
loop:
|
||||
- issue
|
||||
- issue.net
|
||||
- motd
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
# OS-specific binary names for CIS permission targets.
|
||||
# fusermount3 is the modern name; older distros still use fusermount.
|
||||
# fusermount3 is the modern name; older distros still ship fusermount.
|
||||
cis_fusermount_binary: >-
|
||||
{{
|
||||
'fusermount3'
|
||||
@@ -19,3 +18,235 @@ cis_write_binary: >-
|
||||
if (os == 'debian' and (os_version | string) == '11')
|
||||
else 'write'
|
||||
}}
|
||||
|
||||
cis_pkg_install: >-
|
||||
{{ chroot_command }} {{
|
||||
'apt-get install -y'
|
||||
if is_debian | bool
|
||||
else 'pacman -S --noconfirm'
|
||||
if os == 'archlinux'
|
||||
else 'dnf install -y'
|
||||
}}
|
||||
|
||||
# Rule catalog: control -> CIS level + whether a task implements it.
|
||||
# `default` enables only implemented rules; `l1`/`l2` add the level-tagged ones.
|
||||
cis_rule_catalog:
|
||||
module_blacklist: {level: l1, implemented: true} # fs/net modprobe blacklist (list per profile)
|
||||
usb_lockdown: {level: l2, implemented: true} # udev authorized_default=0 (aggressive)
|
||||
sysctl_hardening: {level: l1, implemented: true}
|
||||
ipv6_disable: {level: l2, implemented: true} # disable_ipv6 subset of the sysctl set
|
||||
umask_default: {level: l1, implemented: true}
|
||||
empty_password_login: {level: l1, implemented: true}
|
||||
pwquality: {level: l1, implemented: true}
|
||||
core_dumps: {level: l1, implemented: true}
|
||||
shell_timeout: {level: l1, implemented: true}
|
||||
journald_persistent: {level: l1, implemented: true}
|
||||
sudo_logfile: {level: l1, implemented: true}
|
||||
su_restriction: {level: l1, implemented: true}
|
||||
faillock: {level: l1, implemented: true}
|
||||
password_history: {level: l1, implemented: true}
|
||||
tcp_wrappers: {level: l1, implemented: true}
|
||||
crypto_policy: {level: l1, implemented: true} # RedHat non-Fedora only
|
||||
mask_services: {level: l1, implemented: true}
|
||||
cron_at_access: {level: l1, implemented: true}
|
||||
file_permissions: {level: l1, implemented: true}
|
||||
sshd_hardening: {level: l1, implemented: true}
|
||||
password_expiry: {level: l1, implemented: true} # login.defs aging policy
|
||||
aide: {level: l1, implemented: true} # file-integrity db + daily check
|
||||
warning_banners: {level: l1, implemented: true} # /etc/issue, issue.net, motd
|
||||
auditd: {level: l2, implemented: true} # audit daemon + CIS rule set
|
||||
grub_password: {level: l1, implemented: true} # opt-in only; needs params.grub_password_hash
|
||||
|
||||
# Rules not listed are off. A per-host system.features.cis.rules map overlays this.
|
||||
cis_profiles:
|
||||
# default = established house behaviour, kept byte-for-byte unchanged.
|
||||
default:
|
||||
module_blacklist: true
|
||||
usb_lockdown: true
|
||||
sysctl_hardening: true
|
||||
ipv6_disable: true
|
||||
umask_default: true
|
||||
empty_password_login: true
|
||||
pwquality: true
|
||||
core_dumps: true
|
||||
shell_timeout: true
|
||||
journald_persistent: true
|
||||
sudo_logfile: true
|
||||
su_restriction: true
|
||||
faillock: true
|
||||
password_history: true
|
||||
tcp_wrappers: true
|
||||
crypto_policy: true
|
||||
mask_services: true
|
||||
cron_at_access: true
|
||||
file_permissions: true
|
||||
sshd_hardening: true
|
||||
# l1 = clean CIS Level 1: drops the L2 extras (usb_lockdown, ipv6_disable).
|
||||
l1:
|
||||
module_blacklist: true
|
||||
sysctl_hardening: true
|
||||
umask_default: true
|
||||
empty_password_login: true
|
||||
pwquality: true
|
||||
core_dumps: true
|
||||
shell_timeout: true
|
||||
journald_persistent: true
|
||||
sudo_logfile: true
|
||||
su_restriction: true
|
||||
faillock: true
|
||||
password_history: true
|
||||
tcp_wrappers: true
|
||||
crypto_policy: true
|
||||
mask_services: true
|
||||
cron_at_access: true
|
||||
file_permissions: true
|
||||
sshd_hardening: true
|
||||
password_expiry: true
|
||||
aide: true
|
||||
warning_banners: true
|
||||
# l2 = l1 plus the defence-in-depth Level 2 controls.
|
||||
l2:
|
||||
module_blacklist: true
|
||||
usb_lockdown: true
|
||||
sysctl_hardening: true
|
||||
ipv6_disable: true
|
||||
umask_default: true
|
||||
empty_password_login: true
|
||||
pwquality: true
|
||||
core_dumps: true
|
||||
shell_timeout: true
|
||||
journald_persistent: true
|
||||
sudo_logfile: true
|
||||
su_restriction: true
|
||||
faillock: true
|
||||
password_history: true
|
||||
tcp_wrappers: true
|
||||
crypto_policy: true
|
||||
mask_services: true
|
||||
cron_at_access: true
|
||||
file_permissions: true
|
||||
sshd_hardening: true
|
||||
password_expiry: true
|
||||
aide: true
|
||||
warning_banners: true
|
||||
auditd: true
|
||||
|
||||
# Override per host via system.features.cis.params: dicts deep-merge,
|
||||
# list-valued keys (e.g. sshd_options) replace wholesale.
|
||||
cis_param_defaults:
|
||||
modules_blacklist:
|
||||
- freevxfs
|
||||
- jffs2
|
||||
- hfs
|
||||
- hfsplus
|
||||
- cramfs
|
||||
- udf
|
||||
- usb-storage
|
||||
- dccp
|
||||
- sctp
|
||||
- rds
|
||||
- tipc
|
||||
- firewire-core
|
||||
- firewire-sbp2
|
||||
- thunderbolt
|
||||
sysctl:
|
||||
fs.suid_dumpable: 0
|
||||
kernel.dmesg_restrict: 1
|
||||
kernel.kptr_restrict: 2
|
||||
kernel.perf_event_paranoid: 3
|
||||
kernel.unprivileged_bpf_disabled: 1
|
||||
kernel.yama.ptrace_scope: 2
|
||||
kernel.randomize_va_space: 2
|
||||
net.ipv4.ip_forward: 0
|
||||
net.ipv4.tcp_syncookies: 1
|
||||
net.ipv4.icmp_echo_ignore_broadcasts: 1
|
||||
net.ipv4.icmp_ignore_bogus_error_responses: 1
|
||||
net.ipv4.conf.all.log_martians: 1
|
||||
net.ipv4.conf.all.rp_filter: 1
|
||||
net.ipv4.conf.all.secure_redirects: 0
|
||||
net.ipv4.conf.all.send_redirects: 0
|
||||
net.ipv4.conf.all.accept_redirects: 0
|
||||
net.ipv4.conf.all.accept_source_route: 0
|
||||
net.ipv4.conf.all.arp_ignore: 1
|
||||
net.ipv4.conf.all.arp_announce: 2
|
||||
net.ipv4.conf.default.log_martians: 1
|
||||
net.ipv4.conf.default.rp_filter: 1
|
||||
net.ipv4.conf.default.secure_redirects: 0
|
||||
net.ipv4.conf.default.send_redirects: 0
|
||||
net.ipv4.conf.default.accept_redirects: 0
|
||||
net.ipv6.conf.all.accept_redirects: 0
|
||||
net.ipv6.conf.all.disable_ipv6: 1
|
||||
net.ipv6.conf.default.accept_redirects: 0
|
||||
net.ipv6.conf.default.disable_ipv6: 1
|
||||
net.ipv6.conf.lo.disable_ipv6: 1
|
||||
sshd_options:
|
||||
- {option: LogLevel, value: VERBOSE}
|
||||
- {option: LoginGraceTime, value: "60"}
|
||||
- {option: PermitRootLogin, value: "no"}
|
||||
- {option: StrictModes, value: "yes"}
|
||||
- {option: MaxAuthTries, value: "4"}
|
||||
- {option: MaxSessions, value: "10"}
|
||||
- {option: MaxStartups, value: "10:30:60"}
|
||||
- {option: PubkeyAuthentication, value: "yes"}
|
||||
- {option: HostbasedAuthentication, value: "no"}
|
||||
- {option: IgnoreRhosts, value: "yes"}
|
||||
- {option: PasswordAuthentication, value: "no"}
|
||||
- {option: PermitEmptyPasswords, value: "no"}
|
||||
- {option: KerberosAuthentication, value: "no"}
|
||||
- {option: GSSAPIAuthentication, value: "no"}
|
||||
- {option: AllowAgentForwarding, value: "no"}
|
||||
- {option: AllowTcpForwarding, value: "no"}
|
||||
- {option: KbdInteractiveAuthentication, value: "no"}
|
||||
- {option: GatewayPorts, value: "no"}
|
||||
- {option: X11Forwarding, value: "no"}
|
||||
- {option: PermitUserEnvironment, value: "no"}
|
||||
- {option: ClientAliveInterval, value: "300"}
|
||||
- {option: ClientAliveCountMax, value: "1"}
|
||||
- {option: PermitTunnel, value: "no"}
|
||||
- {option: Banner, value: /etc/issue.net}
|
||||
pwquality_minlen: 14
|
||||
# pwquality strict set (l1/l2 only, cis_strict): SSG cis_server_l1 values.
|
||||
pwquality_difok: 2
|
||||
pwquality_maxrepeat: 3
|
||||
pwquality_maxsequence: 3
|
||||
pwquality_minclass: 4
|
||||
pwquality_dictcheck: 1
|
||||
tmout: 900
|
||||
umask: "077"
|
||||
umask_profile: "027"
|
||||
faillock_deny: 5
|
||||
faillock_unlock_time: 900
|
||||
password_remember: 5
|
||||
# pwhistory remember (l1/l2 only, cis_strict): SSG wants 24 via pam_pwhistory.
|
||||
pwhistory_remember: 24
|
||||
# password_expiry (l1/l2): /etc/login.defs aging.
|
||||
pass_max_days: 365
|
||||
pass_min_days: 1
|
||||
pass_warn_age: 7
|
||||
# account_disable_post_pw_expiration (l1/l2): days after expiry to lock (SSG=45).
|
||||
pass_inactive: 45
|
||||
# aide (l1/l2): daily integrity-check schedule.
|
||||
aide_cron_hour: "5"
|
||||
aide_cron_minute: "0"
|
||||
# warning_banners (l1/l2): login/MOTD text.
|
||||
banner_text: "Authorized access only. All activity may be monitored and reported."
|
||||
# grub_password (opt-in only): a grub2 pbkdf2 hash; empty unless opted in.
|
||||
grub_password_hash: ""
|
||||
# insecure_packages (l1/l2 only, cis_strict): legacy cleartext clients to remove.
|
||||
insecure_packages:
|
||||
- telnet
|
||||
|
||||
# Only the module blacklist differs by profile: l1 trims to the L1 filesystem
|
||||
# modules; default/l2 keep the full list.
|
||||
cis_profile_params:
|
||||
default: {}
|
||||
l1:
|
||||
modules_blacklist:
|
||||
- cramfs
|
||||
- freevxfs
|
||||
- jffs2
|
||||
- hfs
|
||||
- hfsplus
|
||||
- udf
|
||||
- usb-storage
|
||||
l2: {}
|
||||
|
||||
@@ -103,6 +103,9 @@ system_defaults:
|
||||
features:
|
||||
cis:
|
||||
enabled: false
|
||||
profile: default # default|l1|l2 (default = current house behaviour)
|
||||
rules: {} # per-rule overrides, e.g. {usb_lockdown: false}
|
||||
params: {} # parameter overrides, e.g. {pwquality_minlen: 16}
|
||||
selinux:
|
||||
enabled: true
|
||||
firewall:
|
||||
|
||||
@@ -142,6 +142,9 @@
|
||||
features:
|
||||
cis:
|
||||
enabled: "{{ system_raw.features.cis.enabled | bool }}"
|
||||
profile: "{{ system_raw.features.cis.profile | default('default') | string }}"
|
||||
rules: "{{ system_raw.features.cis.rules | default({}) }}"
|
||||
params: "{{ system_raw.features.cis.params | default({}) }}"
|
||||
selinux:
|
||||
enabled: "{{ system_raw.features.selinux.enabled | bool }}"
|
||||
firewall:
|
||||
|
||||
Reference in New Issue
Block a user