diff --git a/roles/cis/defaults/main.yml b/roles/cis/defaults/main.yml index 0a3611b..15d2ff7 100644 --- a/roles/cis/defaults/main.yml +++ b/roles/cis/defaults/main.yml @@ -1,4 +1,85 @@ --- +# 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' }}" diff --git a/roles/cis/tasks/_normalize.yml b/roles/cis/tasks/_normalize.yml new file mode 100644 index 0000000..020cac0 --- /dev/null +++ b/roles/cis/tasks/_normalize.yml @@ -0,0 +1,4 @@ +--- +- name: Build cis_cfg from defaults and user overrides + ansible.builtin.set_fact: + cis_cfg: "{{ cis_defaults | combine(cis | default({}), recursive=true) }}" diff --git a/roles/cis/tasks/auth.yml b/roles/cis/tasks/auth.yml index 741136e..588436c 100644 --- a/roles/cis/tasks/auth.yml +++ b/roles/cis/tasks/auth.yml @@ -3,7 +3,7 @@ ansible.builtin.lineinfile: path: "/mnt/etc/profile" regexp: "^(\\s*)umask\\s+\\d+" - line: "umask 027" + line: "umask {{ cis_cfg.umask_profile }}" # Non-RHEL/non-Debian distros: loop evaluates to [] (intentional skip) - name: Prevent Login to Accounts With Empty Password diff --git a/roles/cis/tasks/main.yml b/roles/cis/tasks/main.yml index 74a84c3..7cab50c 100644 --- a/roles/cis/tasks/main.yml +++ b/roles/cis/tasks/main.yml @@ -1,4 +1,7 @@ --- +- name: Normalize CIS configuration + ansible.builtin.include_tasks: _normalize.yml + - name: Include CIS hardening tasks ansible.builtin.include_tasks: "{{ cis_task }}" loop: diff --git a/roles/cis/tasks/modules.yml b/roles/cis/tasks/modules.yml index 06fbde2..281fb89 100644 --- a/roles/cis/tasks/modules.yml +++ b/roles/cis/tasks/modules.yml @@ -1,23 +1,8 @@ --- - name: Disable Kernel Modules vars: - cis_modules_base: - - freevxfs - - jffs2 - - hfs - - hfsplus - - cramfs - - udf - - usb-storage - - dccp - - sctp - - rds - - tipc - - firewire-core - - firewire-sbp2 - - thunderbolt cis_modules_squashfs: "{{ [] if os in ['ubuntu', 'ubuntu-lts'] else ['squashfs'] }}" - cis_modules_all: "{{ cis_modules_base + cis_modules_squashfs }}" + cis_modules_all: "{{ cis_cfg.modules_blacklist + cis_modules_squashfs }}" ansible.builtin.copy: dest: /mnt/etc/modprobe.d/cis.conf mode: "0644" diff --git a/roles/cis/tasks/security_lines.yml b/roles/cis/tasks/security_lines.yml index dc6348e..dfa35f8 100644 --- a/roles/cis/tasks/security_lines.yml +++ b/roles/cis/tasks/security_lines.yml @@ -6,17 +6,17 @@ line: "{{ item.content }}" 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 = 14 } + - { 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 077 + content: "umask {{ cis_cfg.umask }}" - path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}' regexp: '^\s*(export\s+)?TMOUT=' - content: export TMOUT=900 + 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 @@ -36,7 +36,7 @@ }} regexp: '^\s*auth\s+required\s+pam_faillock\.so' content: >- - auth required pam_faillock.so onerr=fail audit silent deny=5 unlock_time=900 + auth required pam_faillock.so onerr=fail audit silent deny={{ cis_cfg.faillock_deny }} unlock_time={{ cis_cfg.faillock_unlock_time }} - path: >- /mnt/etc/{{ "pam.d/common-account" @@ -55,7 +55,7 @@ }} regexp: '^\s*password\s+\[success=1.*\]\s+pam_unix\.so' content: >- - password [success=1 default=ignore] pam_unix.so obscure sha512 remember=5 + 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" } loop_control: diff --git a/roles/cis/tasks/sshd.yml b/roles/cis/tasks/sshd.yml index 78933c4..2f94341 100644 --- a/roles/cis/tasks/sshd.yml +++ b/roles/cis/tasks/sshd.yml @@ -4,31 +4,7 @@ path: /mnt/etc/ssh/sshd_config regexp: ^\s*#?{{ item.option }}\s+.*$ line: "{{ item.option }} {{ item.value }}" - loop: - - { 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 } + loop: "{{ cis_cfg.sshd_options }}" loop_control: label: "{{ item.option }}" diff --git a/roles/cis/tasks/sysctl.yml b/roles/cis/tasks/sysctl.yml index daa838a..6822667 100644 --- a/roles/cis/tasks/sysctl.yml +++ b/roles/cis/tasks/sysctl.yml @@ -5,35 +5,6 @@ mode: "0644" content: | ## CIS Sysctl configurations - 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 - # Network - # Disable forwarding; override in inventory for routers/containers - 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 - # Disable IPv6; override in inventory if IPv6 is needed - 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 + {% for key, value in cis_cfg.sysctl | dictsort %} + {{ key }}={{ value }} + {% endfor %}