diff --git a/README.md b/README.md index 217bacc1..3e47fd75 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ disruption, the role deletes every `ufw` rule without The role also sets default deny policies, which means that firewall rules needs to be created for any additional ports except those specified in -the `sshd_port` and `ufw_outgoing_traffic` variables. +the `sshd_ports` and `ufw_outgoing_traffic` variables. ## Task Execution and Structure @@ -458,75 +458,101 @@ sshd_accept_env: LANG LC_* sshd_admin_net: - 192.168.0.0/24 - 192.168.1.0/24 -sshd_allow_agent_forwarding: "no" -sshd_allow_groups: sudo -sshd_allow_users: "{{ ansible_user | default(lookup('ansible.builtin.env', 'USER')) }}" -sshd_allow_tcp_forwarding: "no" +sshd_allow_agent_forwarding: false +sshd_allow_groups: + - sudo +sshd_allow_tcp_forwarding: false +sshd_allow_users: + - {{ ansible_user | default(lookup('ansible.builtin.env', 'USER')) }} sshd_authentication_methods: any sshd_banner: /etc/issue.net -sshd_ca_signature_algorithms: >- - ecdsa-sha2-nistp256, - ecdsa-sha2-nistp384, - ecdsa-sha2-nistp521, - ssh-ed25519, - rsa-sha2-256, - rsa-sha2-512, - ssh-rsa -sshd_challenge_response_authentication: "no" -sshd_ciphers: >- - chacha20-poly1305@openssh.com, - aes256-gcm@openssh.com, - aes256-ctr +sshd_ca_signature_algorithms: + - ecdsa-sha2-nistp256 + - ecdsa-sha2-nistp384 + - ecdsa-sha2-nistp521 + - ssh-ed25519 + - rsa-sha2-256 + - rsa-sha2-512 + - ssh-rsa +sshd_kbd_interactive_authentication: false +sshd_ciphers: + - chacha20-poly1305@openssh.com + - aes256-gcm@openssh.com + - aes256-ctr sshd_client_alive_count_max: 1 sshd_client_alive_interval: 200 -sshd_compression: "no" -sshd_gssapi_authentication: "no" -sshd_hostbased_authentication: "no" -sshd_host_key_algorithms: >- - ssh-ed25519-cert-v01@openssh.com, - ssh-rsa-cert-v01@openssh.com, - ssh-ed25519, - ssh-rsa, - ecdsa-sha2-nistp521-cert-v01@openssh.com, - ecdsa-sha2-nistp384-cert-v01@openssh.com, - ecdsa-sha2-nistp256-cert-v01@openssh.com, - ecdsa-sha2-nistp521, - ecdsa-sha2-nistp384, - ecdsa-sha2-nistp256 -sshd_ignore_rhosts: "yes" -sshd_ignore_user_known_hosts: "yes" -sshd_kerberos_authentication: "no" -sshd_kex_algorithms: >- - curve25519-sha256@libssh.org, - ecdh-sha2-nistp521, - ecdh-sha2-nistp384, - ecdh-sha2-nistp256, - diffie-hellman-group-exchange-sha256 -sshd_login_grace_time: 20 +sshd_compression: false +sshd_config_d_force_clear: false +sshd_config_force_replace: false +sshd_debian_banner: false +sshd_deny_groups: [] +sshd_deny_users: [] +sshd_gssapi_authentication: false +sshd_host_key_algorithms: + - ssh-ed25519-cert-v01@openssh.com + - ssh-rsa-cert-v01@openssh.com + - ssh-ed25519 + - ssh-rsa + - ecdsa-sha2-nistp521-cert-v01@openssh.com + - ecdsa-sha2-nistp384-cert-v01@openssh.com + - ecdsa-sha2-nistp256-cert-v01@openssh.com + - ecdsa-sha2-nistp521 + - ecdsa-sha2-nistp384 + - ecdsa-sha2-nistp256 +sshd_host_keys_files: [] +sshd_host_keys_group: root +sshd_host_keys_mode: "0600" +sshd_host_keys_owner: root +sshd_hostbased_authentication: false +sshd_ignore_rhosts: true +sshd_ignore_user_known_hosts: true +sshd_kerberos_authentication: false +sshd_kex_algorithms: + - curve25519-sha256@libssh.org + - ecdh-sha2-nistp521 + - ecdh-sha2-nistp384 + - ecdh-sha2-nistp256 + - diffie-hellman-group-exchange-sha256 +sshd_listen: + - 0.0.0.0 sshd_log_level: VERBOSE -sshd_macs: >- - hmac-sha2-512-etm@openssh.com, - hmac-sha2-256-etm@openssh.com, - hmac-sha2-512, - hmac-sha2-256 +sshd_login_grace_time: 20 +sshd_macs: + - hmac-sha2-512-etm@openssh.com + - hmac-sha2-256-etm@openssh.com + - hmac-sha2-512 + - hmac-sha2-256 +sshd_match_addresses: {} +sshd_match_groups: {} +sshd_match_local_ports: {} +sshd_match_users: {} sshd_max_auth_tries: 3 sshd_max_sessions: 3 -sshd_max_startups: 10:30:60 -sshd_password_authentication: "no" -sshd_permit_empty_passwords: "no" -sshd_permit_root_login: "no" -sshd_permit_user_environment: "no" -sshd_port: 22 -sshd_print_last_log: "yes" -sshd_print_motd: "no" +sshd_max_startups: '10:30:60' +sshd_password_authentication: false +sshd_permit_empty_passwords: false +sshd_permit_root_login: false +sshd_permit_tunnel: false +sshd_permit_user_environment: false +sshd_ports: + - 22 +sshd_print_last_log: true +sshd_print_motd: false +sshd_print_pam_motd: false sshd_rekey_limit: 512M 1h sshd_required_rsa_size: 2048 -sshd_strict_modes: "yes" -sshd_subsystem: sftp internal-sftp -sshd_tcp_keep_alive: "no" -sshd_use_dns: "no" -sshd_use_pam: "yes" -sshd_x11_forwarding: "no" +sshd_sftp_enabled: true +sshd_sftp_only_chroot: true +sshd_sftp_only_chroot_dir: '%h' +sshd_sftp_only_group: '' +sshd_sftp_subsystem: internal-sftp -f LOCAL6 -l INFO +sshd_strict_modes: true +sshd_syslog_facility: AUTH +sshd_tcp_keep_alive: false +sshd_use_dns: false +sshd_use_pam: true +sshd_use_privilege_separation: sandbox +sshd_x11_forwarding: false ``` > **Note** @@ -538,39 +564,109 @@ sshd_x11_forwarding: "no" For a explanation of the options not described below, please read [https://man.openbsd.org/sshd_config](https://man.openbsd.org/sshd_config). -Only the network(s) defined in `sshd_admin_net` are allowed to -connect to `sshd_port`. Note that additional rules need to be set up in order -to allow access to additional services. +Only the network(s) defined in `sshd_admin_net` are allowed to connect to `sshd_ports`. Note that additional rules need to be set up in order to allow access to additional services. -OpenSSH login is allowed only for users whose primary group or supplementary -group list matches one of the patterns in `sshd_allow_groups`. -OpenSSH login is also allowed for users in `sshd_allow_users`. +OpenSSH login is allowed only for users whose primary group or supplementary group list matches one of the patterns in `sshd_allow_groups`. OpenSSH login is also allowed for users in `sshd_allow_users`. To do the opposite and deny access, use the `sshd_deny_groups` and `sshd_deny_users` parameters, which in turn have priority over the previous parameters. -`sshd_allow_agent_forwarding` specifies whether ssh-agent(1) forwarding is -permitted. +`sshd_allow_agent_forwarding` specifies whether ssh-agent(1) forwarding is permitted. -`sshd_allow_tcp_forwarding` specifies whether TCP forwarding is permitted. -The available options are `yes` or all to allow TCP forwarding, `no` to prevent -all TCP forwarding, `local` to allow local (from the perspective of ssh(1)) -forwarding only or `remote` to allow remote forwarding only. +`sshd_allow_tcp_forwarding` specifies whether TCP forwarding is permitted. The available options are `true|yes` or all to allow TCP forwarding, `false|no` to prevent all TCP forwarding, `local` to allow local (from the perspective of ssh(1)) forwarding only or `remote` to allow remote forwarding only. -`sshd_authentication_methods` specifies the authentication methods that must -be successfully completed in order to grant access to a user. +`sshd_authentication_methods` specifies the authentication methods that must be successfully completed in order to grant access to a user. `sshd_log_level` gives the verbosity level that is used when logging messages. -`sshd_max_auth_tries` and `sshd_max_sessions` specifies the maximum number of -SSH authentication attempts permitted per connection and the maximum number of -open shell, login or subsystem (e.g. sftp) sessions permitted per network +`sshd_max_auth_tries` and `sshd_max_sessions` specifies the maximum number of SSH authentication attempts permitted per connection and the maximum number of open shell, login or subsystem (e.g. sftp) sessions permitted per network connection. -`sshd_password_authentication` specifies whether password authentication is -allowed. +`sshd_password_authentication` specifies whether password authentication is allowed. + +`sshd_ports` specifies the port(s) number that sshd(8) listens on. + +`sshd_required_rsa_size`, RequiredRSASize, will only be set if SSH version is higher than 9.1. + +`sshd_config_d_force_clear` force clear directory `/etc/ssh/sshd_config.d`. Default: `false`. + +`sshd_config_force_replace` force replace configuration file `/etc/ssh/sshd_config`. Default: `false`. + +> **Note** +> +> By default, the role checks whether the directory `/etc/ssh/sshd_config.d` exists and whether it is linked via the `Include` parameter in the `/etc/ssh/sshd_config` file, if so, an additional configuration file is created in `/ etc/ssh/sshd_config.d`, if not, the `/etc/ssh/sshd_config` file is overwritten. + +> **Warning** +> +> If any `sshd_match_(users|groups|addresses|local_ports)` or `sshd_sftp_only_group` parameters is set, the value `true` will be implicit. + +`sshd_host_keys_files` host keys for sshd. If empty `['/etc/ssh/ssh_host_rsa_key', '/etc/ssh/ssh_host_ecdsa_key', '/etc/ssh/ssh_host_ed25519_key']` will be used, as far as supported by the installed sshd version. + +`sshd_host_keys_owner` set owner of host keys for sshd. Default: `root`. +`sshd_host_keys_group` set group of host keys for sshd. Default: `root`. +`sshd_host_keys_mode` set permission of host keys for sshd. Default: `"0600"`. + +`sshd_match_users` add a conditional block for users. If all of the criteria on the Match line are satisfied, the rules/parameters defined on the following lines override those set in the global section of the config file, until either another Match line or the end of the file. + +Expected configuration structure: +```yaml +sshd_match_users: + - user: + rules: + - + - +``` +Example, allow `ubuntu` user access through password authentication and allow `ansible` user access without banner: +```yaml +sshd_match_users: + - user: ubuntu + rules: + - AllowUsers ubuntu + - AuthenticationMethods password + - PasswordAuthentication yes + - user: ansible + rules: + - AllowUsers ansible + - Banner none +``` +`sshd_match_groups` add a conditional block for groups. More details and examples in the parameter description `sshd_match_users`. + +Expected configuration structure: +```yaml +sshd_match_groups: + - group: + rules: + - + - +``` + +`sshd_match_addresses` add a conditional block for adddresses. More details and examples in the parameter description `sshd_match_users`. + +Expected configuration structure: +```yaml +sshd_match_addresses: + - address: + rules: + - + - +``` +`sshd_match_local_ports` add a conditional block for ports. More details and examples in the parameter description `sshd_match_users`. + +Expected configuration structure: +```yaml +sshd_match_ports: + - port: + rules: + - + - +``` + +`sshd_print_pam_motd` specifies whether printing of the MOTD via pam (Debian and Ubuntu). Default: `false`. -`sshd_port` specifies the port number that sshd(8) listens on. +`sshd_sftp_enabled` specifies whether enabled sftp configuration. Default: `true`. +`sshd_sftp_subsystem` Set external subsystem for file transfer daemon. Default: `internal-sftp -f LOCAL6 -l INFO`. +`sshd_sftp_only_group` specifies the name of the group that will have access restricted to the sftp service only. Default: `""`. +`sshd_sftp_only_chroot` specifies group access will be via chroot isolation. Default: `true`. +`sshd_sftp_only_chroot_dir` specifies the chroot directory. Accepts the tokens `%%` (a literal `%`), `%h` (home directory of the user), and `%u` (username). Default: `"%h"`. -`sshd_required_rsa_size`, RequiredRSASize, will only be set if SSH version is -higher than 9.1. +`sshd_syslog_facility` set the facility code that is used when logging messages from sshd.Default: `AUTH`. ### ./defaults/main/suid_sgid_blocklist.yml diff --git a/Vagrantfile b/Vagrantfile index 2fcd9d96..c98ba6cb 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -20,8 +20,8 @@ Vagrant.configure("2") do |config| a.extra_vars = { "ansible_become_pass" => "vagrant", "ansible_python_interpreter" => "/usr/bin/python3", - "sshd_admin_net" => "0.0.0.0/0", - "sshd_allow_groups" => "vagrant sudo debian ubuntu", + "sshd_admin_net" => ["0.0.0.0/0"], + "sshd_allow_groups" => ["vagrant", "sudo", "debian", "ubuntu"], "system_upgrade" => "no", } end @@ -39,8 +39,8 @@ Vagrant.configure("2") do |config| a.limit = "all" a.playbook = "tests/test.yml" a.extra_vars = { - "sshd_admin_net" => "0.0.0.0/0", - "sshd_allow_groups" => "vagrant sudo ubuntu", + "sshd_admin_net" => ["0.0.0.0/0"], + "sshd_allow_groups" => ["vagrant", "sudo", "ubuntu"], "ansible_python_interpreter" => "/usr/bin/python3", } end @@ -58,8 +58,8 @@ Vagrant.configure("2") do |config| a.limit = "all" a.playbook = "tests/test.yml" a.extra_vars = { - "sshd_admin_net" => "0.0.0.0/0", - "sshd_allow_groups" => "vagrant sudo ubuntu", + "sshd_admin_net" => ["0.0.0.0/0"], + "sshd_allow_groups" => ["vagrant", "sudo", "ubuntu"], "ansible_python_interpreter" => "/usr/bin/python3", } end @@ -80,8 +80,8 @@ Vagrant.configure("2") do |config| a.limit = "all" a.playbook = "tests/test.yml" a.extra_vars = { - "sshd_admin_net" => "0.0.0.0/0", - "sshd_allow_groups" => "vagrant sudo", + "sshd_admin_net" => ["0.0.0.0/0"], + "sshd_allow_groups" => ["vagrant", "sudo"], "ansible_python_interpreter" => "/usr/bin/python3", } end diff --git a/defaults/main/sshd.yml b/defaults/main/sshd.yml index 3456d0d4..41a132b2 100644 --- a/defaults/main/sshd.yml +++ b/defaults/main/sshd.yml @@ -3,72 +3,98 @@ sshd_accept_env: LANG LC_* sshd_admin_net: - 192.168.0.0/24 - 192.168.1.0/24 -sshd_allow_agent_forwarding: "no" -sshd_allow_groups: sudo -sshd_allow_users: "{{ ansible_user | default(lookup('ansible.builtin.env', 'USER')) }}" -sshd_allow_tcp_forwarding: "no" +sshd_allow_agent_forwarding: false +sshd_allow_groups: + - sudo +sshd_allow_tcp_forwarding: false +sshd_allow_users: + - "{{ ansible_user | default(lookup('ansible.builtin.env', 'USER')) }}" sshd_authentication_methods: any sshd_banner: /etc/issue.net -sshd_ca_signature_algorithms: >- - ecdsa-sha2-nistp256, - ecdsa-sha2-nistp384, - ecdsa-sha2-nistp521, - ssh-ed25519, - rsa-sha2-256, - rsa-sha2-512, - ssh-rsa -sshd_challenge_response_authentication: "no" -sshd_ciphers: >- - chacha20-poly1305@openssh.com, - aes256-gcm@openssh.com, - aes256-ctr +sshd_ca_signature_algorithms: + - ecdsa-sha2-nistp256 + - ecdsa-sha2-nistp384 + - ecdsa-sha2-nistp521 + - ssh-ed25519 + - rsa-sha2-256 + - rsa-sha2-512 + - ssh-rsa +sshd_kbd_interactive_authentication: false +sshd_ciphers: + - chacha20-poly1305@openssh.com + - aes256-gcm@openssh.com + - aes256-ctr sshd_client_alive_count_max: 1 sshd_client_alive_interval: 200 -sshd_compression: "no" -sshd_gssapi_authentication: "no" -sshd_hostbased_authentication: "no" -sshd_host_key_algorithms: >- - ssh-ed25519-cert-v01@openssh.com, - ssh-rsa-cert-v01@openssh.com, - ssh-ed25519, - ssh-rsa, - ecdsa-sha2-nistp521-cert-v01@openssh.com, - ecdsa-sha2-nistp384-cert-v01@openssh.com, - ecdsa-sha2-nistp256-cert-v01@openssh.com, - ecdsa-sha2-nistp521, - ecdsa-sha2-nistp384, - ecdsa-sha2-nistp256 -sshd_ignore_rhosts: "yes" -sshd_ignore_user_known_hosts: "yes" -sshd_kerberos_authentication: "no" -sshd_kex_algorithms: >- - curve25519-sha256@libssh.org, - ecdh-sha2-nistp521, - ecdh-sha2-nistp384, - ecdh-sha2-nistp256, - diffie-hellman-group-exchange-sha256 -sshd_login_grace_time: 20 +sshd_compression: false +sshd_config_d_force_clear: false +sshd_config_force_replace: false +sshd_debian_banner: false +sshd_deny_groups: [] +sshd_deny_users: [] +sshd_gssapi_authentication: false +sshd_host_key_algorithms: + - ssh-ed25519-cert-v01@openssh.com + - ssh-rsa-cert-v01@openssh.com + - ssh-ed25519 + - ssh-rsa + - ecdsa-sha2-nistp521-cert-v01@openssh.com + - ecdsa-sha2-nistp384-cert-v01@openssh.com + - ecdsa-sha2-nistp256-cert-v01@openssh.com + - ecdsa-sha2-nistp521 + - ecdsa-sha2-nistp384 + - ecdsa-sha2-nistp256 +sshd_host_keys_files: [] +sshd_host_keys_group: root +sshd_host_keys_mode: "0600" +sshd_host_keys_owner: root +sshd_hostbased_authentication: false +sshd_ignore_rhosts: true +sshd_ignore_user_known_hosts: true +sshd_kerberos_authentication: false +sshd_kex_algorithms: + - curve25519-sha256@libssh.org + - ecdh-sha2-nistp521 + - ecdh-sha2-nistp384 + - ecdh-sha2-nistp256 + - diffie-hellman-group-exchange-sha256 +sshd_listen: + - 0.0.0.0 sshd_log_level: VERBOSE -sshd_macs: >- - hmac-sha2-512-etm@openssh.com, - hmac-sha2-256-etm@openssh.com, - hmac-sha2-512, - hmac-sha2-256 +sshd_login_grace_time: 20 +sshd_macs: + - hmac-sha2-512-etm@openssh.com + - hmac-sha2-256-etm@openssh.com + - hmac-sha2-512 + - hmac-sha2-256 +sshd_match_addresses: {} +sshd_match_groups: {} +sshd_match_local_ports: {} +sshd_match_users: {} sshd_max_auth_tries: 3 sshd_max_sessions: 3 -sshd_max_startups: 10:30:60 -sshd_password_authentication: "no" -sshd_permit_empty_passwords: "no" -sshd_permit_root_login: "no" -sshd_permit_user_environment: "no" -sshd_port: 22 -sshd_print_last_log: "yes" -sshd_print_motd: "no" +sshd_max_startups: '10:30:60' +sshd_password_authentication: false +sshd_permit_empty_passwords: false +sshd_permit_root_login: false +sshd_permit_tunnel: false +sshd_permit_user_environment: false +sshd_ports: + - 22 +sshd_print_last_log: true +sshd_print_motd: false +sshd_print_pam_motd: false sshd_rekey_limit: 512M 1h sshd_required_rsa_size: 2048 -sshd_strict_modes: "yes" -sshd_subsystem: sftp internal-sftp -sshd_tcp_keep_alive: "no" -sshd_use_dns: "no" -sshd_use_pam: "yes" -sshd_x11_forwarding: "no" +sshd_sftp_enabled: true +sshd_sftp_only_chroot: true +sshd_sftp_only_chroot_dir: '%h' +sshd_sftp_only_group: '' +sshd_sftp_subsystem: internal-sftp -f LOCAL6 -l INFO +sshd_strict_modes: true +sshd_syslog_facility: AUTH +sshd_tcp_keep_alive: false +sshd_use_dns: false +sshd_use_pam: true +sshd_use_privilege_separation: sandbox +sshd_x11_forwarding: false diff --git a/genREADME.sh b/genREADME.sh index b018f0c1..b01381d2 100644 --- a/genREADME.sh +++ b/genREADME.sh @@ -103,7 +103,7 @@ disruption, the role deletes every \`ufw\` rule without The role also sets default deny policies, which means that firewall rules needs to be created for any additional ports except those specified in -the \`sshd_port\` and \`ufw_outgoing_traffic\` variables. +the \`sshd_ports\` and \`ufw_outgoing_traffic\` variables. ## Task Execution and Structure diff --git a/molecule/almalinux/molecule.yml b/molecule/almalinux/molecule.yml index aaf7cae1..080498be 100644 --- a/molecule/almalinux/molecule.yml +++ b/molecule/almalinux/molecule.yml @@ -19,13 +19,19 @@ provisioner: host_vars: almalinux8: enable_timesyncd: false - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo suid_sgid_permissions: false almalinux9: enable_timesyncd: false - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo suid_sgid_permissions: false platforms: - name: almalinux8 diff --git a/molecule/debian/molecule.yml b/molecule/debian/molecule.yml index b1fe46c7..3c7afa78 100644 --- a/molecule/debian/molecule.yml +++ b/molecule/debian/molecule.yml @@ -19,21 +19,30 @@ provisioner: host_vars: bookworm: ansible_python_interpreter: /usr/bin/python3 - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo suid_sgid_permissions: false system_upgrade: false bullseye: ansible_become_pass: vagrant ansible_python_interpreter: /usr/bin/python3 - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo suid_sgid_permissions: false testing: ansible_become_pass: vagrant ansible_python_interpreter: /usr/bin/python3 - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo suid_sgid_permissions: false platforms: - name: bookworm diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml index 240b44e2..1179ddab 100644 --- a/molecule/default/molecule.yml +++ b/molecule/default/molecule.yml @@ -18,33 +18,48 @@ provisioner: disable_wireless: true enable_timesyncd: false install_aide: false - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo suid_sgid_permissions: false almalinux9: enable_timesyncd: false - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo bookworm: ansible_become_pass: vagrant ansible_python_interpreter: /usr/bin/python3 disable_wireless: false - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo system_upgrade: false focal: block_blacklisted: true disable_wireless: true install_aide: false - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo suid_sgid_permissions: false jammy: disable_ipv6: true block_blacklisted: true disable_wireless: true - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo suid_sgid_permissions: false platforms: - name: almalinux8 diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml index 6c4fa95a..f431ff3a 100644 --- a/molecule/default/verify.yml +++ b/molecule/default/verify.yml @@ -62,6 +62,7 @@ cmd: ssh -V changed_when: false failed_when: false + check_mode: false register: ssh_version tags: - sshd @@ -225,148 +226,166 @@ failed_when: sshd_config_d is changed when: sshd_config_directory.stat.exists + - name: Set sshd_config_parameters + ansible.builtin.set_fact: + sshd_config_parameters: + - "{{ 'AcceptEnv ' + sshd_accept_env if (sshd_accept_env is defined and sshd_accept_env | length != 0)}}" + - AddressFamily {{ 'inet' if (disable_ipv6 | bool) else 'any' }} + - AllowGroups vagrant sudo + - AllowAgentForwarding {{ 'yes' if (sshd_allow_agent_forwarding | bool) else 'no' }} + - >- + {%- if (ssh_installed_version is version('6.2', '>=')) -%} + {%- if (sshd_allow_tcp_forwarding in ('yes', 'no', 'local', 'all', 'remote')) -%} + AllowTcpForwarding {{ sshd_allow_tcp_forwarding }} + {%- else -%} + AllowTcpForwarding {{ 'yes' if (sshd_allow_tcp_forwarding | bool) else 'no' }} + {%- endif -%} + {%- else -%} + {%- if (sshd_allow_tcp_forwarding in ('yes', 'no')) -%} + AllowTcpForwarding {{ sshd_allow_tcp_forwarding }} + {%- else -%} + AllowTcpForwarding {{ 'yes' if (sshd_allow_tcp_forwarding | bool) else 'no' }} + {%- endif -%} + {%- endif -%} + - "{{ 'AuthenticationMethods ' + sshd_authentication_methods if (ssh_installed_version is version('6.2', '>='))}}" + - Banner {{ sshd_banner if sshd_banner else 'none' }} + - "{{ 'CASignatureAlgorithms ' + sshd_ca_signature_algorithms | join(',') if (not crypto_policies_config or not set_crypto_policy)}}" + - "{{ 'Ciphers ' + sshd_ciphers | join(',') if (not crypto_policies_config or not set_crypto_policy)}}" + - ClientAliveCountMax {{ sshd_client_alive_count_max | int }} + - ClientAliveInterval {{ sshd_client_alive_interval | int }} + - Compression {{ 'yes' if (sshd_compression | bool) else 'no' }} + - "{{ 'DebianBanner ' + ('yes' if (sshd_debian_banner | bool) else 'no') if (ansible_facts.os_family == 'Debian')}}" + - GSSAPIAuthentication {{ 'yes' if (sshd_gssapi_authentication | bool) else 'no' }} + - GSSAPICleanupCredentials yes + - HostbasedAuthentication {{ 'yes' if (sshd_hostbased_authentication | bool) else 'no' }} + - "{{ 'HostKeyAlgorithms ' + sshd_host_key_algorithms | join(',') if (not crypto_policies_config or not set_crypto_policy)}}" + - IgnoreRhosts {{ 'yes' if (sshd_ignore_rhosts | bool) else 'no' }} + - IgnoreUserKnownHosts {{ 'yes' if (sshd_ignore_user_known_hosts | bool) else 'no' }} + - >- + {%- if ssh_installed_version is version('8.7', '>=') -%} + KbdInteractiveAuthentication {{ 'yes' if (sshd_kbd_interactive_authentication | bool) else 'no' }} + {%- else -%} + ChallengeResponseAuthentication {{ 'yes' if (sshd_kbd_interactive_authentication | bool) else 'no' }} + {%- endif -%} + - KerberosAuthentication {{ 'yes' if (sshd_kerberos_authentication | bool) else 'no' }} + - "{{ 'KexAlgorithms ' + sshd_kex_algorithms | join(',') if (not crypto_policies_config or not set_crypto_policy)}}" + - LogLevel {{ sshd_log_level }} + - LoginGraceTime {{ sshd_login_grace_time | int }} + - "{{ 'MACs ' + sshd_macs | join(',') if (not crypto_policies_config or not set_crypto_policy)}}" + - MaxAuthTries {{ sshd_max_auth_tries | int }} + - MaxSessions {{ sshd_max_sessions | int }} + - MaxStartups {{ sshd_max_startups }} + - PasswordAuthentication {{ 'yes' if (sshd_password_authentication | bool) else 'no' }} + - PermitEmptyPasswords {{ 'yes' if (sshd_permit_empty_passwords | bool) else 'no' }} + - PermitRootLogin {{ 'yes' if (sshd_permit_root_login | bool) else 'no' }} + - PermitTunnel {{ 'yes' if (sshd_permit_tunnel | bool) else 'no' }} + - PermitUserEnvironment {{ 'yes' if (sshd_permit_user_environment | bool) else 'no' }} + - PrintLastLog {{ 'yes' if (sshd_print_last_log | bool) else 'no' }} + - PrintMotd {{ 'yes' if (sshd_print_motd | bool) else 'no' }} + - "{{ 'Protocol 2' if (ssh_installed_version is version('7.6', '<'))}}" + - PubkeyAuthentication yes + - RekeyLimit {{ sshd_rekey_limit }} + - "{{ 'RequiredRSASize ' + (sshd_required_rsa_size | string) if (ssh_installed_version is version('9.1', '>'))}}" + - StrictModes {{ 'yes' if (sshd_strict_modes | bool) else 'no' }} + - "{{ 'Subsystem sftp ' + sshd_sftp_subsystem if (sshd_sftp_enabled | bool)}}" + - SyslogFacility {{ sshd_syslog_facility }} + - TCPKeepAlive {{ 'yes' if (sshd_tcp_keep_alive | bool) else 'no' }} + - UseDNS {{ 'yes' if (sshd_use_dns | bool) else 'no' }} + - "{{ 'UseLogin no' if (ssh_installed_version is version('7.4', '<'))}}" + - UsePAM {{ 'yes' if (sshd_use_pam | bool) else 'no' }} + - >- + {%- if ssh_installed_version is version('7.5', '<') -%} + {%- if sshd_use_privilege_separation in ('yes', 'no', 'sandbox') -%} + UsePrivilegeSeparation {{ sshd_use_privilege_separation }} + {%- else -%} + UsePrivilegeSeparation {{ 'yes' if (sshd_use_privilege_separation | bool) else 'no' }} + {%- endif -%} + {%- endif -%} + - X11Forwarding {{ 'yes' if (sshd_x11_forwarding | bool) else 'no' }} + - X11UseLocalhost yes + + - name: Set repeating parameters and clear empty values in sshd_config_parameters + vars: + _hostkey: "{{ ['HostKey '] | product(sshd_host_keys_files) | map('join') | list }}" + _listenaddress: "{{ ['ListenAddress '] | product(sshd_listen) | map('join') | list }}" + _port: "{{ ['Port '] | product(sshd_ports) | map('join') | list }}" + ansible.builtin.set_fact: + sshd_config_parameters: + "{{ (sshd_config_parameters + _hostkey + _listenaddress + _port) | select() }}" + - name: Verify sshd configuration become: true ansible.builtin.lineinfile: dest: /etc/ssh/sshd_config - line: "{{ item | replace(', ', ',') }}" + line: "{{ item }}" state: present check_mode: true register: sshd_config failed_when: sshd_config is changed - with_items: - - AllowGroups vagrant sudo - - AllowAgentForwarding {{ sshd_allow_agent_forwarding }} - - AllowTcpForwarding {{ sshd_allow_tcp_forwarding }} - - AuthenticationMethods {{ sshd_authentication_methods }} - - Banner {{ sshd_banner }} - - ChallengeResponseAuthentication {{ sshd_challenge_response_authentication }} - - ClientAliveCountMax {{ sshd_client_alive_count_max | int }} - - ClientAliveInterval {{ sshd_client_alive_interval | int }} - - Compression {{ sshd_compression }} - - GSSAPIAuthentication {{ sshd_gssapi_authentication }} - - HostbasedAuthentication {{ sshd_hostbased_authentication }} - - IgnoreUserKnownHosts {{ sshd_ignore_user_known_hosts }} - - KerberosAuthentication {{ sshd_kerberos_authentication }} - - LogLevel {{ sshd_log_level }} - - LoginGraceTime {{ sshd_login_grace_time | int }} - - MaxAuthTries {{ sshd_max_auth_tries | int }} - - MaxSessions {{ sshd_max_sessions | int }} - - MaxStartups {{ sshd_max_startups }} - - PasswordAuthentication {{ sshd_password_authentication }} - - PermitEmptyPasswords {{ sshd_permit_empty_passwords }} - - PermitRootLogin {{ sshd_permit_root_login }} - - PermitUserEnvironment {{ sshd_permit_user_environment }} - - Port {{ sshd_port | int }} - - PrintLastLog {{ sshd_print_last_log }} - - PrintMotd {{ sshd_print_motd }} - - RekeyLimit {{ sshd_rekey_limit }} - - StrictModes {{ sshd_strict_modes }} - - TCPKeepAlive {{ sshd_tcp_keep_alive }} - - UseDNS {{ sshd_use_dns }} - - UsePAM {{ sshd_use_pam }} - - X11Forwarding {{ sshd_x11_forwarding }} + loop: "{{ sshd_config_parameters }}" when: not sshd_config_directory.stat.exists - name: Verify sshd config.d configuration become: true ansible.builtin.lineinfile: dest: /etc/ssh/sshd_config.d/01-hardening.conf - line: "{{ item | replace(', ', ',') }}" + line: "{{ item }}" state: present check_mode: true register: sshd_config failed_when: sshd_config is changed - with_items: - - AllowGroups vagrant sudo - - AllowAgentForwarding {{ sshd_allow_agent_forwarding }} - - AllowTcpForwarding {{ sshd_allow_tcp_forwarding }} - - AuthenticationMethods {{ sshd_authentication_methods }} - - Banner {{ sshd_banner }} - - ChallengeResponseAuthentication {{ sshd_challenge_response_authentication }} - - ClientAliveCountMax {{ sshd_client_alive_count_max | int }} - - ClientAliveInterval {{ sshd_client_alive_interval | int }} - - Compression {{ sshd_compression }} - - GSSAPIAuthentication {{ sshd_gssapi_authentication }} - - HostbasedAuthentication {{ sshd_hostbased_authentication }} - - IgnoreUserKnownHosts {{ sshd_ignore_user_known_hosts }} - - KerberosAuthentication {{ sshd_kerberos_authentication }} - - LogLevel {{ sshd_log_level }} - - LoginGraceTime {{ sshd_login_grace_time | int }} - - MaxAuthTries {{ sshd_max_auth_tries | int }} - - MaxSessions {{ sshd_max_sessions | int }} - - MaxStartups {{ sshd_max_startups }} - - PasswordAuthentication {{ sshd_password_authentication }} - - PermitEmptyPasswords {{ sshd_permit_empty_passwords }} - - PermitRootLogin {{ sshd_permit_root_login }} - - PermitUserEnvironment {{ sshd_permit_user_environment }} - - Port {{ sshd_port | int }} - - PrintLastLog {{ sshd_print_last_log }} - - PrintMotd {{ sshd_print_motd }} - - RekeyLimit {{ sshd_rekey_limit }} - - StrictModes {{ sshd_strict_modes }} - - Subsystem {{ sshd_subsystem }} - - TCPKeepAlive {{ sshd_tcp_keep_alive }} - - UseDNS {{ sshd_use_dns }} - - UsePAM {{ sshd_use_pam }} - - X11Forwarding {{ sshd_x11_forwarding }} + loop: "{{ sshd_config_parameters }}" when: sshd_config_directory.stat.exists + - name: Set parameters for runtime check + ansible.builtin.set_fact: + sshd_config_parameters_runtime: >- + {{ + sshd_config_parameters_runtime | default([]) + + [(item.split(' ')[0] | lower) + ' ' + (item.split(' ')[1:] | join(' '))] + }} + loop: "{{ sshd_config_parameters }}" + + - name: Set multiple parameters in runtime checking + ansible.builtin.set_fact: + sshd_config_parameters_runtime: >- + {{ + ( sshd_config_parameters_runtime | reject('regex', '^' + item + ' ') | list ) + + ( [item + ' '] | + product( sshd_config_parameters_runtime | + select('match', '^' + item + ' ') | + map('split', ' ', 1) | + map('last') | + map('split', ' ') | + unique | + flatten ) | + map('join') | list ) + }} + loop: + - allowgroups + - acceptenv + + - name: Ignore parameters for runtime check + ansible.builtin.set_fact: + sshd_config_parameters_runtime: "{{ (sshd_config_parameters_runtime | reject('regex', '^' + item + ' ') | list) }}" + loop: + # debianbanner: not show in runtime. + - debianbanner + # listenaddress: runtime returns values addresses and ports(Ex: 0.0.0.0:22), ports is optional and can have multiple values. + - listenaddress + # rekeylimit: runtime returns values only in bytes and seconds, having to treat these values would be a lot of work. + - rekeylimit + - name: Verify sshd runtime configuration become: true environment: PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ansible.builtin.command: sshd -T + check_mode: false register: sshd_config changed_when: false failed_when: item not in sshd_config.stdout_lines - with_items: - - allowgroups sudo - - allowgroups vagrant - - allowagentforwarding {{ sshd_allow_agent_forwarding }} - - allowtcpforwarding {{ sshd_allow_tcp_forwarding }} - - authenticationmethods {{ sshd_authentication_methods }} - - banner {{ sshd_banner }} - - clientalivecountmax {{ sshd_client_alive_count_max | int }} - - clientaliveinterval {{ sshd_client_alive_interval | int }} - - compression {{ sshd_compression }} - - logingracetime {{ sshd_login_grace_time | int }} - - maxauthtries {{ sshd_max_auth_tries | int }} - - maxsessions {{ sshd_max_sessions | int }} - - maxstartups {{ sshd_max_startups }} - - permitrootlogin {{ sshd_permit_root_login }} - - port {{ sshd_port | int }} - - subsystem {{ sshd_subsystem }} - - usedns {{ sshd_use_dns }} - - usepam {{ sshd_use_pam }} - - x11forwarding {{ sshd_x11_forwarding }} - - - name: Verify sshd runtime ciphers and algorithms - become: true - environment: - PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin - ansible.builtin.command: sshd -T - register: sshd_algo_runtime - changed_when: false - failed_when: item not in sshd_algo_runtime.stdout_lines - when: not crypto_policies_config or not set_crypto_policy - with_items: - - casignaturealgorithms {{ sshd_ca_signature_algorithms | replace(', ', ',') }} - - ciphers {{ sshd_ciphers | replace(', ', ',') }} - - hostkeyalgorithms {{ sshd_host_key_algorithms | replace(', ', ',') }} - - kexalgorithms {{ sshd_kex_algorithms | replace(', ', ',') }} - - macs {{ sshd_macs | replace(', ', ',') }} - - - name: Verify sshd RequiredRSASize - become: true - ansible.builtin.lineinfile: - dest: /etc/ssh/sshd_config.d/01-hardening.conf - line: RequiredRSASize {{ sshd_required_rsa_size }} - state: present - check_mode: true - register: sshd_config_rsasize - failed_when: sshd_config_rsasize is changed - when: sshd_config_directory.stat.exists and ssh_installed_version is version('9.1', '>') + loop: "{{ sshd_config_parameters_runtime }}" - name: Verify ssh client configuration become: true @@ -377,8 +396,8 @@ check_mode: true register: ssh_config failed_when: ssh_config is changed - with_items: - - GSSAPIAuthentication {{ sshd_gssapi_authentication }} + loop: + - GSSAPIAuthentication {{ 'yes' if (sshd_gssapi_authentication | bool) else 'no' }} - HashKnownHosts yes - RekeyLimit {{ sshd_rekey_limit }} diff --git a/molecule/redhat/molecule.yml b/molecule/redhat/molecule.yml index 2eaa96e6..5d29a817 100644 --- a/molecule/redhat/molecule.yml +++ b/molecule/redhat/molecule.yml @@ -21,8 +21,11 @@ provisioner: inventory: host_vars: redhat: - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo platforms: - name: redhat box: generic/rhel8 diff --git a/molecule/single/molecule.yml b/molecule/single/molecule.yml index ca9b2990..9eb7f8cf 100644 --- a/molecule/single/molecule.yml +++ b/molecule/single/molecule.yml @@ -18,8 +18,11 @@ provisioner: inventory: host_vars: focal: - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo suid_sgid_permissions: false umask_value: "027" platforms: diff --git a/molecule/ubuntu/molecule.yml b/molecule/ubuntu/molecule.yml index 3b53ee75..46a915df 100644 --- a/molecule/ubuntu/molecule.yml +++ b/molecule/ubuntu/molecule.yml @@ -18,17 +18,29 @@ provisioner: inventory: host_vars: focal: - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo jammy: - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo lunar: - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo mantic: - sshd_admin_net: "0.0.0.0/0" - sshd_allow_groups: vagrant sudo + sshd_admin_net: + - 0.0.0.0/0 + sshd_allow_groups: + - vagrant + - sudo platforms: - name: focal box: bento/ubuntu-20.04 diff --git a/tasks/sshconfig.yml b/tasks/sshconfig.yml index 16b58190..a0f5bc13 100644 --- a/tasks/sshconfig.yml +++ b/tasks/sshconfig.yml @@ -4,6 +4,7 @@ cmd: ssh -V changed_when: false failed_when: false + check_mode: false register: ssh_version tags: - sshd @@ -43,6 +44,7 @@ register: grep_include changed_when: false failed_when: false + check_mode: false tags: - sshd - sshd_config @@ -57,6 +59,28 @@ - sshd_config - M1041 +- name: Clear pre-existing custom configurations in /etc/ssh/sshd_config.d + when: + - sshd_config_d_force_clear | bool + - sshd_config_d.stat.exists + tags: + - sshd + - sshd_config + block: + - name: Search pre-existing custom configurations in /etc/ssh/sshd_config.d + become: true + ansible.builtin.find: + path: /etc/ssh/sshd_config.d + patterns: '*' + register: sshd_config_d_content + + - name: Clear pre-existing custom configurations in /etc/ssh/sshd_config.d + become: true + ansible.builtin.file: + path: "{{ item.path }}" + state: absent + loop: "{{ sshd_config_d_content.files }}" + - name: Ensure /etc/ssh/sshd_config permissions become: true ansible.builtin.file: @@ -68,6 +92,79 @@ - sshd - sshd_config +- name: Set default for sshd_host_keys_files if not supplied + become: true + when: not sshd_host_keys_files + tags: + - sshd + block: + - name: Replace default 2048 bits RSA keypair + community.crypto.openssh_keypair: + state: present + type: rsa + size: "{{ sshd_required_rsa_size }}" + path: "/etc/ssh/ssh_host_rsa_key" + force: false + regenerate: partial_idempotence + + - name: Set hostkeys according to openssh-version if openssh >= 5.3 + ansible.builtin.set_fact: + sshd_host_keys_files: + - "/etc/ssh/ssh_host_rsa_key" + when: + - ssh_installed_version is version('5.3', '>=') + - ssh_installed_version is version('6.0', '<') + + - name: Set hostkeys according to openssh-version if openssh >= 6.0 + ansible.builtin.set_fact: + sshd_host_keys_files: + - "/etc/ssh/ssh_host_rsa_key" + - "/etc/ssh/ssh_host_ecdsa_key" + when: + - ssh_installed_version is version('6.0', '>=') + - ssh_installed_version is version('6.3', '<') + + - name: Set hostkeys according to openssh-version if openssh >= 6.3 + ansible.builtin.set_fact: + sshd_host_keys_files: + - "/etc/ssh/ssh_host_rsa_key" + - "/etc/ssh/ssh_host_ecdsa_key" + - "/etc/ssh/ssh_host_ed25519_key" + when: ssh_installed_version is version('6.3', '>=') + + - name: Change host private key ownership, group and permissions + ansible.builtin.file: + path: "{{ item }}" + owner: "{{ sshd_host_keys_owner }}" + group: "{{ sshd_host_keys_group }}" + mode: "{{ sshd_host_keys_mode }}" + loop: "{{ sshd_host_keys_files }}" + +- name: Disable PAM dynamic MOTD + become: true + community.general.pamd: + name: sshd + type: session + control: optional + module_path: pam_motd.so + state: absent + backup: true + when: + - sshd_use_pam | bool + - not (sshd_print_pam_motd | bool) + tags: + - sshd + +- name: Check variable sshd_config_force_replace + ansible.builtin.set_fact: + sshd_config_force_replace: true + when: >- + sshd_match_users | length > 0 or + sshd_match_groups | length > 0 or + sshd_match_addresses | length > 0 or + sshd_match_local_ports | length > 0 or + (sshd_sftp_only_group is defined and sshd_sftp_only_group | length != 0) + - name: Configure sshd become: true ansible.builtin.template: @@ -77,8 +174,8 @@ mode: "0600" owner: root group: root - validate: sshd -t -f %s - when: not sshd_config_d.stat.exists or grep_include.rc != 0 + validate: "/usr/sbin/sshd -T -C user=root -C host=localhost -C addr=localhost -C lport=22 -f %s" + when: sshd_config_force_replace | bool or not sshd_config_d.stat.exists or grep_include.rc != 0 notify: - Restart sshd service - Restart ssh service @@ -131,8 +228,8 @@ mode: "0600" owner: root group: root - validate: sshd -t -f %s - when: sshd_config_d.stat.exists and grep_include.rc == 0 + validate: "/usr/sbin/sshd -T -C user=root -C host=localhost -C addr=localhost -C lport=22 -f %s" + when: not ( sshd_config_force_replace | bool ) and sshd_config_d.stat.exists and grep_include.rc == 0 notify: - Restart sshd service - Restart ssh service diff --git a/tasks/ufw.yml b/tasks/ufw.yml index bfe41f46..4effa9ec 100644 --- a/tasks/ufw.yml +++ b/tasks/ufw.yml @@ -118,12 +118,11 @@ become: true community.general.ufw: rule: limit - src: "{{ item }}" - port: "{{ sshd_port | int }}" + src: "{{ item.0 }}" + port: "{{ item.1 | int }}" proto: tcp comment: ansible managed - with_items: - - "{{ sshd_admin_net }}" + loop: "{{ sshd_admin_net | product(sshd_ports) | list }}" tags: - ufw - M1037 @@ -135,8 +134,7 @@ port: "{{ item | int }}" direction: out comment: ansible managed - with_items: - - "{{ ufw_outgoing_traffic }}" + loop: "{{ ufw_outgoing_traffic }}" tags: - ufw - CIS-UBUNTU2004-3.5.1.5 @@ -198,8 +196,7 @@ changed_when: ufw_delete.rc != 0 failed_when: ufw_delete.rc != 0 when: ufw_not_managed.stdout_lines | length > 0 and not ansible_os_family == "RedHat" - with_items: - - "{{ ufw_not_managed.stdout_lines }}" + loop: "{{ ufw_not_managed.stdout_lines }}" tags: - ufw - D3-OTF diff --git a/templates/etc/audit/rules.d/hardening.rules.j2 b/templates/etc/audit/rules.d/hardening.rules.j2 index 33e70d15..0dbeae85 100644 --- a/templates/etc/audit/rules.d/hardening.rules.j2 +++ b/templates/etc/audit/rules.d/hardening.rules.j2 @@ -11,7 +11,7 @@ -c # Failure Mode --f {{ auditd_mode|int }} +-f {{ auditd_mode | int }} # File and directory access -a always,exit -F arch=b32 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=-1 -k access diff --git a/templates/etc/ssh/ssh_config.j2 b/templates/etc/ssh/ssh_config.j2 index aacc7c85..b2730095 100644 --- a/templates/etc/ssh/ssh_config.j2 +++ b/templates/etc/ssh/ssh_config.j2 @@ -1,17 +1,20 @@ +#jinja2: trim_blocks: "true", lstrip_blocks: "true" # {{ ansible_managed }} # Generated by Ansible role {{ ansible_role_name }} +# OpenSSH SSH client configuration files +# See ssh_config(5) for more information + {% if ssh_config_d.stat.exists and ssh_config_d.stat.isdir %} Include /etc/ssh/ssh_config.d/*.conf -{% endif %} - +{%+ endif +%} Host * {% if not crypto_policies_config or not set_crypto_policy %} - Ciphers {{ sshd_ciphers | replace(', ', ',') }} - HostKeyAlgorithms {{ sshd_host_key_algorithms | replace(', ', ',') }} - KexAlgorithms {{ sshd_kex_algorithms | replace(', ', ',') }} - MACs {{ sshd_macs | replace(', ', ',') }} + {{ 'Ciphers ' ~ sshd_ciphers | join(',') if sshd_ciphers }} + {{ 'HostKeyAlgorithms ' ~ sshd_host_key_algorithms | join(',') if sshd_host_key_algorithms }} + {{ 'KexAlgorithms ' ~ sshd_kex_algorithms | join(',') if sshd_kex_algorithms }} + {{ 'MACs ' ~ sshd_macs | join(',') if sshd_macs }} {% endif %} - GSSAPIAuthentication {{ sshd_gssapi_authentication }} + GSSAPIAuthentication {{ 'yes' if (sshd_gssapi_authentication | bool) else 'no' }} HashKnownHosts yes RekeyLimit {{ sshd_rekey_limit }} diff --git a/templates/etc/ssh/sshd_config.j2 b/templates/etc/ssh/sshd_config.j2 index e1adf5bf..7ee7e9fc 100644 --- a/templates/etc/ssh/sshd_config.j2 +++ b/templates/etc/ssh/sshd_config.j2 @@ -1,52 +1,197 @@ +#jinja2: trim_blocks: "true", lstrip_blocks: "true" # {{ ansible_managed }} # Generated by Ansible role {{ ansible_role_name }} +# OpenSSH SSH daemon configuration file +# See sshd_config(5) for more information + +####################################################### +# Basic configuration +####################################################### + +{% for port in sshd_ports %} +Port {{ port }} +{% endfor %} +AddressFamily {{ 'inet' if (disable_ipv6 | bool) else 'any' }} +{% for address in sshd_listen %} +ListenAddress {{ address }} +{% endfor %} +{% for key in sshd_host_keys_files %} +HostKey {{ key }} +{% endfor %} + +####################################################### +# Security configuration +####################################################### + +{% if ssh_installed_version is version('7.6', '<') %} +Protocol 2 +{% endif %} +StrictModes {{ 'yes' if (sshd_strict_modes | bool) else 'no' }} +SyslogFacility {{ sshd_syslog_facility }} +LogLevel {{ sshd_log_level }} + +####################################################### +# Cryptography +####################################################### + {% if not crypto_policies_config or not set_crypto_policy %} +{{ 'CASignatureAlgorithms ' ~ sshd_ca_signature_algorithms | join(',') if sshd_ca_signature_algorithms }} +{{ 'Ciphers ' ~ sshd_ciphers | join(',') if sshd_ciphers }} +{{ 'HostKeyAlgorithms ' ~ sshd_host_key_algorithms | join(',') if sshd_host_key_algorithms }} +{{ 'KexAlgorithms ' ~ sshd_kex_algorithms | join(',') if sshd_kex_algorithms }} +{{ 'MACs ' ~ sshd_macs | join(',') if sshd_macs }} +{% endif %} +RekeyLimit {{ sshd_rekey_limit }} +{% if ssh_installed_version is version('9.1', '>') and (not crypto_policies_config or not set_crypto_policy) %} +RequiredRSASize {{ sshd_required_rsa_size }} +{% endif %} -CASignatureAlgorithms {{ sshd_ca_signature_algorithms | replace(', ', ',') }} -Ciphers {{ sshd_ciphers | replace(', ', ',') }} -HostKeyAlgorithms {{ sshd_host_key_algorithms | replace(', ', ',') }} -KexAlgorithms {{ sshd_kex_algorithms | replace(', ', ',') }} -MACs {{ sshd_macs | replace(', ', ',') }} +####################################################### +# Authentication +####################################################### +PermitRootLogin {{ 'yes' if (sshd_permit_root_login | bool) else 'no' }} +{% if ssh_installed_version is version('7.4', '<') %} +UseLogin no {% endif %} -AcceptEnv {{ sshd_accept_env }} -AllowAgentForwarding {{ sshd_allow_agent_forwarding }} -AllowGroups {{ sshd_allow_groups }} -{% if sshd_allow_users is defined and sshd_allow_users %} -AllowUsers {{ sshd_allow_users }} +{% if ssh_installed_version is version('7.5', '<') %} +UsePrivilegeSeparation {{ sshd_use_privilege_separation if (sshd_use_privilege_separation in ('yes', 'no', 'sandbox')) else ('yes' if (sshd_use_privilege_separation | bool) else 'no') }} {% endif %} -AllowTcpForwarding {{ sshd_allow_tcp_forwarding }} -AuthenticationMethods {{ sshd_authentication_methods }} -Banner {{ sshd_banner }} -ChallengeResponseAuthentication {{ sshd_challenge_response_authentication }} -ClientAliveCountMax {{ sshd_client_alive_count_max | int }} -ClientAliveInterval {{ sshd_client_alive_interval | int }} -Compression {{ sshd_compression }} -GSSAPIAuthentication {{ sshd_gssapi_authentication }} -HostbasedAuthentication {{ sshd_hostbased_authentication }} -IgnoreRhosts {{ sshd_ignore_rhosts }} -IgnoreUserKnownHosts {{ sshd_ignore_user_known_hosts }} -KerberosAuthentication {{ sshd_kerberos_authentication }} -LogLevel {{ sshd_log_level }} LoginGraceTime {{ sshd_login_grace_time | int }} MaxAuthTries {{ sshd_max_auth_tries | int }} MaxSessions {{ sshd_max_sessions | int }} MaxStartups {{ sshd_max_startups }} -PasswordAuthentication {{ sshd_password_authentication }} -PermitEmptyPasswords {{ sshd_permit_empty_passwords }} -PermitRootLogin {{ sshd_permit_root_login }} -PermitUserEnvironment {{ sshd_permit_user_environment }} -Port {{ sshd_port|int }} -PrintLastLog {{ sshd_print_last_log }} -PrintMotd {{ sshd_print_motd }} -RekeyLimit {{ sshd_rekey_limit }} -{% if ssh_installed_version is version('9.1', '>') and (not crypto_policies_config or not set_crypto_policy) %} -RequiredRSASize {{ sshd_required_rsa_size }} +PubkeyAuthentication yes +IgnoreRhosts {{ 'yes' if (sshd_ignore_rhosts | bool) else 'no' }} +IgnoreUserKnownHosts {{ 'yes' if (sshd_ignore_user_known_hosts | bool) else 'no' }} +HostbasedAuthentication {{ 'yes' if (sshd_hostbased_authentication | bool) else 'no' }} +UsePAM {{ 'yes' if (sshd_use_pam | bool) else 'no' }} +{% if ssh_installed_version is version('6.2', '>=') %} +AuthenticationMethods {{ sshd_authentication_methods }} +{% endif %} +PasswordAuthentication {{ 'yes' if (sshd_password_authentication | bool) else 'no' }} +PermitEmptyPasswords {{ 'yes' if (sshd_permit_empty_passwords | bool) else 'no' }} +{% if ssh_installed_version is version('8.7', '>=') %} +KbdInteractiveAuthentication {{ 'yes' if (sshd_kbd_interactive_authentication | bool) else 'no' }} +{% else %} +ChallengeResponseAuthentication {{ 'yes' if (sshd_kbd_interactive_authentication | bool) else 'no' }} +{% endif %} +KerberosAuthentication {{ 'yes' if (sshd_kerberos_authentication | bool) else 'no' }} +GSSAPIAuthentication {{ 'yes' if (sshd_gssapi_authentication | bool) else 'no' }} +GSSAPICleanupCredentials yes +{% if sshd_deny_groups %} +{{ 'DenyGroups ' ~ sshd_deny_groups | join(' ') }} +{% endif %} +{% if sshd_allow_groups %} +{{ 'AllowGroups ' ~ sshd_allow_groups | join(' ') }} +{% endif %} +{% if sshd_deny_users %} +{{ 'DenyUsers ' ~ sshd_deny_users | join(' ') }} +{% endif %} +{% if sshd_allow_users %} +{{ 'AllowUsers ' ~ sshd_allow_users | join(' ') }} +{% endif %} + +####################################################### +# Network +####################################################### + +TCPKeepAlive {{ 'yes' if (sshd_tcp_keep_alive | bool) else 'no' }} +ClientAliveCountMax {{ sshd_client_alive_count_max | int }} +ClientAliveInterval {{ sshd_client_alive_interval | int }} +PermitTunnel {{ 'yes' if (sshd_permit_tunnel | bool) else 'no' }} +{% if ssh_installed_version is version('6.2', '>=') %} +AllowTcpForwarding {{ sshd_allow_tcp_forwarding if (sshd_allow_tcp_forwarding in ('yes', 'no', 'local', 'all', 'remote')) else ('yes' if (sshd_allow_tcp_forwarding | bool) else 'no') }} +{% else %} +AllowTcpForwarding {{ sshd_allow_tcp_forwarding if (sshd_allow_tcp_forwarding in ('yes', 'no')) else ('yes' if (sshd_allow_tcp_forwarding | bool) else 'no') }} +{% endif %} +AllowAgentForwarding {{ 'yes' if (sshd_allow_agent_forwarding | bool) else 'no' }} +X11Forwarding {{ 'yes' if (sshd_x11_forwarding | bool) else 'no' }} +X11UseLocalhost yes + +####################################################### +# User environment configuration +####################################################### + +PermitUserEnvironment {{ 'yes' if (sshd_permit_user_environment | bool) else 'no' }} +{% if sshd_accept_env %} +AcceptEnv {{ sshd_accept_env }} +{% endif %} + +####################################################### +# Misc. configuration +####################################################### + +Compression {{ 'yes' if (sshd_compression | bool) else 'no' }} +UseDNS {{ 'yes' if (sshd_use_dns | bool) else 'no' }} +PrintMotd {{ 'yes' if (sshd_print_motd | bool) else 'no' }} +PrintLastLog {{ 'yes' if (sshd_print_last_log | bool) else 'no' }} +Banner {{ sshd_banner if sshd_banner else 'none' }} +{% if ansible_facts.os_family == 'Debian' %} +DebianBanner {{ 'yes' if (sshd_debian_banner | bool) else 'no' }} +{% endif %} + +{%+ if sshd_sftp_enabled +%} +####################################################### +# SFTP matching configuration +####################################################### + +Subsystem sftp {{ sshd_sftp_subsystem }} +{%+ if sshd_sftp_only_group %} +Match Group {{ sshd_sftp_only_group }} + ForceCommand {{ sshd_sftp_subsystem }} +{% if sshd_sftp_chroot %} + ChrootDirectory {{ sshd_sftp_chroot_dir }} +{% endif %} + AllowTcpForwarding no + AllowAgentForwarding no + PasswordAuthentication {{ 'yes' if (sshd_password_authentication | bool) else 'no' }} + PermitRootLogin no + X11Forwarding no {% endif %} -StrictModes {{ sshd_strict_modes }} -Subsystem {{ sshd_subsystem }} -TCPKeepAlive {{ sshd_tcp_keep_alive }} -UseDNS {{ sshd_use_dns }} -UsePAM {{ sshd_use_pam }} -X11Forwarding {{ sshd_x11_forwarding }} +{%+ endif +%} +{% for item in sshd_match_addresses %} +{%+ if loop.first +%} +####################################################### +# Address matching configuration +####################################################### +{%+ endif +%} +Match Address {{ item.address }} + {% for rule in item.rules %} + {{ rule | indent(4) }} + {% endfor %} +{% endfor %} +{% for item in sshd_match_groups %} +{%+ if loop.first +%} +####################################################### +# Group matching configuration +####################################################### +{%+ endif +%} +Match Group {{ item.group }} + {% for rule in item.rules %} + {{ rule | indent(4) }} + {% endfor %} +{% endfor %} +{% for item in sshd_match_users %} +{%+ if loop.first +%} +####################################################### +# User matching configuration +####################################################### +{%+ endif +%} +Match User {{ item.user }} + {% for rule in item.rules %} + {{ rule | indent(4) }} + {% endfor %} +{% endfor %} +{% for item in sshd_match_local_ports %} +{%+ if loop.first +%} +####################################################### +# LocalPort matching configuration +####################################################### +{%+ endif +%} +Match LocalPort {{ item.port }} + {% for rule in item.rules %} + {{ rule | indent(4) }} + {% endfor %} +{% endfor %} diff --git a/templates/etc/systemd/resolved.conf.j2 b/templates/etc/systemd/resolved.conf.j2 index ce66bc48..26451174 100644 --- a/templates/etc/systemd/resolved.conf.j2 +++ b/templates/etc/systemd/resolved.conf.j2 @@ -5,6 +5,6 @@ DNS={{ dns }} FallbackDNS={{ fallback_dns }} DNSSEC={{ dnssec }} -{% if ansible_local.systemd.version|int >= 239 %} +{% if ansible_local.systemd.version | int >= 239 %} DNSOverTLS={{ dns_over_tls }} {% endif %} diff --git a/templates/etc/systemd/timesyncd.conf.j2 b/templates/etc/systemd/timesyncd.conf.j2 index 23dd48d1..8f082621 100644 --- a/templates/etc/systemd/timesyncd.conf.j2 +++ b/templates/etc/systemd/timesyncd.conf.j2 @@ -4,6 +4,6 @@ [Time] NTP={{ ntp }} FallbackNTP={{ fallback_ntp }} -{% if ansible_local.systemd.version|int >= 236 %} +{% if ansible_local.systemd.version | int >= 236 %} RootDistanceMaxSec=1 {% endif %}