├── .prettierignore ├── roles ├── nginx_hardening │ ├── vars │ │ └── main.yml │ ├── handlers │ │ └── main.yml │ ├── meta │ │ └── main.yml │ ├── templates │ │ └── hardening.conf.j2 │ ├── defaults │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ ├── CHANGELOG.md │ └── README.md ├── mysql_hardening │ ├── vars │ │ ├── main.yml │ │ ├── Fedora.yml │ │ ├── Oracle Linux.yml │ │ ├── RedHat_8.yml │ │ ├── Rocky_8.yml │ │ ├── RedHat_7.yml │ │ ├── Debian.yml │ │ ├── Ubuntu_16.yml │ │ ├── Ubuntu_18.yml │ │ └── Ubuntu_20.yml │ ├── handlers │ │ └── main.yml │ ├── templates │ │ ├── my.cnf.j2 │ │ └── hardening.cnf.j2 │ ├── meta │ │ └── main.yml │ ├── tasks │ │ ├── configure.yml │ │ ├── main.yml │ │ └── mysql_secure_installation.yml │ ├── defaults │ │ └── main.yml │ ├── README.md │ └── CHANGELOG.md ├── os_hardening │ ├── tasks │ │ ├── main.yml │ │ ├── selinux.yml │ │ ├── securetty.yml │ │ ├── login_defs.yml │ │ ├── apt.yml │ │ ├── auditd.yml │ │ ├── profile.yml │ │ ├── rhosts.yml │ │ ├── cron.yml │ │ ├── modprobe.yml │ │ ├── pam_rhel.yml │ │ ├── limits.yml │ │ ├── pam.yml │ │ ├── suid_sgid.yml │ │ ├── yum.yml │ │ ├── minimize_access.yml │ │ ├── user_accounts.yml │ │ ├── sysctl.yml │ │ ├── hardening.yml │ │ └── pam_debian.yml │ ├── templates │ │ ├── etc │ │ │ ├── securetty.j2 │ │ │ ├── modprobe.d │ │ │ │ └── modprobe.j2 │ │ │ ├── profile.d │ │ │ │ └── profile.conf.j2 │ │ │ ├── audit │ │ │ │ └── auditd.conf.j2 │ │ │ ├── sysconfig │ │ │ │ └── rhel_sysconfig_init.j2 │ │ │ ├── initramfs-tools │ │ │ │ └── modules.j2 │ │ │ ├── default │ │ │ │ └── ufw.j2 │ │ │ ├── pam.d │ │ │ │ └── rhel_auth.j2 │ │ │ ├── security │ │ │ │ └── faillock.conf.j2 │ │ │ ├── libuser.conf.j2 │ │ │ └── login.defs.j2 │ │ └── usr │ │ │ └── share │ │ │ └── pam-configs │ │ │ ├── pam_faillock_authfail.j2 │ │ │ ├── pam_passwdqc.j2 │ │ │ ├── pam_faillock.j2 │ │ │ └── pam_tally2.j2 │ ├── handlers │ │ └── main.yml │ ├── vars │ │ ├── Archlinux.yml │ │ ├── Suse.yml │ │ ├── Fedora.yml │ │ ├── RedHat.yml │ │ ├── RedHat_7.yml │ │ ├── RedHat_8.yml │ │ ├── Rocky_8.yml │ │ ├── Debian.yml │ │ ├── Amazon.yml │ │ └── main.yml │ └── meta │ │ └── main.yml └── ssh_hardening │ ├── tasks │ ├── main.yml │ ├── crypto_ciphers.yml │ ├── crypto_kex.yml │ ├── crypto_macs.yml │ ├── ca_keys_and_principals.yml │ ├── crypto_hostkeys.yml │ ├── selinux.yml │ └── hardening.yml │ ├── handlers │ └── main.yml │ ├── templates │ ├── revoked_keys.j2 │ ├── trusted_user_ca_keys.j2 │ ├── authorized_principals.j2 │ └── openssh.conf.j2 │ ├── files │ ├── ssh_password │ └── sshd │ ├── vars │ ├── OpenBSD.yml │ ├── Suse.yml │ ├── FreeBSD.yml │ ├── SmartOS.yml │ ├── Debian.yml │ ├── Archlinux.yml │ ├── Fedora.yml │ ├── RedHat.yml │ ├── Rocky_8.yml │ ├── RedHat_8.yml │ └── main.yml │ ├── meta │ └── main.yml │ └── defaults │ └── main.yml ├── .gitignore ├── meta └── runtime.yml ├── molecule ├── os_hardening │ ├── requirements.yml │ ├── INSTALL.rst │ ├── molecule.yml │ ├── prepare.yml │ ├── verify_pam.yml │ ├── verify.yml │ └── converge.yml ├── ssh_hardening │ ├── requirements.yml │ ├── converge.yml │ ├── INSTALL.rst │ ├── molecule.yml │ ├── verify.yml │ └── prepare.yml ├── nginx_hardening │ ├── requirements.yml │ ├── official-nginx-role-redhat.yml │ ├── converge.yml │ ├── INSTALL.rst │ ├── official-nginx-role-debian.yml │ ├── prepare.yml │ ├── molecule.yml │ └── verify.yml └── mysql_hardening │ ├── requirements.yml │ ├── INSTALL.rst │ ├── converge.yml │ ├── molecule.yml │ ├── verify.yml │ └── prepare.yml ├── requirements.txt ├── .gitmodules ├── .github ├── workflows │ ├── enforce-labels.yml │ ├── prettier-md.yml │ ├── galaxy.yml │ ├── nginx_hardening.yml │ ├── os_hardening.yml │ ├── ssh_hardening.yml │ ├── mysql_hardening.yml │ └── release.yml ├── dependabot.yml ├── labeler.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .yamllint ├── .gitattributes ├── galaxy.yml ├── README.md └── CONTRIBUTING.md /.prettierignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | -------------------------------------------------------------------------------- /roles/nginx_hardening/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .kitchen 2 | hosts 3 | Gemfile.lock 4 | -------------------------------------------------------------------------------- /meta/runtime.yml: -------------------------------------------------------------------------------- 1 | --- 2 | requires_ansible: '>=2.9.10' 3 | -------------------------------------------------------------------------------- /molecule/os_hardening/requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | roles: 3 | - geerlingguy.git 4 | -------------------------------------------------------------------------------- /molecule/ssh_hardening/requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | roles: 3 | - geerlingguy.git 4 | -------------------------------------------------------------------------------- /roles/mysql_hardening/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | mysql_hardening_user: 'mysql' # owner of data 4 | -------------------------------------------------------------------------------- /molecule/nginx_hardening/requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | roles: 3 | - geerlingguy.git 4 | - geerlingguy.nginx 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | molecule[docker] 2 | yamllint 3 | ansible 4 | ansible-lint 5 | docker 6 | flake8 7 | jmespath 8 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: hardening.yml 3 | when: os_hardening_enabled | bool 4 | -------------------------------------------------------------------------------- /roles/ssh_hardening/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: hardening.yml 3 | when: ssh_hardening_enabled | bool 4 | -------------------------------------------------------------------------------- /roles/nginx_hardening/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart nginx 3 | service: 4 | name: "nginx" 5 | state: restarted 6 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/selinux.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Configure selinux | selinux-01 3 | selinux: 4 | policy: "{{ os_selinux_policy }}" 5 | state: "{{ os_selinux_state }}" 6 | -------------------------------------------------------------------------------- /roles/mysql_hardening/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Restart mysql 4 | service: 5 | name: '{{ mysql_daemon }}' 6 | state: restarted 7 | when: mysql_hardening_restart_mysql | bool 8 | -------------------------------------------------------------------------------- /roles/ssh_hardening/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart sshd 3 | service: 4 | name: '{{ sshd_service_name }}' 5 | state: restarted 6 | when: ssh_server_enabled | bool 7 | become: true 8 | -------------------------------------------------------------------------------- /roles/ssh_hardening/templates/revoked_keys.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | {% for key in ssh_server_revoked_keys %} 5 | {{ key }} 6 | {% endfor %} 7 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/securetty.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create securetty 3 | template: 4 | src: 'etc/securetty.j2' 5 | dest: '/etc/securetty' 6 | owner: 'root' 7 | group: 'root' 8 | mode: '0400' 9 | -------------------------------------------------------------------------------- /roles/mysql_hardening/templates/my.cnf.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | [client] 5 | user=root 6 | password='{{ mysql_root_password | mandatory }}' 7 | #ssl 8 | -------------------------------------------------------------------------------- /roles/ssh_hardening/templates/trusted_user_ca_keys.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | {% for key in ssh_trusted_user_ca_keys %} 5 | {{ key }} 6 | {% endfor %} 7 | -------------------------------------------------------------------------------- /roles/ssh_hardening/templates/authorized_principals.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | {% for principal in item.principals %} 5 | {{ principal }} 6 | {% endfor %} 7 | -------------------------------------------------------------------------------- /roles/mysql_hardening/vars/Fedora.yml: -------------------------------------------------------------------------------- 1 | --- 2 | mysql_daemon: mysqld 3 | mysql_hardening_mysql_conf_file: '/etc/my.cnf' 4 | mysql_hardening_mysql_confd_dir: '/etc/my.cnf.d' 5 | 6 | mysql_hardening_mysql_log_file: '/var/log/mysqld.log' 7 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/login_defs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create login.defs | os-05, os-05b 3 | template: 4 | src: 'etc/login.defs.j2' 5 | dest: '/etc/login.defs' 6 | owner: 'root' 7 | group: 'root' 8 | mode: '0444' 9 | -------------------------------------------------------------------------------- /molecule/nginx_hardening/official-nginx-role-redhat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: wrapper playbook for kitchen testing "ansible-nginx-hardening" with custom settings 3 | hosts: localhost 4 | roles: 5 | - nginxinc.nginx 6 | - nginx_hardening 7 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/apt.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Remove deprecated or insecure packages | package-01 - package-09 3 | apt: 4 | name: '{{ os_security_packages_list }}' 5 | state: 'absent' 6 | purge: 'yes' 7 | when: os_security_packages_clean | bool 8 | -------------------------------------------------------------------------------- /roles/os_hardening/templates/etc/securetty.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | # A list of TTYs, from which root can log in 5 | # see `man securetty` for reference 6 | {{ "\n".join(os_auth_root_ttys) }} 7 | -------------------------------------------------------------------------------- /roles/os_hardening/templates/etc/modprobe.d/modprobe.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | {% for fs in os_unused_filesystems | difference(os_filesystem_whitelist) %} 5 | install {{fs}} /bin/true 6 | {% endfor %} 7 | -------------------------------------------------------------------------------- /roles/ssh_hardening/files/ssh_password: -------------------------------------------------------------------------------- 1 | module ssh_password 1.0; 2 | 3 | require { 4 | type sshd_t; 5 | type shadow_t; 6 | class file { read open }; 7 | } 8 | 9 | #============= sshd_t ============== 10 | allow sshd_t shadow_t:file { read open }; 11 | -------------------------------------------------------------------------------- /roles/mysql_hardening/vars/Oracle Linux.yml: -------------------------------------------------------------------------------- 1 | --- 2 | mysql_daemon: mysqld 3 | 4 | mysql_hardening_mysql_conf_file: '/etc/my.cnf' 5 | mysql_hardening_mysql_confd_dir: '/etc/my.cnf.d' 6 | 7 | mysql_hardening_log_file: '/var/log/mysqld.log' 8 | 9 | mysql_hardening_group: 'adm' 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "roles/windows_hardening"] 2 | path = roles/windows_hardening 3 | url = https://github.com/dev-sec/ansible-windows-hardening/ 4 | [submodule "roles/apache_hardening"] 5 | path = roles/apache_hardening 6 | url = https://github.com/dev-sec/ansible-apache-hardening/ 7 | -------------------------------------------------------------------------------- /molecule/mysql_hardening/requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | roles: 3 | - name: geerlingguy.git 4 | version: 3.0.0 5 | - name: dev-sec.mysql 6 | version: master 7 | 8 | collections: 9 | - name: https://github.com/ansible-collections/community.mysql.git 10 | type: git 11 | version: main 12 | -------------------------------------------------------------------------------- /.github/workflows/enforce-labels.yml: -------------------------------------------------------------------------------- 1 | name: "Enforce PR labels" 2 | 3 | on: 4 | pull_request_target: 5 | types: [labeled, unlabeled, opened, edited, synchronize] 6 | jobs: 7 | enforce-label: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/labeler@main 11 | with: 12 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 13 | -------------------------------------------------------------------------------- /roles/os_hardening/templates/etc/profile.d/profile.conf.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | # Disable core dumps via soft limits for all users. Compliance to this setting is voluntary and can be modified by users up to a hard limit. This setting is a sane default. 5 | ulimit -S -c 0 > /dev/null 2>&1 6 | -------------------------------------------------------------------------------- /roles/os_hardening/templates/usr/share/pam-configs/pam_faillock_authfail.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | Name: faillock lockout after failed attempts enforcement 5 | Default: yes 6 | Priority: 0 7 | Auth-Type: Primary 8 | Auth: 9 | required pam_faillock.so authfail 10 | 11 | -------------------------------------------------------------------------------- /roles/os_hardening/templates/usr/share/pam-configs/pam_passwdqc.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | Name: passwdqc password strength enforcement 5 | Default: yes 6 | Priority: 1024 7 | Conflicts: cracklib 8 | Password-Type: Primary 9 | Password: 10 | requisite pam_passwdqc.so {{ os_auth_pam_passwdqc_options }} 11 | -------------------------------------------------------------------------------- /roles/os_hardening/templates/usr/share/pam-configs/pam_faillock.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | Name: faillock boilerplate 5 | Default: yes 6 | Priority: 1024 7 | Auth-Type: Primary 8 | Auth: 9 | required pam_faillock.so preauth 10 | Account-Type: Primary 11 | Account: 12 | required pam_faillock.so 13 | -------------------------------------------------------------------------------- /roles/mysql_hardening/vars/RedHat_8.yml: -------------------------------------------------------------------------------- 1 | --- 2 | mysql_daemon: mariadb 3 | mysql_hardening_mysql_conf_file: '/etc/my.cnf' 4 | mysql_hardening_mysql_confd_dir: '/etc/my.cnf.d' 5 | mysql_hardening_log_file: '/var/log/mariadb/mariadb.log' 6 | 7 | mysql_cnf_owner: 'root' # owner of /etc/mysql/*.cnf files 8 | mysql_cnf_group: 'mysql' # owner of /etc/mysql/*.cnf files 9 | 10 | mysql_hardening_group: 'mysql' 11 | -------------------------------------------------------------------------------- /roles/mysql_hardening/vars/Rocky_8.yml: -------------------------------------------------------------------------------- 1 | --- 2 | mysql_daemon: mariadb 3 | mysql_hardening_mysql_conf_file: '/etc/my.cnf' 4 | mysql_hardening_mysql_confd_dir: '/etc/my.cnf.d' 5 | mysql_hardening_log_file: '/var/log/mariadb/mariadb.log' 6 | 7 | mysql_cnf_owner: 'root' # owner of /etc/mysql/*.cnf files 8 | mysql_cnf_group: 'mysql' # owner of /etc/mysql/*.cnf files 9 | 10 | mysql_hardening_group: 'mysql' 11 | -------------------------------------------------------------------------------- /roles/mysql_hardening/vars/RedHat_7.yml: -------------------------------------------------------------------------------- 1 | --- 2 | mysql_daemon: mariadb 3 | mysql_hardening_mysql_conf_file: '/etc/my.cnf' 4 | mysql_hardening_mysql_confd_dir: '/etc/my.cnf.d' 5 | 6 | mysql_hardening_log_file: '/var/log/mariadb/mariadb.log' 7 | 8 | mysql_cnf_owner: 'root' # owner of /etc/mysql/*.cnf files 9 | mysql_cnf_group: 'mysql' # owner of /etc/mysql/*.cnf files 10 | 11 | mysql_hardening_group: 'mysql' 12 | -------------------------------------------------------------------------------- /roles/mysql_hardening/vars/Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | mysql_daemon: mysql 3 | 4 | mysql_hardening_mysql_conf_file: '/etc/mysql/my.cnf' 5 | mysql_hardening_mysql_confd_dir: '/etc/mysql/conf.d' 6 | 7 | mysql_hardening_log_file: '/var/log/mysql/error.log' 8 | 9 | mysql_hardening_group: 'adm' 10 | 11 | mysql_cnf_owner: 'root' # owner of /etc/mysql/*.cnf files 12 | mysql_cnf_group: 'mysql' # owner of /etc/mysql/*.cnf files 13 | -------------------------------------------------------------------------------- /roles/mysql_hardening/vars/Ubuntu_16.yml: -------------------------------------------------------------------------------- 1 | --- 2 | mysql_daemon: mysql 3 | 4 | mysql_hardening_mysql_conf_file: '/etc/mysql/my.cnf' 5 | mysql_hardening_mysql_confd_dir: '/etc/mysql/conf.d' 6 | 7 | mysql_hardening_log_file: '/var/log/mysql/error.log' 8 | 9 | mysql_cnf_owner: 'root' # owner of /etc/mysql/*.cnf files 10 | mysql_cnf_group: 'mysql' # owner of /etc/mysql/*.cnf files 11 | 12 | mysql_hardening_group: 'adm' 13 | -------------------------------------------------------------------------------- /roles/mysql_hardening/vars/Ubuntu_18.yml: -------------------------------------------------------------------------------- 1 | --- 2 | mysql_daemon: mysql 3 | 4 | mysql_hardening_mysql_conf_file: '/etc/mysql/my.cnf' 5 | mysql_hardening_mysql_confd_dir: '/etc/mysql/conf.d' 6 | 7 | mysql_hardening_log_file: '/var/log/mysql/error.log' 8 | 9 | mysql_cnf_owner: 'root' # owner of /etc/mysql/*.cnf files 10 | mysql_cnf_group: 'mysql' # owner of /etc/mysql/*.cnf files 11 | 12 | mysql_hardening_group: 'adm' 13 | -------------------------------------------------------------------------------- /roles/mysql_hardening/vars/Ubuntu_20.yml: -------------------------------------------------------------------------------- 1 | --- 2 | mysql_daemon: mysql 3 | 4 | mysql_hardening_mysql_conf_file: '/etc/mysql/my.cnf' 5 | mysql_hardening_mysql_confd_dir: '/etc/mysql/conf.d' 6 | 7 | mysql_hardening_log_file: '/var/log/mysql/error.log' 8 | 9 | mysql_cnf_owner: 'root' # owner of /etc/mysql/*.cnf files 10 | mysql_cnf_group: 'mysql' # owner of /etc/mysql/*.cnf files 11 | 12 | mysql_hardening_group: 'adm' 13 | -------------------------------------------------------------------------------- /roles/os_hardening/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Update-initramfs 3 | command: 'update-initramfs -u' 4 | 5 | - name: restart-auditd 6 | command: 7 | cmd: 'service auditd restart' # rhel: see: https://access.redhat.com/solutions/2664811 8 | warn: false # sadly 'service' module fails in that case also by using 'use: service' 9 | when: molecule_yml is not defined # restarting auditd in a container does not work 10 | -------------------------------------------------------------------------------- /roles/ssh_hardening/tasks/crypto_ciphers.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set ciphers according to openssh-version if openssh >= 5.3 3 | set_fact: 4 | ssh_ciphers: '{{ ssh_ciphers_53_default }}' 5 | when: sshd_version is version('5.3', '>=') 6 | 7 | - name: Set ciphers according to openssh-version if openssh >= 6.6 8 | set_fact: 9 | ssh_ciphers: '{{ ssh_ciphers_66_default }}' 10 | when: sshd_version is version('6.6', '>=') 11 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/auditd.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install auditd package | package-08 3 | package: 4 | name: '{{ auditd_package }}' 5 | state: 'present' 6 | tags: auditd 7 | 8 | - name: Configure auditd | package-08 9 | template: 10 | src: 'etc/audit/auditd.conf.j2' 11 | dest: '/etc/audit/auditd.conf' 12 | owner: 'root' 13 | group: 'root' 14 | mode: '0640' 15 | notify: 'restart-auditd' 16 | tags: auditd 17 | -------------------------------------------------------------------------------- /roles/ssh_hardening/vars/OpenBSD.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sshd_path: /usr/sbin/sshd 3 | ssh_host_keys_dir: '/etc/ssh' 4 | sshd_service_name: sshd 5 | ssh_owner: root 6 | ssh_group: wheel 7 | ssh_host_keys_owner: 'root' 8 | ssh_host_keys_group: 'root' 9 | 10 | # true if SSH support Kerberos 11 | ssh_kerberos_support: false 12 | 13 | # true if SSH has PAM support 14 | ssh_pam_support: false 15 | 16 | sshd_moduli_file: '/etc/moduli' 17 | 18 | sshd_disable_crypto_policy: false 19 | -------------------------------------------------------------------------------- /roles/ssh_hardening/vars/Suse.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sshd_path: /usr/sbin/sshd 3 | ssh_host_keys_dir: '/etc/ssh' 4 | sshd_service_name: sshd 5 | ssh_owner: root 6 | ssh_group: root 7 | ssh_host_keys_owner: 'root' 8 | ssh_host_keys_group: 'root' 9 | 10 | # true if SSH support Kerberos 11 | ssh_kerberos_support: true 12 | 13 | # true if SSH has PAM support 14 | ssh_pam_support: true 15 | 16 | sshd_moduli_file: '/etc/ssh/moduli' 17 | 18 | sshd_disable_crypto_policy: false 19 | -------------------------------------------------------------------------------- /roles/ssh_hardening/vars/FreeBSD.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sshd_path: /usr/sbin/sshd 3 | ssh_host_keys_dir: '/etc/ssh' 4 | sshd_service_name: sshd 5 | ssh_owner: root 6 | ssh_group: wheel 7 | ssh_host_keys_owner: 'root' 8 | ssh_host_keys_group: 'root' 9 | 10 | # true if SSH support Kerberos 11 | ssh_kerberos_support: true 12 | 13 | # true if SSH has PAM support 14 | ssh_pam_support: true 15 | 16 | sshd_moduli_file: '/etc/ssh/moduli' 17 | 18 | sshd_disable_crypto_policy: false 19 | -------------------------------------------------------------------------------- /roles/ssh_hardening/vars/SmartOS.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sshd_path: /usr/lib/ssh/sshd 3 | ssh_host_keys_dir: '/var/ssh' 4 | sshd_service_name: ssh 5 | ssh_owner: root 6 | ssh_group: root 7 | ssh_host_keys_owner: 'root' 8 | ssh_host_keys_group: 'root' 9 | 10 | # true if SSH support Kerberos 11 | ssh_kerberos_support: true 12 | 13 | # true if SSH has PAM support 14 | ssh_pam_support: false 15 | 16 | sshd_moduli_file: '/etc/ssh/moduli' 17 | 18 | sshd_disable_crypto_policy: false 19 | -------------------------------------------------------------------------------- /roles/os_hardening/templates/usr/share/pam-configs/pam_tally2.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | Name: tally2 lockout after failed attempts enforcement 5 | Default: yes 6 | Priority: 1024 7 | Conflicts: cracklib 8 | Auth-Type: Primary 9 | Auth-Initial: 10 | required pam_tally2.so deny={{ os_auth_retries }} onerr=fail unlock_time={{ os_auth_lockout_time }} 11 | Account-Type: Primary 12 | Account-Initial: 13 | required pam_tally2.so 14 | -------------------------------------------------------------------------------- /molecule/ssh_hardening/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: wrapper playbook for kitchen testing "ansible-ssh-hardening" with default settings 3 | hosts: all 4 | become: true 5 | environment: 6 | http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" 7 | https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" 8 | no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" 9 | collections: 10 | - devsec.hardening 11 | tasks: 12 | - include_role: 13 | name: ssh_hardening 14 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/profile.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add pinerolo_profile.sh to profile.d 3 | template: 4 | src: 'etc/profile.d/profile.conf.j2' 5 | dest: '/etc/profile.d/pinerolo_profile.sh' 6 | owner: 'root' 7 | group: 'root' 8 | mode: '0750' 9 | when: not os_security_kernel_enable_core_dump | bool 10 | 11 | - name: Remove pinerolo_profile.sh from profile.d 12 | file: 13 | path: /etc/profile.d/pinerolo_profile.sh 14 | state: absent 15 | when: os_security_kernel_enable_core_dump | bool 16 | -------------------------------------------------------------------------------- /roles/ssh_hardening/vars/Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sshd_path: /usr/sbin/sshd 3 | ssh_host_keys_dir: '/etc/ssh' 4 | sshd_service_name: ssh 5 | ssh_owner: root 6 | ssh_group: root 7 | ssh_host_keys_owner: 'root' 8 | ssh_host_keys_group: 'root' 9 | ssh_selinux_packages: 10 | - policycoreutils-python 11 | - checkpolicy 12 | 13 | # true if SSH support Kerberos 14 | ssh_kerberos_support: true 15 | 16 | # true if SSH has PAM support 17 | ssh_pam_support: true 18 | 19 | sshd_moduli_file: '/etc/ssh/moduli' 20 | 21 | sshd_disable_crypto_policy: false 22 | -------------------------------------------------------------------------------- /roles/mysql_hardening/templates/hardening.cnf.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | [mysqld] 5 | {% if mysql_hardening_skip_show_database -%} 6 | skip-show-database 7 | {% endif %} 8 | {% if mysql_hardening_skip_grant_tables -%} 9 | skip-grant-tables 10 | {% endif %} 11 | 12 | {% for (key, value) in mysql_hardening_options.items() %} 13 | {{ key }} = {{ value }} 14 | {% endfor %} 15 | 16 | {% if mysql_hardening_chroot %} 17 | chroot = '{{ mysql_hardening_chroot }}' 18 | {% endif %} 19 | -------------------------------------------------------------------------------- /molecule/nginx_hardening/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: wrapper playbook for kitchen testing "ansible-nginx-hardening" with custom settings 3 | become: true 4 | hosts: all 5 | environment: 6 | http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" 7 | https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" 8 | no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" 9 | vars: 10 | - nginx_ppa_use: true 11 | - nginx_ppa_version: stable 12 | tasks: 13 | - include_role: 14 | name: nginx_hardening 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /roles/ssh_hardening/vars/Archlinux.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sshd_path: /usr/sbin/sshd 3 | ssh_host_keys_dir: '/etc/ssh' 4 | sshd_service_name: sshd 5 | ssh_owner: root 6 | ssh_group: root 7 | ssh_host_keys_owner: 'root' 8 | ssh_host_keys_group: 'root' 9 | 10 | # true if SSH support Kerberos 11 | ssh_kerberos_support: true 12 | 13 | # true if SSH has PAM support 14 | ssh_pam_support: true 15 | 16 | sshd_moduli_file: '/etc/ssh/moduli' 17 | 18 | # CRYPTO_POLICY is not supported on Archlinux 19 | # and the package check only works in Ansible >2.10 20 | sshd_disable_crypto_policy: false 21 | -------------------------------------------------------------------------------- /.github/workflows/prettier-md.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://github.com/creyD/prettier_action 3 | name: Prettier markdown files 4 | 5 | on: 6 | push: 7 | paths: 8 | - '**.md' 9 | 10 | jobs: 11 | prettier-md: 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 1 14 | 15 | steps: 16 | - name: Git checkout 17 | uses: actions/checkout@v2 18 | with: 19 | ref: ${{ github.head_ref }} 20 | 21 | - name: Prettify code 22 | uses: creyD/prettier_action@v3.1 23 | with: 24 | prettier_options: --write {**/*,*}.md 25 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | mysql_hardening: 3 | - 'roles/mysql_hardening/**' 4 | - 'molecule/mysql_hardening/**' 5 | - '.github/workflows/mysql_hardening.yml' 6 | 7 | os_hardening: 8 | - 'roles/os_hardening/**' 9 | - 'molecule/os_hardening/**' 10 | - '.github/workflows/os_hardening.yml' 11 | 12 | ssh_hardening: 13 | - 'roles/ssh_hardening/**' 14 | - 'molecule/ssh_hardening/**' 15 | - '.github/workflows/ssh_hardening.yml' 16 | 17 | nginx_hardening: 18 | - 'roles/nginx_hardening/**' 19 | - 'molecule/nginx_hardening/**' 20 | - '.github/workflows/nginx_hardening.yml' 21 | -------------------------------------------------------------------------------- /roles/os_hardening/vars/Archlinux.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | os_nologin_shell_path: '/sbin/nologin' 4 | 5 | os_shadow_perms: 6 | owner: root 7 | group: root 8 | mode: '0600' 9 | 10 | os_passwd_perms: 11 | owner: root 12 | group: root 13 | mode: '0644' 14 | 15 | os_env_umask: '027' 16 | 17 | os_auth_uid_min: 1000 18 | os_auth_uid_max: 60000 19 | os_auth_gid_min: 1000 20 | os_auth_gid_max: 60000 21 | os_auth_sys_uid_min: 500 22 | os_auth_sys_uid_max: 999 23 | os_auth_sys_gid_min: 500 24 | os_auth_sys_gid_max: 999 25 | 26 | modprobe_package: 'kmod' 27 | auditd_package: 'audit' 28 | 29 | hidepid_option: '2' # allowed values: 0, 1, 2 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | **Is your feature request related to a problem? Please describe.** 7 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 8 | 9 | **Describe the solution you'd like** 10 | A clear and concise description of what you want to happen. 11 | 12 | **Describe alternatives you've considered** 13 | A clear and concise description of any alternative solutions or features you've considered. 14 | 15 | **Additional context** 16 | Add any other context or screenshots about the feature request here. 17 | -------------------------------------------------------------------------------- /roles/ssh_hardening/vars/Fedora.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sshd_path: /usr/sbin/sshd 3 | ssh_host_keys_dir: '/etc/ssh' 4 | sshd_service_name: sshd 5 | ssh_owner: root 6 | ssh_group: root 7 | ssh_host_keys_owner: 'root' 8 | ssh_host_keys_group: 'ssh_keys' 9 | ssh_selinux_packages: 10 | - python3-policycoreutils 11 | - checkpolicy 12 | 13 | # true if SSH support Kerberos 14 | ssh_kerberos_support: true 15 | 16 | # true if SSH has PAM support 17 | ssh_pam_support: true 18 | 19 | sshd_moduli_file: '/etc/ssh/moduli' 20 | 21 | # disable CRYPTO_POLICY to take settings from sshd configuration 22 | # see: https://access.redhat.com/solutions/4410591 23 | sshd_disable_crypto_policy: true 24 | -------------------------------------------------------------------------------- /roles/ssh_hardening/vars/RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sshd_path: /usr/sbin/sshd 3 | ssh_host_keys_dir: '/etc/ssh' 4 | sshd_service_name: sshd 5 | ssh_owner: root 6 | ssh_group: root 7 | ssh_host_keys_owner: 'root' 8 | ssh_host_keys_group: 'ssh_keys' 9 | ssh_selinux_packages: 10 | - policycoreutils-python 11 | - checkpolicy 12 | 13 | # true if SSH support Kerberos 14 | ssh_kerberos_support: true 15 | 16 | # true if SSH has PAM support 17 | ssh_pam_support: true 18 | 19 | sshd_moduli_file: '/etc/ssh/moduli' 20 | 21 | # disable CRYPTO_POLICY to take settings from sshd configuration 22 | # see: https://access.redhat.com/solutions/4410591 23 | sshd_disable_crypto_policy: true 24 | -------------------------------------------------------------------------------- /roles/ssh_hardening/vars/Rocky_8.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sshd_path: /usr/sbin/sshd 3 | ssh_host_keys_dir: '/etc/ssh' 4 | sshd_service_name: sshd 5 | ssh_owner: root 6 | ssh_group: root 7 | ssh_host_keys_owner: 'root' 8 | ssh_host_keys_group: 'ssh_keys' 9 | ssh_selinux_packages: 10 | - python3-policycoreutils 11 | - checkpolicy 12 | 13 | # true if SSH support Kerberos 14 | ssh_kerberos_support: true 15 | 16 | # true if SSH has PAM support 17 | ssh_pam_support: true 18 | 19 | sshd_moduli_file: '/etc/ssh/moduli' 20 | 21 | # disable CRYPTO_POLICY to take settings from sshd configuration 22 | # see: https://access.redhat.com/solutions/4410591 23 | sshd_disable_crypto_policy: true 24 | -------------------------------------------------------------------------------- /roles/nginx_hardening/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: "Sebastian Gumprich" 4 | description: 'This Ansible role provides secure nginx configurations. http://dev-sec.io/' 5 | company: Hardening Framework Team 6 | license: Apache License 2.0 7 | min_ansible_version: '2.5' 8 | platforms: 9 | - name: EL 10 | versions: 11 | - 6 12 | - 7 13 | - name: Ubuntu 14 | versions: 15 | - xenial 16 | - bionic 17 | - name: Debian 18 | versions: 19 | - stretch 20 | - buster 21 | galaxy_tags: 22 | - system 23 | - security 24 | - hardening 25 | - nginx 26 | dependencies: [] 27 | -------------------------------------------------------------------------------- /roles/ssh_hardening/vars/RedHat_8.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sshd_path: /usr/sbin/sshd 3 | ssh_host_keys_dir: '/etc/ssh' 4 | sshd_service_name: sshd 5 | ssh_owner: root 6 | ssh_group: root 7 | ssh_host_keys_owner: 'root' 8 | ssh_host_keys_group: 'ssh_keys' 9 | ssh_selinux_packages: 10 | - python3-policycoreutils 11 | - checkpolicy 12 | 13 | # true if SSH support Kerberos 14 | ssh_kerberos_support: true 15 | 16 | # true if SSH has PAM support 17 | ssh_pam_support: true 18 | 19 | sshd_moduli_file: '/etc/ssh/moduli' 20 | 21 | # disable CRYPTO_POLICY to take settings from sshd configuration 22 | # see: https://access.redhat.com/solutions/4410591 23 | sshd_disable_crypto_policy: true 24 | -------------------------------------------------------------------------------- /roles/ssh_hardening/files/sshd: -------------------------------------------------------------------------------- 1 | # Configuration file for the sshd service. 2 | 3 | # The server keys are automatically generated if they are missing. 4 | # To change the automatic creation, adjust sshd.service options for 5 | # example using systemctl enable sshd-keygen@dsa.service to allow creation 6 | # of DSA key or systemctl mask sshd-keygen@rsa.service to disable RSA key 7 | # creation. 8 | 9 | # Do not change this option unless you have hardware random 10 | # generator and you REALLY know what you are doing 11 | 12 | SSH_USE_STRONG_RNG=0 13 | # SSH_USE_STRONG_RNG=1 14 | 15 | # System-wide crypto policy: 16 | # To opt-out, uncomment the following line 17 | CRYPTO_POLICY= 18 | -------------------------------------------------------------------------------- /molecule/mysql_hardening/INSTALL.rst: -------------------------------------------------------------------------------- 1 | ****** 2 | Docker driver installation guide 3 | ****** 4 | 5 | Requirements 6 | ============ 7 | 8 | * Docker Engine 9 | 10 | Install 11 | ======= 12 | 13 | Please refer to the `Virtual environment`_ documentation for installation best 14 | practices. If not using a virtual environment, please consider passing the 15 | widely recommended `'--user' flag`_ when invoking ``pip``. 16 | 17 | .. _Virtual environment: https://virtualenv.pypa.io/en/latest/ 18 | .. _'--user' flag: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site 19 | 20 | .. code-block:: bash 21 | 22 | $ python3 -m pip install 'molecule[docker]' 23 | -------------------------------------------------------------------------------- /molecule/nginx_hardening/INSTALL.rst: -------------------------------------------------------------------------------- 1 | ******* 2 | Docker driver installation guide 3 | ******* 4 | 5 | Requirements 6 | ============ 7 | 8 | * Docker Engine 9 | 10 | Install 11 | ======= 12 | 13 | Please refer to the `Virtual environment`_ documentation for installation best 14 | practices. If not using a virtual environment, please consider passing the 15 | widely recommended `'--user' flag`_ when invoking ``pip``. 16 | 17 | .. _Virtual environment: https://virtualenv.pypa.io/en/latest/ 18 | .. _'--user' flag: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site 19 | 20 | .. code-block:: bash 21 | 22 | $ python3 -m pip install 'molecule[docker]' 23 | -------------------------------------------------------------------------------- /molecule/os_hardening/INSTALL.rst: -------------------------------------------------------------------------------- 1 | ******* 2 | Docker driver installation guide 3 | ******* 4 | 5 | Requirements 6 | ============ 7 | 8 | * Docker Engine 9 | 10 | Install 11 | ======= 12 | 13 | Please refer to the `Virtual environment`_ documentation for installation best 14 | practices. If not using a virtual environment, please consider passing the 15 | widely recommended `'--user' flag`_ when invoking ``pip``. 16 | 17 | .. _Virtual environment: https://virtualenv.pypa.io/en/latest/ 18 | .. _'--user' flag: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site 19 | 20 | .. code-block:: bash 21 | 22 | $ python3 -m pip install 'molecule[docker]' 23 | -------------------------------------------------------------------------------- /molecule/ssh_hardening/INSTALL.rst: -------------------------------------------------------------------------------- 1 | ******* 2 | Docker driver installation guide 3 | ******* 4 | 5 | Requirements 6 | ============ 7 | 8 | * Docker Engine 9 | 10 | Install 11 | ======= 12 | 13 | Please refer to the `Virtual environment`_ documentation for installation best 14 | practices. If not using a virtual environment, please consider passing the 15 | widely recommended `'--user' flag`_ when invoking ``pip``. 16 | 17 | .. _Virtual environment: https://virtualenv.pypa.io/en/latest/ 18 | .. _'--user' flag: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site 19 | 20 | .. code-block:: bash 21 | 22 | $ python3 -m pip install 'molecule[docker]' 23 | -------------------------------------------------------------------------------- /roles/mysql_hardening/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: "Sebastian Gumprich" 4 | description: 'This Ansible playbook provides security configuration for mysql.' 5 | company: Hardening Framework Team 6 | license: Apache License 2.0 7 | min_ansible_version: '1.9' 8 | platforms: 9 | - name: EL 10 | versions: 11 | - 6 12 | - 7 13 | - name: Ubuntu 14 | versions: 15 | - xenial 16 | - bionic 17 | - name: Debian 18 | versions: 19 | - jessie 20 | - name: Amazon 21 | - name: Fedora 22 | galaxy_tags: 23 | - system 24 | - security 25 | - hardening 26 | - database 27 | - mysql 28 | dependencies: [] 29 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/rhosts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get user accounts | os-09 3 | command: "awk -F: '{print $1}' /etc/passwd" 4 | changed_when: false 5 | check_mode: false 6 | register: users_accounts 7 | 8 | - name: Delete rhosts-files from system | os-09 9 | file: 10 | dest: '~{{ item }}/.rhosts' 11 | state: 'absent' 12 | with_flattened: '{{ users_accounts.stdout_lines | default([]) }}' 13 | 14 | - name: Delete hosts.equiv from system | os-01 15 | file: 16 | dest: '/etc/hosts.equiv' 17 | state: 'absent' 18 | 19 | - name: Delete .netrc-files from system | os-09 20 | file: 21 | dest: '~{{ item }}/.netrc' 22 | state: 'absent' 23 | with_flattened: '{{ users_accounts.stdout_lines | default([]) }}' 24 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | # Based on ansible-lint config 3 | extends: default 4 | 5 | rules: 6 | braces: 7 | max-spaces-inside: 1 8 | level: error 9 | brackets: 10 | max-spaces-inside: 1 11 | level: error 12 | colons: 13 | max-spaces-after: -1 14 | level: error 15 | commas: 16 | max-spaces-after: -1 17 | level: error 18 | # comments: disable 19 | # comments-indentation: disable 20 | # document-start: disable 21 | empty-lines: 22 | max: 3 23 | level: error 24 | hyphens: 25 | level: error 26 | # indentation: disable 27 | key-duplicates: enable 28 | line-length: disable 29 | # new-line-at-end-of-file: disable 30 | new-lines: 31 | type: unix 32 | # trailing-spaces: disable 33 | # truthy: disable 34 | -------------------------------------------------------------------------------- /roles/ssh_hardening/tasks/crypto_kex.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set kex according to openssh-version if openssh >= 5.9 3 | set_fact: 4 | ssh_kex: '{{ ssh_kex_59_default }}' 5 | when: sshd_version is version('5.9', '>=') 6 | 7 | - name: Set kex according to openssh-version if openssh >= 6.6 8 | set_fact: 9 | ssh_kex: '{{ ssh_kex_66_default }}' 10 | when: sshd_version is version('6.6', '>=') 11 | 12 | - name: Set kex according to openssh-version if openssh >= 8.0 13 | set_fact: 14 | ssh_kex: '{{ ssh_kex_80_default }}' 15 | when: sshd_version is version('8.0', '>=') 16 | 17 | - name: Set kex according to openssh-version if openssh >= 8.5 18 | set_fact: 19 | ssh_kex: '{{ ssh_kex_85_default }}' 20 | when: sshd_version is version('8.5', '>=') 21 | -------------------------------------------------------------------------------- /roles/ssh_hardening/tasks/crypto_macs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set macs according to openssh-version if openssh >= 5.3 3 | set_fact: 4 | ssh_macs: '{{ ssh_macs_53_default }}' 5 | when: sshd_version is version('5.3', '>=') 6 | 7 | - name: Set macs according to openssh-version if openssh >= 5.9 8 | set_fact: 9 | ssh_macs: '{{ ssh_macs_59_default }}' 10 | when: sshd_version is version('5.9', '>=') 11 | 12 | - name: Set macs according to openssh-version if openssh >= 6.6 13 | set_fact: 14 | ssh_macs: '{{ ssh_macs_66_default }}' 15 | when: sshd_version is version('6.6', '>=') 16 | 17 | - name: Set macs according to openssh-version if openssh >= 7.6 18 | set_fact: 19 | ssh_macs: '{{ ssh_macs_76_default }}' 20 | when: sshd_version is version('7.6', '>=') 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # These files are text and should be normalized (Convert crlf => lf) 2 | *.php text eol=lf 3 | *.css text eol=lf 4 | *.js text eol=lf 5 | *.htm text eol=lf 6 | *.html text eol=lf 7 | *.xml text eol=lf 8 | *.txt text eol=lf 9 | *.ini text eol=lf 10 | *.inc text eol=lf 11 | .htaccess text eol=lf 12 | *.pp text eol=lf 13 | *.yml text eol=lf 14 | *.yaml text eol=lf 15 | *.sh text eol=lf 16 | 17 | # These files are binary and should be left untouched 18 | # (binary is a macro for -text -diff) 19 | *.png binary 20 | *.jpg binary 21 | *.jpeg binary 22 | *.gif binary 23 | *.ico binary 24 | *.mov binary 25 | *.mp4 binary 26 | *.mp3 binary 27 | *.flv binary 28 | *.fla binary 29 | *.swf binary 30 | *.gz binary 31 | *.zip binary 32 | *.7z binary 33 | *.ttf binary 34 | *.rpm binary 35 | 36 | -------------------------------------------------------------------------------- /roles/os_hardening/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: "Sebastian Gumprich" 4 | description: 'This Ansible role provides numerous security-related ssh configurations, providing all-round base protection.' 5 | company: Hardening Framework Team 6 | license: Apache License 2.0 7 | min_ansible_version: '2.5' 8 | platforms: 9 | - name: EL 10 | versions: 11 | - 7 12 | - 8 13 | - name: Ubuntu 14 | versions: 15 | - xenial 16 | - bionic 17 | - name: Debian 18 | versions: 19 | - stretch 20 | - buster 21 | - name: Amazon 22 | - name: Fedora 23 | - name: Archlinux 24 | - name: SmartOS 25 | - name: openSUSE 26 | galaxy_tags: 27 | - system 28 | - security 29 | - hardening 30 | dependencies: [] 31 | -------------------------------------------------------------------------------- /roles/ssh_hardening/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: "Sebastian Gumprich" 4 | description: 'This Ansible role provides numerous security-related ssh configurations, providing all-round base protection.' 5 | company: Hardening Framework Team 6 | license: Apache License 2.0 7 | min_ansible_version: '2.5' 8 | platforms: 9 | - name: EL 10 | versions: 11 | - 7 12 | - 8 13 | - name: Ubuntu 14 | versions: 15 | - xenial 16 | - bionic 17 | - name: Debian 18 | versions: 19 | - stretch 20 | - buster 21 | - name: Amazon 22 | - name: Fedora 23 | - name: Archlinux 24 | - name: SmartOS 25 | - name: openSUSE 26 | galaxy_tags: 27 | - system 28 | - security 29 | - hardening 30 | dependencies: [] 31 | -------------------------------------------------------------------------------- /molecule/nginx_hardening/official-nginx-role-debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: wrapper playbook for kitchen testing "ansible-nginx-hardening" with custom settings 3 | hosts: localhost 4 | vars: 5 | - nginx_main_template_enable: true 6 | - nginx_main_template: 7 | template_file: nginx.conf.j2 8 | conf_file_name: nginx.conf 9 | conf_file_location: /etc/nginx/ 10 | user: www-data 11 | worker_processes: auto 12 | error_level: warn 13 | worker_connections: 1024 14 | http_enable: true 15 | http_settings: 16 | keepalive_timeout: 65 17 | cache: false 18 | rate_limit: false 19 | keyval: false 20 | stream_enable: false 21 | http_global_autoindex: false 22 | roles: 23 | - nginxinc.nginx 24 | - nginx_hardening 25 | -------------------------------------------------------------------------------- /roles/nginx_hardening/templates/hardening.conf.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed|comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | # Additional configuration for Nginx. 4 | 5 | client_header_buffer_size {{ nginx_client_header_buffer_size }}; 6 | large_client_header_buffers {{ nginx_large_client_header_buffers }}; 7 | client_body_timeout {{ nginx_client_body_timeout }}; 8 | client_header_timeout {{ nginx_client_header_timeout }}; 9 | send_timeout {{ nginx_send_timeout }}; 10 | limit_conn_zone {{ nginx_limit_conn_zone }}; 11 | limit_conn {{ nginx_limit_conn }}; 12 | ssl_ciphers '{{ nginx_ssl_ciphers }}'; 13 | ssl_session_tickets {{ nginx_ssl_session_tickets }}; 14 | ssl_dhparam /etc/nginx/dh{{ nginx_dh_size }}.pem; 15 | {% for header in nginx_add_header %} 16 | add_header {{ header }}; 17 | {% endfor %} 18 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/cron.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Granting write access to this directory for non-privileged users could provide 3 | # them the means for gaining unauthorized elevated privileges. 4 | # Granting read access to this directory could give an unprivileged user insight 5 | # in how to gain elevated privileges or circumvent auditing controls. 6 | # CIS 5.1.2 - CIS 5.1.7 7 | # 8 | - name: Find cron files and directories 9 | find: 10 | paths: 11 | - /etc 12 | patterns: 13 | - cron.hourly 14 | - cron.daily 15 | - cron.weekly 16 | - cron.monthly 17 | - cron.d 18 | - crontab 19 | file_type: any 20 | register: cron_directories 21 | 22 | - name: Ensure permissions on cron files and directories are configured 23 | ansible.builtin.file: 24 | path: "{{ item.path }}" 25 | owner: root 26 | group: root 27 | mode: og-rwx 28 | with_items: "{{ cron_directories.files }}" 29 | -------------------------------------------------------------------------------- /roles/os_hardening/templates/etc/audit/auditd.conf.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | log_file = /var/log/audit/audit.log 5 | log_format = RAW 6 | log_group = root 7 | priority_boost = 4 8 | flush = INCREMENTAL 9 | freq = 20 10 | num_logs = 5 11 | disp_qos = lossy 12 | dispatcher = /sbin/audispd 13 | name_format = NONE 14 | ##name = mydomain 15 | max_log_file = {{ os_auditd_max_log_file }} 16 | max_log_file_action = {{ os_auditd_max_log_file_action }} 17 | space_left = 75 18 | space_left_action = SYSLOG 19 | action_mail_acct = root 20 | admin_space_left = 50 21 | admin_space_left_action = SUSPEND 22 | disk_full_action = SUSPEND 23 | disk_error_action = SUSPEND 24 | ##tcp_listen_port = 25 | tcp_listen_queue = 5 26 | tcp_max_per_addr = 1 27 | ##tcp_client_ports = 1024-65535 28 | tcp_client_max_idle = 0 29 | enable_krb5 = no 30 | krb5_principal = auditd 31 | ##krb5_key_file = /etc/audit/audit.key 32 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/modprobe.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install modprobe to disable filesystems | os-10 3 | package: 4 | name: '{{ modprobe_package }}' 5 | state: 'present' 6 | 7 | - name: Check if efi is installed 8 | stat: 9 | path: "/sys/firmware/efi" 10 | register: efi_installed 11 | 12 | - name: Remove vfat from fs-list if efi is used 13 | set_fact: 14 | os_unused_filesystems: "{{ os_unused_filesystems | difference('vfat') }}" 15 | when: 16 | - efi_installed.stat.isdir is defined 17 | - efi_installed.stat.isdir 18 | 19 | - name: Remove used filesystems from fs-list 20 | set_fact: 21 | os_unused_filesystems: "{{ os_unused_filesystems | difference(ansible_mounts | map(attribute='fstype') | list) }}" 22 | 23 | - name: Disable unused filesystems | os-10 24 | template: 25 | src: 'etc/modprobe.d/modprobe.j2' 26 | dest: '/etc/modprobe.d/dev-sec.conf' 27 | owner: 'root' 28 | group: 'root' 29 | mode: '0644' 30 | -------------------------------------------------------------------------------- /roles/os_hardening/vars/Suse.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | os_packages_pam_ccreds: 'pam_ccreds' 4 | os_nologin_shell_path: '/sbin/nologin' 5 | 6 | # Different distros use different standards for /etc/shadow perms, e.g. 7 | # RHEL derivatives use root:root 0000, whereas Debian-based use root:shadow 0640. 8 | # You must provide key/value pairs for owner, group, and mode if overriding. 9 | os_shadow_perms: 10 | owner: root 11 | group: shadow 12 | mode: '0640' 13 | 14 | os_passwd_perms: 15 | owner: root 16 | group: root 17 | mode: '0644' 18 | 19 | os_env_umask: '027' 20 | 21 | os_auth_uid_min: 1000 22 | os_auth_uid_max: 60000 23 | os_auth_gid_min: 1000 24 | os_auth_gid_max: 60000 25 | os_auth_sys_uid_min: 100 26 | os_auth_sys_uid_max: 499 27 | os_auth_sys_gid_min: 100 28 | os_auth_sys_gid_max: 499 29 | 30 | # defaults for useradd 31 | os_useradd_create_home: false 32 | 33 | modprobe_package: 'kmod-compat' 34 | auditd_package: 'audit' 35 | 36 | hidepid_option: '2' # allowed values: 0, 1, 2 37 | -------------------------------------------------------------------------------- /galaxy.yml: -------------------------------------------------------------------------------- 1 | namespace: devsec 2 | name: hardening 3 | version: 7.8.0 4 | readme: README.md 5 | authors: 6 | - dev-sec 7 | description: 'This collection provides battle tested hardening for Linux, SSH, nginx, MySQL' 8 | license: 9 | - GPL-2.0-or-later 10 | license_file: '' 11 | tags: 12 | - devsec 13 | - hardening 14 | - centos 15 | - ubuntu 16 | - debian 17 | - nginx 18 | - mysql 19 | - openssh 20 | dependencies: 21 | ansible.posix: '>=1.0.0' 22 | community.crypto: '>=1.0.0' 23 | community.general: '>=1.0.0' 24 | community.mysql: '>=1.3.0' 25 | repository: 'https://github.com/dev-sec/ansible-collection-hardening/' 26 | homepage: 'https://dev-sec.io/' 27 | issues: 'https://github.com/dev-sec/ansible-collection-hardening/issues' 28 | build_ignore: 29 | - codecov.yml 30 | - .github 31 | - .gitattributes 32 | - .gitignore 33 | - hacking 34 | - requirements.txt 35 | - test-requirements.txt 36 | - tests 37 | - .tox 38 | - tox.ini 39 | - .yamllint 40 | -------------------------------------------------------------------------------- /molecule/nginx_hardening/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: prepare playbook for kitchen testing "ansible-nginx-hardening" with custom settings 3 | become: true 4 | hosts: all 5 | environment: 6 | http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" 7 | https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" 8 | no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" 9 | tasks: 10 | - name: install required tools on SuSE 11 | zypper: 12 | name: "python-xml" 13 | state: present 14 | when: ansible_facts.os_family == 'Suse' 15 | 16 | - name: install required packages 17 | package: 18 | name: "python3-apt" 19 | update_cache: true 20 | ignore_errors: true 21 | 22 | - name: Set correct distribution Version for Amazon Linux 23 | set_fact: 24 | ansible_distribution_major_version: 7 25 | when: ansible_distribution == 'Amazon' 26 | 27 | - include_role: 28 | name: geerlingguy.nginx 29 | -------------------------------------------------------------------------------- /roles/ssh_hardening/tasks/ca_keys_and_principals.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set ssh CA pub keys 3 | template: 4 | src: 'trusted_user_ca_keys.j2' 5 | dest: '{{ ssh_trusted_user_ca_keys_file }}' 6 | mode: '0644' 7 | owner: '{{ ssh_owner }}' 8 | group: '{{ ssh_group }}' 9 | notify: Restart sshd 10 | 11 | - name: Create ssh authorized principals directories 12 | file: 13 | path: '{{ item.path | dirname }}' 14 | mode: '{{ item.directorymode | default(0700) }}' 15 | owner: '{{ item.directoryowner | default(ssh_owner) }}' 16 | group: '{{ item.directorygroup | default(ssh_group) }}' 17 | state: directory 18 | loop: '{{ ssh_authorized_principals }}' 19 | 20 | - name: Set ssh authorized principals 21 | template: 22 | src: 'authorized_principals.j2' 23 | dest: '{{ item.path }}' 24 | mode: '{{ item.filemode | default(0600) }}' 25 | owner: '{{ item.owner| default(ssh_owner) }}' 26 | group: '{{ item.group | default(ssh_group) }}' 27 | loop: '{{ ssh_authorized_principals }}' 28 | -------------------------------------------------------------------------------- /roles/os_hardening/vars/Fedora.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | os_packages_pam_ccreds: 'pam_ccreds' 4 | os_nologin_shell_path: '/sbin/nologin' 5 | 6 | # Different distros use different standards for /etc/shadow perms, e.g. 7 | # RHEL derivatives use root:root 0000, whereas Debian-based use root:shadow 0640. 8 | # You must provide key/value pairs for owner, group, and mode if overriding. 9 | os_shadow_perms: 10 | owner: root 11 | group: root 12 | mode: '0000' 13 | 14 | os_passwd_perms: 15 | owner: root 16 | group: root 17 | mode: '0644' 18 | 19 | os_env_umask: '027' 20 | 21 | os_auth_uid_min: 1000 22 | os_auth_uid_max: 60000 23 | os_auth_gid_min: 1000 24 | os_auth_gid_max: 60000 25 | os_auth_sys_uid_min: 201 26 | os_auth_sys_uid_max: 999 27 | os_auth_sys_gid_min: 201 28 | os_auth_sys_gid_max: 999 29 | 30 | os_auth_pam_sssd_enable: true 31 | 32 | # defaults for useradd 33 | os_useradd_mail_dir: /var/spool/mail 34 | os_useradd_create_home: true 35 | 36 | modprobe_package: 'module-init-tools' 37 | auditd_package: 'audit' 38 | 39 | hidepid_option: '2' # allowed values: 0, 1, 2 40 | -------------------------------------------------------------------------------- /roles/os_hardening/vars/RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | os_packages_pam_ccreds: 'pam_ccreds' 4 | os_nologin_shell_path: '/sbin/nologin' 5 | 6 | # Different distros use different standards for /etc/shadow perms, e.g. 7 | # RHEL derivatives use root:root 0000, whereas Debian-based use root:shadow 0640. 8 | # You must provide key/value pairs for owner, group, and mode if overriding. 9 | os_shadow_perms: 10 | owner: root 11 | group: root 12 | mode: '0000' 13 | 14 | os_passwd_perms: 15 | owner: root 16 | group: root 17 | mode: '0644' 18 | 19 | os_env_umask: '077' 20 | 21 | os_auth_uid_min: 1000 22 | os_auth_uid_max: 60000 23 | os_auth_gid_min: 1000 24 | os_auth_gid_max: 60000 25 | os_auth_sys_uid_min: 201 26 | os_auth_sys_uid_max: 999 27 | os_auth_sys_gid_min: 201 28 | os_auth_sys_gid_max: 999 29 | 30 | os_auth_pam_sssd_enable: false 31 | 32 | # defaults for useradd 33 | os_useradd_mail_dir: /var/spool/mail 34 | os_useradd_create_home: true 35 | 36 | modprobe_package: 'module-init-tools' 37 | auditd_package: 'audit' 38 | 39 | hidepid_option: '2' # allowed values: 0, 1, 2 40 | -------------------------------------------------------------------------------- /roles/os_hardening/vars/RedHat_7.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | os_packages_pam_ccreds: 'pam_ccreds' 4 | os_nologin_shell_path: '/sbin/nologin' 5 | 6 | # Different distros use different standards for /etc/shadow perms, e.g. 7 | # RHEL derivatives use root:root 0000, whereas Debian-based use root:shadow 0640. 8 | # You must provide key/value pairs for owner, group, and mode if overriding. 9 | os_shadow_perms: 10 | owner: root 11 | group: root 12 | mode: '0000' 13 | 14 | os_passwd_perms: 15 | owner: root 16 | group: root 17 | mode: '0644' 18 | 19 | os_env_umask: '077' 20 | 21 | os_auth_uid_min: 1000 22 | os_auth_uid_max: 60000 23 | os_auth_gid_min: 1000 24 | os_auth_gid_max: 60000 25 | os_auth_sys_uid_min: 201 26 | os_auth_sys_uid_max: 999 27 | os_auth_sys_gid_min: 201 28 | os_auth_sys_gid_max: 999 29 | 30 | os_auth_pam_sssd_enable: false 31 | 32 | # defaults for useradd 33 | os_useradd_mail_dir: /var/spool/mail 34 | os_useradd_create_home: true 35 | 36 | modprobe_package: 'module-init-tools' 37 | auditd_package: 'audit' 38 | 39 | hidepid_option: '0' # allowed values: 0, 1, 2 40 | -------------------------------------------------------------------------------- /roles/os_hardening/vars/RedHat_8.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | os_packages_pam_ccreds: 'pam_ccreds' 4 | os_nologin_shell_path: '/sbin/nologin' 5 | 6 | # Different distros use different standards for /etc/shadow perms, e.g. 7 | # RHEL derivatives use root:root 0000, whereas Debian-based use root:shadow 0640. 8 | # You must provide key/value pairs for owner, group, and mode if overriding. 9 | os_shadow_perms: 10 | owner: root 11 | group: root 12 | mode: '0000' 13 | 14 | os_passwd_perms: 15 | owner: root 16 | group: root 17 | mode: '0644' 18 | 19 | os_env_umask: '077' 20 | 21 | os_auth_uid_min: 1000 22 | os_auth_uid_max: 60000 23 | os_auth_gid_min: 1000 24 | os_auth_gid_max: 60000 25 | os_auth_sys_uid_min: 201 26 | os_auth_sys_uid_max: 999 27 | os_auth_sys_gid_min: 201 28 | os_auth_sys_gid_max: 999 29 | 30 | os_auth_pam_sssd_enable: true 31 | 32 | # defaults for useradd 33 | os_useradd_mail_dir: /var/spool/mail 34 | os_useradd_create_home: true 35 | 36 | modprobe_package: 'module-init-tools' 37 | auditd_package: 'audit' 38 | 39 | hidepid_option: '2' # allowed values: 0, 1, 2 40 | -------------------------------------------------------------------------------- /roles/os_hardening/vars/Rocky_8.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | os_packages_pam_ccreds: 'pam_ccreds' 4 | os_nologin_shell_path: '/sbin/nologin' 5 | 6 | # Different distros use different standards for /etc/shadow perms, e.g. 7 | # RHEL derivatives use root:root 0000, whereas Debian-based use root:shadow 0640. 8 | # You must provide key/value pairs for owner, group, and mode if overriding. 9 | os_shadow_perms: 10 | owner: root 11 | group: root 12 | mode: '0000' 13 | 14 | os_passwd_perms: 15 | owner: root 16 | group: root 17 | mode: '0644' 18 | 19 | os_env_umask: '077' 20 | 21 | os_auth_uid_min: 1000 22 | os_auth_uid_max: 60000 23 | os_auth_gid_min: 1000 24 | os_auth_gid_max: 60000 25 | os_auth_sys_uid_min: 201 26 | os_auth_sys_uid_max: 999 27 | os_auth_sys_gid_min: 201 28 | os_auth_sys_gid_max: 999 29 | 30 | os_auth_pam_sssd_enable: true 31 | 32 | # defaults for useradd 33 | os_useradd_mail_dir: /var/spool/mail 34 | os_useradd_create_home: true 35 | 36 | modprobe_package: 'module-init-tools' 37 | auditd_package: 'audit' 38 | 39 | hidepid_option: '2' # allowed values: 0, 1, 2 40 | -------------------------------------------------------------------------------- /roles/os_hardening/vars/Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | os_packages_pam_ccreds: 'libpam-ccreds' 4 | os_nologin_shell_path: '/usr/sbin/nologin' 5 | 6 | # Different distros use different standards for /etc/shadow perms, e.g. 7 | # RHEL derivatives use root:root 0000, whereas Debian-based use root:shadow 0640. 8 | # You must provide key/value pairs for owner, group, and mode if overriding. 9 | os_shadow_perms: 10 | owner: root 11 | group: shadow 12 | mode: '0640' 13 | 14 | os_passwd_perms: 15 | owner: root 16 | group: root 17 | mode: '0644' 18 | 19 | os_env_umask: '027' 20 | 21 | os_auth_uid_min: 1000 22 | os_auth_uid_max: 60000 23 | os_auth_gid_min: 1000 24 | os_auth_gid_max: 60000 25 | os_auth_sys_uid_min: 100 26 | os_auth_sys_uid_max: 999 27 | os_auth_sys_gid_min: 100 28 | os_auth_sys_gid_max: 999 29 | 30 | # defaults for useradd 31 | os_useradd_mail_dir: /var/mail 32 | 33 | modprobe_package: 'kmod' 34 | auditd_package: 'auditd' 35 | 36 | tally2_path: '/usr/share/pam-configs/tally2' 37 | passwdqc_path: '/usr/share/pam-configs/passwdqc' 38 | 39 | hidepid_option: '2' # allowed values: 0, 1, 2 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | --- 5 | 6 | **Describe the bug** 7 | A clear and concise description of what the bug is. 8 | 9 | **Expected behavior** 10 | A clear and concise description of what you expected to happen. 11 | 12 | **Actual behavior** 13 | 14 | 15 | 16 | ```paste below 17 | 18 | ``` 19 | 20 | **Example Playbook** 21 | 22 | 23 | 24 | ```paste below 25 | 26 | ``` 27 | 28 | **OS / Environment** 29 | 30 | 31 | 32 | **Ansible Version** 33 | 34 | 35 | 36 | ```paste below 37 | 38 | ``` 39 | 40 | **Role Version** 41 | 42 | 43 | 44 | ```paste below 45 | 46 | ``` 47 | 48 | **Additional context** 49 | Add any other context about the problem here. 50 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/pam_rhel.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install sssd-clients 3 | yum: 4 | name: sssd-client 5 | state: 'present' 6 | when: 7 | - os_auth_pam_sssd_enable | bool 8 | 9 | - name: Configure passwdqc and faillock via central system-auth config 10 | template: 11 | src: 'etc/pam.d/rhel_auth.j2' 12 | dest: '/etc/pam.d/system-auth-local' 13 | mode: '0640' 14 | owner: 'root' 15 | group: 'root' 16 | 17 | - name: Enable our config for system-auth 18 | file: 19 | src: /etc/pam.d/system-auth-local 20 | dest: /etc/pam.d/system-auth 21 | mode: '0640' 22 | owner: 'root' 23 | group: 'root' 24 | state: link 25 | force: true 26 | 27 | - name: Configure passwdqc and faillock via central password-auth config 28 | template: 29 | src: 'etc/pam.d/rhel_auth.j2' 30 | dest: '/etc/pam.d/password-auth-local' 31 | mode: '0640' 32 | owner: 'root' 33 | group: 'root' 34 | 35 | - name: Enable our config for password-auth 36 | file: 37 | src: /etc/pam.d/password-auth-local 38 | dest: /etc/pam.d/password-auth 39 | mode: '0640' 40 | owner: 'root' 41 | group: 'root' 42 | state: link 43 | force: true 44 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/limits.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - block: 3 | - name: Create limits.d-directory if it does not exist | sysctl-31a, sysctl-31b 4 | file: 5 | path: '/etc/security/limits.d' 6 | owner: 'root' 7 | group: 'root' 8 | mode: '0755' 9 | state: 'directory' 10 | 11 | - name: Create additional limits config file -> 10.hardcore.conf | sysctl-31a, sysctl-31b 12 | pam_limits: 13 | dest: '/etc/security/limits.d/10.hardcore.conf' 14 | domain: '*' 15 | limit_type: hard 16 | limit_item: core 17 | value: '0' 18 | comment: Prevent core dumps for all users. These are usually not needed and may contain sensitive information 19 | 20 | - name: Set 10.hardcore.conf perms to 0400 and root ownership 21 | file: 22 | path: /etc/security/limits.d/10.hardcore.conf 23 | owner: 'root' 24 | group: 'root' 25 | mode: '0440' 26 | state: touch 27 | modification_time: preserve 28 | access_time: preserve 29 | 30 | when: not os_security_kernel_enable_core_dump | bool 31 | 32 | - name: Remove 10.hardcore.conf config file 33 | file: 34 | path: /etc/security/limits.d/10.hardcore.conf 35 | state: absent 36 | when: os_security_kernel_enable_core_dump | bool 37 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/pam.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Gather package facts 3 | package_facts: 4 | manager: auto 5 | when: 6 | - ansible_facts.os_family != 'Suse' 7 | - ansible_facts.os_family != 'Archlinux' 8 | 9 | # the reason for this is so a user cannot connect to a server, 10 | # that isn't connected to an LDAP server anymore. 11 | # normally caching credentials shouldn't be necessary for most machines. 12 | # removing it provides some more security while not removing usability. 13 | - name: Remove pam ccreds to disable password caching 14 | package: 15 | name: '{{ os_packages_pam_ccreds }}' 16 | state: 'absent' 17 | when: 18 | - ansible_facts.os_family != 'Archlinux' 19 | 20 | - import_tasks: pam_debian.yml 21 | when: 22 | - ansible_facts.os_family == 'Debian' 23 | 24 | - import_tasks: pam_rhel.yml 25 | when: 26 | - ansible_facts.os_family == 'RedHat' 27 | 28 | - name: NSA 2.3.3.5 Upgrade Password Hashing Algorithm to SHA-512 29 | template: 30 | src: 'etc/libuser.conf.j2' 31 | dest: '/etc/libuser.conf' 32 | mode: '0640' 33 | owner: 'root' 34 | group: 'root' 35 | when: 36 | - ansible_facts.os_family != 'Suse' 37 | - ansible_facts.os_family != 'Archlinux' 38 | - "'libuser' in ansible_facts.packages" 39 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/suid_sgid.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Remove suid/sgid bit from binaries in blacklist | os-06 3 | file: 4 | path: '{{ item }}' 5 | mode: 'a-s' 6 | state: 'file' 7 | follow: 'yes' 8 | failed_when: false 9 | with_flattened: 10 | - '{{ os_security_suid_sgid_system_blacklist }}' 11 | - '{{ os_security_suid_sgid_blacklist }}' 12 | 13 | - name: Find binaries with suid/sgid set | os-06 14 | shell: find / -xdev \( -perm -4000 -o -perm -2000 \) -type f ! -path '/proc/*' -print 2>/dev/null 15 | register: sbit_binaries 16 | when: os_security_suid_sgid_remove_from_unknown | bool 17 | changed_when: false 18 | 19 | - name: Gather files from which to remove suids/sgids and remove system white-listed files | os-06 20 | set_fact: 21 | suid: '{{ sbit_binaries.stdout_lines | difference(os_security_suid_sgid_system_whitelist) }}' 22 | when: os_security_suid_sgid_remove_from_unknown | bool 23 | 24 | - name: Remove suid/sgid bit from all binaries except in system and user whitelist | os-06 25 | file: 26 | path: '{{ item }}' 27 | mode: 'a-s' 28 | state: 'file' 29 | follow: 'yes' 30 | with_flattened: 31 | - '{{ suid | default([]) | difference(os_security_suid_sgid_whitelist) }}' 32 | when: os_security_suid_sgid_remove_from_unknown | bool 33 | -------------------------------------------------------------------------------- /roles/nginx_hardening/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | nginx_client_body_buffer_size: '1k' 3 | nginx_remove_default_site: true 4 | nginx_client_max_body_size: '1k' 5 | nginx_keepalive_timeout: '5 5' 6 | nginx_server_tokens: 'off' 7 | nginx_client_header_buffer_size: "1k" 8 | nginx_large_client_header_buffers: "2 1k" 9 | nginx_client_body_timeout: "10" 10 | nginx_client_header_timeout: "10" 11 | nginx_send_timeout: "10" 12 | nginx_limit_conn_zone: "$binary_remote_addr zone=default:10m" 13 | nginx_limit_conn: "default 5" 14 | nginx_add_header: [ 15 | # vvoid clickjacking 16 | "X-Frame-Options SAMEORIGIN", 17 | # disable content-type sniffing 18 | "X-Content-Type-Options nosniff", 19 | # XSS filter 20 | "X-XSS-Protection \"1; mode=block\"", 21 | "Strict-Transport-Security max-age=15768000", 22 | "Content-Security-Policy \"script-src 'self'; object-src 'self'\"" ] 23 | 24 | nginx_set_cookie_flag: "* HttpOnly secure" 25 | nginx_ssl_prefer_server_ciphers: "on" 26 | nginx_ssl_protocols: "TLSv1.2" 27 | nginx_ssl_ciphers: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256" 28 | nginx_ssl_session_tickets: "off" 29 | nginx_dh_size: "2048" 30 | -------------------------------------------------------------------------------- /roles/os_hardening/vars/Amazon.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | os_packages_pam_ccreds: 'pam_ccreds' 4 | os_nologin_shell_path: '/sbin/nologin' 5 | 6 | # Different distros use different standards for /etc/shadow perms, e.g. 7 | # RHEL derivatives use root:root 0000, whereas Debian-based use root:shadow 0640. 8 | # You must provide key/value pairs for owner, group, and mode if overriding. 9 | os_shadow_perms: 10 | owner: root 11 | group: root 12 | mode: '0000' 13 | 14 | os_passwd_perms: 15 | owner: root 16 | group: root 17 | mode: '0644' 18 | 19 | os_env_umask: '077' 20 | 21 | os_auth_uid_min: 1000 22 | os_auth_uid_max: 60000 23 | os_auth_gid_min: 1000 24 | os_auth_gid_max: 60000 25 | os_auth_sys_uid_min: 201 26 | os_auth_sys_uid_max: 999 27 | os_auth_sys_gid_min: 201 28 | os_auth_sys_gid_max: 999 29 | 30 | os_auth_pam_sssd_enable: false 31 | 32 | # defaults for useradd 33 | os_useradd_mail_dir: /var/spool/mail 34 | os_useradd_create_home: true 35 | 36 | modprobe_package: 'module-init-tools' 37 | auditd_package: 'audit' 38 | 39 | # system accounts that do not get their login disabled and pasword changed 40 | os_always_ignore_users: ['root', 'sync', 'shutdown', 'halt', 'ec2-user'] 41 | 42 | sysctl_rhel_config: 43 | # ExecShield protection against buffer overflows 44 | kernel.exec-shield: 1 45 | 46 | hidepid_option: '2' # allowed values: 0, 1, 2 47 | -------------------------------------------------------------------------------- /roles/ssh_hardening/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh_macs_53_default: 3 | - hmac-ripemd160 4 | - hmac-sha1 5 | 6 | ssh_macs_59_default: 7 | - hmac-sha2-512 8 | - hmac-sha2-256 9 | - hmac-ripemd160 10 | 11 | ssh_macs_66_default: 12 | - hmac-sha2-512-etm@openssh.com 13 | - hmac-sha2-256-etm@openssh.com 14 | - umac-128-etm@openssh.com 15 | - hmac-sha2-512 16 | - hmac-sha2-256 17 | 18 | ssh_macs_76_default: 19 | - hmac-sha2-512-etm@openssh.com 20 | - hmac-sha2-256-etm@openssh.com 21 | - umac-128-etm@openssh.com 22 | - hmac-sha2-512 23 | - hmac-sha2-256 24 | 25 | ssh_ciphers_53_default: 26 | - aes256-ctr 27 | - aes192-ctr 28 | - aes128-ctr 29 | 30 | ssh_ciphers_66_default: 31 | - chacha20-poly1305@openssh.com 32 | - aes256-gcm@openssh.com 33 | - aes128-gcm@openssh.com 34 | - aes256-ctr 35 | - aes192-ctr 36 | - aes128-ctr 37 | 38 | ssh_kex_59_default: 39 | - diffie-hellman-group-exchange-sha256 40 | 41 | ssh_kex_66_default: 42 | - curve25519-sha256@libssh.org 43 | - diffie-hellman-group-exchange-sha256 44 | 45 | ssh_kex_80_default: 46 | - sntrup4591761x25519-sha512@tinyssh.org 47 | - curve25519-sha256@libssh.org 48 | - diffie-hellman-group-exchange-sha256 49 | 50 | ssh_kex_85_default: 51 | - sntrup761x25519-sha512@openssh.com 52 | - curve25519-sha256@libssh.org 53 | - diffie-hellman-group-exchange-sha256 54 | -------------------------------------------------------------------------------- /.github/workflows/galaxy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Publish collection to Ansible Galaxy 3 | 4 | on: 5 | release: 6 | types: 7 | - published 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | # deploy the collection first, because if it fails, we don't want 16 | # to update the galaxy.yml 17 | - name: Deploy the collection 18 | uses: artis3n/ansible_galaxy_collection@v2 19 | with: 20 | api_key: ${{ secrets.GALAXY_API_KEY }} 21 | galaxy_version: ${{ github.event.release.tag_name }} 22 | 23 | # checkout master instead of the release-tag so we can push the galaxy.yml 24 | - uses: actions/checkout@v2 25 | with: 26 | ref: master 27 | 28 | - name: update galaxy.yml with new version 29 | uses: microsoft/variable-substitution@v1 30 | with: 31 | files: 'galaxy.yml' 32 | env: 33 | version: "${{ github.event.release.tag_name }}" 34 | 35 | - name: push galaxy.yml 36 | uses: github-actions-x/commit@v2.7 37 | with: 38 | github-token: ${{ secrets.GITHUB_TOKEN }} 39 | push-branch: 'master' 40 | commit-message: 'update galaxy.yml with new version' 41 | force-add: 'true' 42 | files: galaxy.yml 43 | name: dev-sec CI 44 | email: hello@dev-sec.io 45 | -------------------------------------------------------------------------------- /molecule/mysql_hardening/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: wrapper playbook for kitchen testing "ansible-mysql-hardening" 3 | hosts: all 4 | become: true 5 | environment: 6 | http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" 7 | https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" 8 | no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" 9 | tasks: 10 | - name: Determine required MySQL Python libraries (Ubuntu Focal Fossa ++) 11 | set_fact: 12 | mysql_python_package_debian: "python3-pymysql" 13 | when: 14 | - mysql_python_package_debian is not defined 15 | - ansible_distribution == "Ubuntu" 16 | - ansible_distribution_major_version|int > 19 17 | 18 | - name: Determine required MySQL Python libraries. 19 | set_fact: 20 | mysql_python_package_debian: "{% if 'python3' in ansible_python_interpreter|default('') %}python3-mysqldb{% else %}python-mysqldb{% endif %}" 21 | when: 22 | - mysql_python_package_debian is not defined 23 | - ansible_distribution != "Ubuntu" 24 | - ansible_distribution_major_version|int < 20 25 | - include_role: 26 | name: mysql_hardening 27 | vars: 28 | overwrite_global_mycnf: false 29 | mysql_root_password: iloverandompasswordsbutthiswilldo 30 | mysql_user_password: iloverandompasswordsbutthiswilldo 31 | mysql_config_file: /etc/mysql/mariadb.cnf 32 | mysql_root_password_update: true 33 | -------------------------------------------------------------------------------- /roles/os_hardening/templates/etc/sysconfig/rhel_sysconfig_init.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | # color => new RH6.0 bootup 5 | # verbose => old-style bootup 6 | # anything else => new style bootup without ANSI colors or positioning 7 | BOOTUP=color 8 | # column to start "[ OK ]" label in 9 | RES_COL=60 10 | # terminal sequence to move to that column. You could change this 11 | # to something like "tput hpa ${RES_COL}" if your terminal supports it 12 | MOVE_TO_COL="echo -en \\033[${RES_COL}G" 13 | # terminal sequence to set color to a 'success' color (currently: green) 14 | SETCOLOR_SUCCESS="echo -en \\033[0;32m" 15 | # terminal sequence to set color to a 'failure' color (currently: red) 16 | SETCOLOR_FAILURE="echo -en \\033[0;31m" 17 | # terminal sequence to set color to a 'warning' color (currently: yellow) 18 | SETCOLOR_WARNING="echo -en \\033[0;33m" 19 | # terminal sequence to reset to the default color. 20 | SETCOLOR_NORMAL="echo -en \\033[0;39m" 21 | # Set to anything other than 'no' to allow hotkey interactive startup... 22 | PROMPT={{ 'yes' if (os_security_init_prompt|bool) else 'no' }} 23 | # Set to 'yes' to allow probing for devices with swap signatures 24 | AUTOSWAP=no 25 | # What ttys should gettys be started on? 26 | ACTIVE_CONSOLES=/dev/tty[1-6] 27 | # Set to '/sbin/sulogin' to prompt for password on single-user mode 28 | # Set to '/sbin/sushell' otherwise 29 | SINGLE={{ '/sbin/sulogin' if os_security_init_single else '/sbin/sushell' }} 30 | 31 | # NSA 2.2.4.1 Set Daemon umask 32 | umask 027 33 | -------------------------------------------------------------------------------- /molecule/os_hardening/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | options: 5 | role-file: molecule/os_hardening/requirements.yml 6 | driver: 7 | name: docker 8 | lint: | 9 | yamllint roles/os_hardening/ molecule/os_hardening/ .github/workflows/os_hardening.yml 10 | ansible-lint roles/os_hardening/ 11 | platforms: 12 | - name: instance 13 | image: "rndmh3ro/docker-${MOLECULE_DISTRO}-ansible:latest" 14 | command: ${MOLECULE_DOCKER_COMMAND:-/lib/systemd/systemd} 15 | volumes: 16 | - /sys/fs/cgroup:/sys/fs/cgroup:ro 17 | privileged: true 18 | tmpfs: 19 | - /tmp 20 | - /run 21 | capabilities: 22 | - SYS_ADMIN 23 | tty: true 24 | env: 25 | http_proxy: "${http_proxy}" 26 | https_proxy: "${https_proxy}" 27 | no_proxy: "${no_proxy}" 28 | container: docker 29 | provisioner: 30 | name: ansible 31 | config_options: 32 | defaults: 33 | interpreter_python: auto_silent 34 | callback_whitelist: profile_tasks, timer, yaml 35 | verifier: 36 | name: ansible 37 | 38 | scenario: 39 | create_sequence: 40 | - dependency 41 | - create 42 | - prepare 43 | check_sequence: 44 | - dependency 45 | - destroy 46 | - create 47 | - prepare 48 | - converge 49 | - check 50 | - destroy 51 | converge_sequence: 52 | - dependency 53 | - create 54 | - prepare 55 | - converge 56 | destroy_sequence: 57 | - destroy 58 | test_sequence: 59 | - dependency 60 | - lint 61 | - destroy 62 | - syntax 63 | - create 64 | - prepare 65 | - converge 66 | - idempotence 67 | - verify 68 | - destroy 69 | -------------------------------------------------------------------------------- /molecule/ssh_hardening/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | options: 5 | role-file: molecule/ssh_hardening/requirements.yml 6 | driver: 7 | name: docker 8 | lint: | 9 | yamllint roles/ssh_hardening/ molecule/ssh_hardening/ .github/workflows/ssh_hardening.yml 10 | ansible-lint roles/ssh_hardening/ 11 | platforms: 12 | - name: instance 13 | image: "rndmh3ro/docker-${MOLECULE_DISTRO}-ansible:latest" 14 | command: ${MOLECULE_DOCKER_COMMAND:-/lib/systemd/systemd} 15 | volumes: 16 | - /sys/fs/cgroup:/sys/fs/cgroup:ro 17 | privileged: true 18 | tmpfs: 19 | - /tmp 20 | - /run 21 | capabilities: 22 | - SYS_ADMIN 23 | tty: true 24 | env: 25 | http_proxy: "${http_proxy}" 26 | https_proxy: "${https_proxy}" 27 | no_proxy: "${no_proxy}" 28 | container: docker 29 | provisioner: 30 | name: ansible 31 | config_options: 32 | defaults: 33 | interpreter_python: auto_silent 34 | callback_whitelist: profile_tasks, timer, yaml 35 | verifier: 36 | name: ansible 37 | 38 | scenario: 39 | create_sequence: 40 | - dependency 41 | - create 42 | - prepare 43 | check_sequence: 44 | - dependency 45 | - destroy 46 | - create 47 | - prepare 48 | - converge 49 | - check 50 | - destroy 51 | converge_sequence: 52 | - dependency 53 | - create 54 | - prepare 55 | - converge 56 | destroy_sequence: 57 | - destroy 58 | test_sequence: 59 | - dependency 60 | - lint 61 | - destroy 62 | - syntax 63 | - create 64 | - prepare 65 | - converge 66 | - idempotence 67 | - verify 68 | - destroy 69 | -------------------------------------------------------------------------------- /molecule/nginx_hardening/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | options: 5 | role-file: molecule/nginx_hardening/requirements.yml 6 | driver: 7 | name: docker 8 | lint: | 9 | yamllint roles/nginx_hardening/ molecule/nginx_hardening/ .github/workflows/nginx_hardening.yml 10 | ansible-lint roles/nginx_hardening/ 11 | platforms: 12 | - name: instance 13 | image: "rndmh3ro/docker-${MOLECULE_DISTRO}-ansible:latest" 14 | command: ${MOLECULE_DOCKER_COMMAND:-/lib/systemd/systemd} 15 | volumes: 16 | - /sys/fs/cgroup:/sys/fs/cgroup:ro 17 | privileged: true 18 | tmpfs: 19 | - /tmp 20 | - /run 21 | capabilities: 22 | - SYS_ADMIN 23 | tty: true 24 | env: 25 | http_proxy: "${http_proxy}" 26 | https_proxy: "${https_proxy}" 27 | no_proxy: "${no_proxy}" 28 | container: docker 29 | provisioner: 30 | name: ansible 31 | config_options: 32 | defaults: 33 | interpreter_python: auto_silent 34 | callback_whitelist: profile_tasks, timer, yaml 35 | verifier: 36 | name: ansible 37 | 38 | scenario: 39 | create_sequence: 40 | - dependency 41 | - create 42 | - prepare 43 | check_sequence: 44 | - dependency 45 | - destroy 46 | - create 47 | - prepare 48 | - converge 49 | - check 50 | - destroy 51 | converge_sequence: 52 | - dependency 53 | - create 54 | - prepare 55 | - converge 56 | destroy_sequence: 57 | - destroy 58 | test_sequence: 59 | - dependency 60 | - lint 61 | - destroy 62 | - syntax 63 | - create 64 | - prepare 65 | - converge 66 | - idempotence 67 | - verify 68 | - destroy 69 | -------------------------------------------------------------------------------- /roles/ssh_hardening/tasks/crypto_hostkeys.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Replace default 2048 bits RSA keypair 3 | community.crypto.openssh_keypair: 4 | state: present 5 | type: rsa 6 | size: "{{ ssh_host_rsa_key_size }}" 7 | path: "{{ ssh_host_keys_dir }}/ssh_host_rsa_key" 8 | force: false 9 | regenerate: partial_idempotence 10 | 11 | # In RHEL and Fedora, the 'ssh_keys' group is the group owner of the host private SSH keys. 12 | # Since the openssh_keypair module needs to read the key to provide idempotency, we need to set ownership and group based on specific OS vars. 13 | - name: Change host private key ownership, group and permissions 14 | file: 15 | path: "{{ ssh_host_keys_dir }}/ssh_host_rsa_key" 16 | owner: "{{ ssh_host_keys_owner }}" 17 | group: "{{ ssh_host_keys_group }}" 18 | mode: "0640" 19 | when: "ansible_facts.os_family == 'RedHat'" 20 | 21 | - name: Set hostkeys according to openssh-version if openssh >= 5.3 22 | set_fact: 23 | ssh_host_key_files: 24 | - "{{ ssh_host_keys_dir }}/ssh_host_rsa_key" 25 | when: sshd_version is version('5.3', '>=') 26 | 27 | - name: Set hostkeys according to openssh-version if openssh >= 6.0 28 | set_fact: 29 | ssh_host_key_files: 30 | - "{{ ssh_host_keys_dir }}/ssh_host_rsa_key" 31 | - "{{ ssh_host_keys_dir }}/ssh_host_ecdsa_key" 32 | when: sshd_version is version('6.0', '>=') 33 | 34 | - name: Set hostkeys according to openssh-version if openssh >= 6.3 35 | set_fact: 36 | ssh_host_key_files: 37 | - "{{ ssh_host_keys_dir }}/ssh_host_rsa_key" 38 | - "{{ ssh_host_keys_dir }}/ssh_host_ecdsa_key" 39 | - "{{ ssh_host_keys_dir }}/ssh_host_ed25519_key" 40 | when: sshd_version is version('6.3', '>=') 41 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/yum.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Remove unused repositories 3 | file: 4 | name: '/etc/yum.repos.d/{{ item }}.repo' 5 | state: 'absent' 6 | loop: 7 | - 'CentOS-Debuginfo' 8 | - 'CentOS-Media' 9 | - 'CentOS-Vault' 10 | when: os_security_packages_clean | bool 11 | 12 | - name: Get yum-repository-files 13 | find: 14 | paths: '/etc/yum.repos.d' 15 | patterns: '*.repo' 16 | register: yum_repos 17 | 18 | # for the 'default([])' see here: 19 | # https://github.com/dev-sec/ansible-os-hardening/issues/99 and 20 | # https://stackoverflow.com/questions/37067827/ansible-deprecation-warning-for-undefined-variable-despite-when-clause 21 | - name: Activate gpg-check for yum-repository-files 22 | replace: 23 | path: '{{ item.path }}' 24 | regexp: '^\s*gpgcheck.*' 25 | replace: 'gpgcheck=1' 26 | mode: '0644' 27 | with_items: 28 | - '{{ yum_repos.files | default([]) }}' 29 | 30 | # failed_when is needed because by default replace module will fail if the file doesn't exists. 31 | # status.rc is only defined if an error accrued and only error code (rc) 257 will be ignored. 32 | # All other errors will still be raised. 33 | - name: Activate gpg-check for config files 34 | replace: 35 | path: '{{ item }}' 36 | regexp: '^\s*gpgcheck\W.*' 37 | replace: 'gpgcheck=1' 38 | mode: '0644' 39 | register: status 40 | failed_when: status.rc is defined and status.rc != 257 41 | loop: 42 | - '/etc/yum.conf' 43 | - '/etc/dnf/dnf.conf' 44 | - '/etc/yum/pluginconf.d/rhnplugin.conf' 45 | 46 | - name: Remove deprecated or insecure packages | package-01 - package-09 47 | yum: 48 | name: '{{ os_security_packages_list }}' 49 | state: 'absent' 50 | when: os_security_packages_clean | bool 51 | -------------------------------------------------------------------------------- /roles/mysql_hardening/tasks/configure.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Protect my.cnf 3 | file: 4 | path: '{{ mysql_hardening_mysql_conf_file }}' 5 | mode: '0640' 6 | owner: '{{ mysql_cnf_owner }}' 7 | group: '{{ mysql_cnf_group }}' 8 | follow: true 9 | state: file 10 | 11 | - name: Ensure permissions on mysql-datadir are correct 12 | file: 13 | path: '{{ mysql_datadir }}' 14 | state: directory 15 | owner: '{{ mysql_hardening_user }}' 16 | group: '{{ mysql_hardening_user }}' 17 | mode: '0750' 18 | 19 | - name: Ensure permissions on mysql-logfile are correct 20 | file: 21 | path: '{{ mysql_hardening_log_file }}' 22 | state: file 23 | owner: '{{ mysql_hardening_user }}' 24 | group: '{{ mysql_hardening_group }}' 25 | mode: '0640' 26 | 27 | - name: Check mysql configuration-directory exists and has right permissions 28 | file: 29 | path: '{{ mysql_hardening_mysql_confd_dir }}' 30 | state: directory 31 | owner: '{{ mysql_hardening_user }}' 32 | group: '{{ mysql_hardening_group }}' 33 | mode: '0750' 34 | 35 | - name: Check include-dir directive is present in my.cnf 36 | lineinfile: 37 | dest: '{{ mysql_hardening_mysql_conf_file }}' 38 | line: '!includedir {{ mysql_hardening_mysql_confd_dir }}' 39 | insertafter: 'EOF' 40 | state: present 41 | backup: true 42 | notify: Restart mysql 43 | 44 | - name: Apply hardening configuration 45 | template: 46 | src: 'hardening.cnf.j2' 47 | dest: '{{ mysql_hardening_mysql_hardening_conf_file }}' 48 | owner: '{{ mysql_cnf_owner }}' 49 | group: '{{ mysql_cnf_group }}' 50 | mode: '0640' 51 | notify: Restart mysql 52 | 53 | - name: Enable mysql 54 | service: 55 | name: '{{ mysql_daemon }}' 56 | enabled: '{{ mysql_daemon_enabled }}' 57 | -------------------------------------------------------------------------------- /molecule/mysql_hardening/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | options: 5 | role-file: molecule/mysql_hardening/requirements.yml 6 | requirements-file: molecule/mysql_hardening/requirements.yml 7 | driver: 8 | name: docker 9 | lint: | 10 | yamllint roles/mysql_hardening/ molecule/mysql_hardening/ .github/workflows/mysql_hardening.yml 11 | ansible-lint roles/mysql_hardening/ 12 | platforms: 13 | - name: instance 14 | image: "rndmh3ro/docker-${MOLECULE_DISTRO}-ansible:latest" 15 | command: ${MOLECULE_DOCKER_COMMAND:-/lib/systemd/systemd} 16 | volumes: 17 | - /sys/fs/cgroup:/sys/fs/cgroup:ro 18 | privileged: true 19 | pre_build_image: true 20 | environment: 21 | container: docker 22 | security_opts: 23 | - apparmor=unconfined 24 | env: 25 | http_proxy: "${http_proxy}" 26 | https_proxy: "${https_proxy}" 27 | no_proxy: "${no_proxy}" 28 | container: docker 29 | provisioner: 30 | name: ansible 31 | config_options: 32 | defaults: 33 | interpreter_python: auto_silent 34 | callback_whitelist: profile_tasks, timer, yaml 35 | verifier: 36 | name: ansible 37 | 38 | scenario: 39 | create_sequence: 40 | - dependency 41 | - create 42 | - prepare 43 | check_sequence: 44 | - dependency 45 | - destroy 46 | - create 47 | - prepare 48 | - converge 49 | - check 50 | - destroy 51 | converge_sequence: 52 | - dependency 53 | - create 54 | - prepare 55 | - converge 56 | destroy_sequence: 57 | - destroy 58 | test_sequence: 59 | - dependency 60 | - lint 61 | - destroy 62 | - syntax 63 | - create 64 | - prepare 65 | - converge 66 | # - idempotence # not idempotent 67 | - verify 68 | - destroy 69 | -------------------------------------------------------------------------------- /molecule/os_hardening/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: wrapper playbook for kitchen testing "ansible-os-hardening" with custom vars for testing 3 | hosts: all 4 | become: true 5 | collections: 6 | - devsec.hardening 7 | environment: 8 | http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" 9 | https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" 10 | no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" 11 | tasks: 12 | - name: set ansible_python_interpreter to "/usr/bin/python3" on fedora 13 | set_fact: 14 | ansible_python_interpreter: "/usr/bin/python3" 15 | when: ansible_facts.distribution == 'Fedora' 16 | 17 | - name: Run the equivalent of "apt-get update && apt-get upgrade" 18 | apt: 19 | name: "*" 20 | state: latest 21 | update_cache: true 22 | when: ansible_os_family == 'Debian' 23 | 24 | - name: install required tools on SuSE 25 | # cannot use zypper module, since it depends on python-xml 26 | shell: "zypper -n install python-xml" 27 | when: ansible_facts.os_family == 'Suse' 28 | 29 | - name: install required tools on fedora 30 | dnf: 31 | name: 32 | - python 33 | - findutils 34 | - procps-ng 35 | when: ansible_facts.distribution == 'Fedora' 36 | 37 | - name: install packages 38 | pacman: 39 | name: 40 | - "awk" 41 | state: present 42 | update_cache: true 43 | ignore_errors: true 44 | 45 | - name: install required tools on RHEL 46 | yum: 47 | name: 48 | - openssh-clients 49 | - openssh 50 | state: present 51 | update_cache: true 52 | ignore_errors: true 53 | 54 | - name: create recursing symlink to test minimize access 55 | shell: "rm -f /usr/bin/zzz && ln -s /usr/bin /usr/bin/zzz" 56 | changed_when: false 57 | -------------------------------------------------------------------------------- /molecule/os_hardening/verify_pam.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: download pam-tester 3 | get_url: 4 | url: https://github.com/schurzi/pam-tester/releases/download/latest/pam-tester 5 | dest: /bin/pam-tester 6 | mode: 0555 7 | 8 | - name: set password for test 9 | set_fact: 10 | test_pw: "myTest!pw" 11 | 12 | - name: set locale for test 13 | set_fact: 14 | locale: "en_US.UTF-8" 15 | when: 16 | - ansible_facts.os_family == 'RedHat' 17 | - ansible_facts.distribution_major_version < '8' 18 | 19 | - name: create testuser 20 | user: 21 | name: testuser 22 | password: "{{ test_pw | password_hash('sha512') }}" 23 | 24 | - name: check successfull login with correct password 25 | shell: 26 | cmd: "pam-tester --user testuser --password {{ test_pw }}" 27 | environment: 28 | TMPDIR: /var/tmp 29 | LC_ALL: "{{ locale | default('C.UTF-8') }}" 30 | LANG: "{{ locale | default('C.UTF-8') }}" 31 | 32 | - name: check unsuccessfull login with incorrect password 33 | shell: 34 | cmd: "pam-tester --user testuser --password {{ test_pw }}fail --expectfail" 35 | environment: 36 | TMPDIR: /var/tmp 37 | LC_ALL: "{{ locale | default('C.UTF-8') }}" 38 | LANG: "{{ locale | default('C.UTF-8') }}" 39 | with_sequence: count=6 40 | 41 | - name: check unsuccessfull login, with correct password (lockout) 42 | shell: 43 | cmd: "pam-tester --user testuser --password {{ test_pw }} --expectfail" 44 | environment: 45 | TMPDIR: /var/tmp 46 | LC_ALL: "{{ locale | default('C.UTF-8') }}" 47 | LANG: "{{ locale | default('C.UTF-8') }}" 48 | 49 | - name: wait for account to unlock 50 | pause: 51 | seconds: 20 52 | 53 | - name: check successfull login 54 | shell: 55 | cmd: "pam-tester --user testuser --password {{ test_pw }}" 56 | environment: 57 | TMPDIR: /var/tmp 58 | LC_ALL: "{{ locale | default('C.UTF-8') }}" 59 | LANG: "{{ locale | default('C.UTF-8') }}" 60 | -------------------------------------------------------------------------------- /molecule/ssh_hardening/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: all 4 | become: true 5 | environment: 6 | http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" 7 | https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" 8 | no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" 9 | roles: 10 | - geerlingguy.git 11 | tasks: 12 | - name: install fake SuSE-release for cinc compatibility 13 | copy: 14 | content: | 15 | openSUSE Faked Enterprise 2020 (x86_64) 16 | VERSION = 2020 17 | CODENAME = Faked Feature 18 | dest: /etc/SuSE-release 19 | owner: root 20 | group: root 21 | mode: '0444' 22 | when: ansible_facts.os_family == 'Suse' 23 | 24 | - name: install git for SuSE since geerlinguy.git does not support it 25 | zypper: 26 | name: git 27 | state: present 28 | when: ansible_facts.os_family == 'Suse' 29 | 30 | - name: install crypto compat modules on fedora 31 | dnf: 32 | name: libxcrypt-compat 33 | when: ansible_facts.distribution == 'Fedora' 34 | 35 | - name: download cinc-auditor 36 | get_url: 37 | url: https://omnitruck.cinc.sh/install.sh 38 | dest: /tmp/install.sh 39 | mode: '0775' 40 | 41 | - name: install cinc-auditor 42 | shell: "bash /tmp/install.sh -s -- -P cinc-auditor -v 4" 43 | 44 | - name: Execute cinc-auditor tests 45 | command: "/opt/cinc-auditor/bin/cinc-auditor exec --no-show-progress --no-color --no-distinct-exit supermarket://dev-sec/nginx-baseline" 46 | register: test_results 47 | changed_when: false 48 | ignore_errors: true 49 | 50 | - name: Display details about the cinc-auditor results 51 | debug: 52 | msg: "{{ test_results.stdout_lines }}" 53 | 54 | - name: Fail when tests fail 55 | fail: 56 | msg: "Inspec failed to validate" 57 | when: test_results.rc != 0 58 | -------------------------------------------------------------------------------- /molecule/mysql_hardening/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: all 4 | become: true 5 | environment: 6 | http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" 7 | https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" 8 | no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" 9 | roles: 10 | - geerlingguy.git 11 | tasks: 12 | - name: install fake SuSE-release for cinc compatibility 13 | copy: 14 | content: | 15 | openSUSE Faked Enterprise 2020 (x86_64) 16 | VERSION = 2020 17 | CODENAME = Faked Feature 18 | dest: /etc/SuSE-release 19 | owner: root 20 | group: root 21 | mode: '0444' 22 | when: ansible_facts.os_family == 'Suse' 23 | 24 | - name: install git for SuSE since geerlinguy.git does not support it 25 | zypper: 26 | name: git 27 | state: present 28 | when: ansible_facts.os_family == 'Suse' 29 | 30 | - name: install procps for debian systems 31 | apt: 32 | name: procps 33 | state: present 34 | update_cache: true 35 | when: ansible_distribution == 'Debian' 36 | 37 | - name: download cinc-auditor 38 | get_url: 39 | url: https://omnitruck.cinc.sh/install.sh 40 | dest: /tmp/install.sh 41 | mode: '0775' 42 | 43 | - name: install cinc-auditor 44 | shell: "bash /tmp/install.sh -s -- -P cinc-auditor -v 4" 45 | 46 | - name: Execute cinc-auditor tests 47 | command: "/opt/cinc-auditor/bin/cinc-auditor exec --no-show-progress --no-color --no-distinct-exit supermarket://dev-sec/mysql-baseline" 48 | register: test_results 49 | changed_when: false 50 | ignore_errors: true 51 | 52 | - name: Display details about the cinc-auditor results 53 | debug: 54 | msg: "{{ test_results.stdout_lines }}" 55 | 56 | - name: Fail when tests fail 57 | fail: 58 | msg: "Inspec failed to validate" 59 | when: test_results.rc != 0 60 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/minimize_access.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # If the find-task throws an error on /usr/bin/X11 like "File system loop detected" 3 | # the other files inside /usr/bin (and all other directories) are 4 | # still getting found and the permissions minimized in the next task. 5 | # This is also the reason why there's ignore_errors: true on the task. 6 | # also see: https://github.com/dev-sec/ansible-os-hardening/issues/219 7 | - name: Find files with write-permissions for group 8 | shell: "find -L {{ item }} -perm /go+w -type f" # noqa command-instead-of-shell 9 | with_flattened: 10 | - '/usr/local/sbin' 11 | - '/usr/local/bin' 12 | - '/usr/sbin' 13 | - '/usr/bin' 14 | - '/sbin' 15 | - '/bin' 16 | - "{{ os_env_extra_user_paths }}" # noqa deprecated-bare-vars 17 | register: minimize_access_directories 18 | ignore_errors: true 19 | changed_when: false 20 | 21 | - name: Minimize access on found files 22 | file: 23 | path: '{{ item.1 }}' 24 | mode: 'go-w' 25 | state: file 26 | with_subelements: 27 | - "{{ minimize_access_directories.results }}" 28 | - stdout_lines 29 | 30 | - name: Change shadow ownership to root and mode to 0600 | os-02 31 | file: 32 | dest: '/etc/shadow' 33 | owner: '{{ os_shadow_perms.owner }}' 34 | group: '{{ os_shadow_perms.group }}' 35 | mode: '{{ os_shadow_perms.mode }}' 36 | 37 | - name: Change passwd ownership to root and mode to 0644 | os-03 38 | file: 39 | dest: '/etc/passwd' 40 | owner: '{{ os_passwd_perms.owner }}' 41 | group: '{{ os_passwd_perms.group }}' 42 | mode: '{{ os_passwd_perms.mode }}' 43 | 44 | - name: Change su-binary to only be accessible to user and group root 45 | file: 46 | dest: '/bin/su' 47 | owner: 'root' 48 | group: 'root' 49 | mode: '0750' 50 | when: '"change_user" not in os_security_users_allow' 51 | 52 | - name: Set option hidepid for proc filesystem 53 | mount: 54 | path: /proc 55 | src: proc 56 | fstype: proc 57 | opts: '{{ proc_mnt_options }}' 58 | state: present 59 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/user_accounts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get UID_MIN from login.defs 3 | shell: awk '/^\s*UID_MIN\s*([0-9]*).*?$/ {print $2}' /etc/login.defs 4 | args: 5 | removes: /etc/login.defs 6 | register: uid_min 7 | check_mode: false 8 | changed_when: false 9 | 10 | - name: Calculate UID_MAX from UID_MIN by substracting 1 11 | set_fact: 12 | uid_max: '{{ uid_min.stdout | int - 1 }}' 13 | when: uid_min.stdout|int > 0 14 | 15 | - name: Set UID_MAX on Debian-systems if no login.defs exist 16 | set_fact: 17 | uid_max: '999' 18 | when: 19 | - ansible_facts.os_family == 'Debian' 20 | - uid_max is not defined 21 | 22 | - name: Set UID_MAX on other systems if no login.defs exist 23 | set_fact: 24 | uid_max: '499' 25 | when: uid_max is not defined 26 | 27 | - name: Get all system accounts 28 | command: awk -F'':'' '{ if ( $3 <= {{ uid_max|quote }} ) print $1}' /etc/passwd 29 | args: 30 | removes: /etc/passwd 31 | changed_when: false 32 | check_mode: false 33 | register: sys_accs 34 | 35 | - name: Remove always ignored system accounts from list 36 | set_fact: 37 | sys_accs_cond: '{{ sys_accs.stdout_lines | difference(os_always_ignore_users) }}' 38 | check_mode: false 39 | 40 | - name: Change system accounts not on the user provided ignore-list 41 | user: 42 | name: '{{ item }}' 43 | shell: '{{ os_nologin_shell_path }}' 44 | password: '*' 45 | createhome: false 46 | with_flattened: 47 | - '{{ sys_accs_cond | default([]) | difference(os_ignore_users) | list }}' 48 | 49 | - name: get all home directories in /home, but skip ignored users 50 | find: 51 | paths: /home/ 52 | recurse: false 53 | file_type: directory 54 | excludes: "{{ os_ignore_home_folder_users | join(',') }}" 55 | register: home_directories 56 | when: os_chmod_home_folders | bool 57 | 58 | - name: set ownership of /home directories to 0700 59 | file: 60 | mode: 0700 61 | path: "{{ item.path }}" 62 | state: directory 63 | loop: "{{ home_directories.files }}" 64 | when: os_chmod_home_folders | bool 65 | -------------------------------------------------------------------------------- /molecule/nginx_hardening/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: all 4 | become: true 5 | environment: 6 | http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" 7 | https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" 8 | no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" 9 | roles: 10 | - geerlingguy.git 11 | tasks: 12 | - name: install fake SuSE-release for cinc compatibility 13 | copy: 14 | content: | 15 | openSUSE Faked Enterprise 2020 (x86_64) 16 | VERSION = 2020 17 | CODENAME = Faked Feature 18 | dest: /etc/SuSE-release 19 | owner: root 20 | group: root 21 | mode: '0444' 22 | when: ansible_facts.os_family == 'Suse' 23 | 24 | - name: install git for SuSE since geerlinguy.git does not support it 25 | zypper: 26 | name: git 27 | state: present 28 | when: ansible_facts.os_family == 'Suse' 29 | 30 | - name: Run the equivalent of "apt-get update" as a separate step 31 | apt: 32 | update_cache: true 33 | when: ansible_facts.os_family == 'Debian' 34 | 35 | - name: install required tools on debian 36 | apt: 37 | name: procps 38 | when: ansible_facts.os_family == 'Debian' 39 | 40 | - name: download cinc-auditor 41 | get_url: 42 | url: https://omnitruck.cinc.sh/install.sh 43 | dest: /tmp/install.sh 44 | mode: '0775' 45 | 46 | - name: install cinc-auditor 47 | shell: "bash /tmp/install.sh -s -- -P cinc-auditor -v 4" 48 | 49 | - name: Execute cinc-auditor tests 50 | command: "/opt/cinc-auditor/bin/cinc-auditor exec --no-show-progress --no-color --no-distinct-exit supermarket://dev-sec/nginx-baseline" 51 | register: test_results 52 | changed_when: false 53 | ignore_errors: true 54 | 55 | - name: Display details about the cinc-auditor results 56 | debug: 57 | msg: "{{ test_results.stdout_lines }}" 58 | 59 | - name: Fail when tests fail 60 | fail: 61 | msg: "Inspec failed to validate" 62 | when: test_results.rc != 0 63 | -------------------------------------------------------------------------------- /.github/workflows/nginx_hardening.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "devsec.nginx_hardening" 3 | on: # yamllint disable-line rule:truthy 4 | workflow_dispatch: 5 | push: 6 | paths: 7 | - 'roles/nginx_hardening/**' 8 | - 'molecule/nginx_hardening/**' 9 | - '.github/workflows/nginx_hardening.yml' 10 | pull_request: 11 | paths: 12 | - 'roles/nginx_hardening/**' 13 | - 'molecule/nginx_hardening/**' 14 | - '.github/workflows/nginx_hardening.yml' 15 | jobs: 16 | build: 17 | runs-on: ubuntu-18.04 18 | env: 19 | PY_COLORS: 1 20 | ANSIBLE_FORCE_COLOR: 1 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | molecule_distro: 25 | - centos7 26 | - centos8 27 | - rocky8 28 | - ubuntu1804 29 | - ubuntu2004 30 | - debian9 31 | - debian10 32 | - amazon 33 | # - arch # needs to be fixed 34 | # - opensuse_tumbleweed # needs to be fixed 35 | # - fedora # no support from geerlingguy role 36 | steps: 37 | - name: Checkout repo 38 | uses: actions/checkout@v2 39 | with: 40 | path: ansible_collections/devsec/hardening 41 | submodules: true 42 | 43 | - name: Set up Python 3.7 44 | uses: actions/setup-python@v1 45 | with: 46 | python-version: 3.7 47 | 48 | - name: Install dependencies 49 | run: | 50 | sudo apt install git 51 | python -m pip install --no-cache-dir --upgrade pip 52 | pip install -r requirements.txt 53 | working-directory: ansible_collections/devsec/hardening 54 | 55 | - name: Create default collection path symlink 56 | run: | 57 | mkdir -p /home/runner/.ansible 58 | ln -s /home/runner/work/ansible-os-hardening/ansible-os-hardening /home/runner/.ansible/collections 59 | 60 | - name: Test with molecule 61 | run: | 62 | molecule --version 63 | molecule test -s nginx_hardening 64 | env: 65 | MOLECULE_DISTRO: ${{ matrix.molecule_distro }} 66 | working-directory: ansible_collections/devsec/hardening 67 | -------------------------------------------------------------------------------- /.github/workflows/os_hardening.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "devsec.os_hardening" 3 | on: # yamllint disable-line rule:truthy 4 | workflow_dispatch: 5 | push: 6 | paths: 7 | - 'roles/os_hardening/**' 8 | - 'molecule/os_hardening/**' 9 | - '.github/workflows/os_hardening.yml' 10 | pull_request: 11 | paths: 12 | - 'roles/os_hardening/**' 13 | - 'molecule/os_hardening/**' 14 | - '.github/workflows/os_hardening.yml' 15 | jobs: 16 | build: 17 | runs-on: ubuntu-18.04 18 | env: 19 | PY_COLORS: 1 20 | ANSIBLE_FORCE_COLOR: 1 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | molecule_distro: 25 | - centos7 26 | - centos8 27 | - rocky8 28 | - ubuntu1804 29 | - ubuntu2004 30 | - debian9 31 | - debian10 32 | - amazon 33 | - opensuse_tumbleweed 34 | # - arch # needs to be fixed 35 | steps: 36 | - name: Checkout repo 37 | uses: actions/checkout@v2 38 | with: 39 | path: ansible_collections/devsec/hardening 40 | submodules: true 41 | 42 | - name: Set up Python 3.7 43 | uses: actions/setup-python@v1 44 | with: 45 | python-version: 3.7 46 | 47 | - name: Install dependencies 48 | run: | 49 | sudo apt install git 50 | python -m pip install --no-cache-dir --upgrade pip 51 | pip install -r requirements.txt 52 | working-directory: ansible_collections/devsec/hardening 53 | 54 | - name: Create default collection path symlink 55 | run: | 56 | mkdir -p /home/runner/.ansible 57 | ln -s /home/runner/work/ansible-os-hardening/ansible-os-hardening /home/runner/.ansible/collections 58 | 59 | - name: Test with molecule 60 | run: | 61 | if [ "$MOLECULE_DISTRO" = "opensuse_tumbleweed" ]; then 62 | export MOLECULE_DOCKER_COMMAND="/usr/lib/systemd/systemd" 63 | fi 64 | molecule --version 65 | molecule test -s os_hardening 66 | env: 67 | MOLECULE_DISTRO: ${{ matrix.molecule_distro }} 68 | working-directory: ansible_collections/devsec/hardening 69 | -------------------------------------------------------------------------------- /molecule/mysql_hardening/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: wrapper playbook for kitchen testing "ansible-mysql-hardening" 4 | hosts: all 5 | become: true 6 | environment: 7 | http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" 8 | https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" 9 | no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" 10 | tasks: 11 | - name: Run the equivalent of "apt-get update && apt-get upgrade" 12 | apt: 13 | name: "*" 14 | state: latest 15 | update_cache: true 16 | when: ansible_os_family == 'Debian' 17 | 18 | - name: install required tools on SuSE 19 | zypper: 20 | name: "python-xml" 21 | state: present 22 | when: ansible_facts.os_family == 'Suse' 23 | 24 | - name: create missing directory 25 | file: 26 | path: "/etc/mysql/conf.d" 27 | state: directory 28 | 29 | - name: Determine required MySQL Python libraries (Ubuntu Focal Fossa ++) 30 | set_fact: 31 | mysql_python_package_debian: "python3-pymysql" 32 | when: 33 | - mysql_python_package_debian is not defined 34 | - ansible_distribution == "Ubuntu" 35 | - ansible_distribution_major_version|int > 19 36 | 37 | - name: Determine required MySQL Python libraries. 38 | set_fact: 39 | mysql_python_package_debian: "{% if 'python3' in ansible_python_interpreter|default('') %}python3-mysqldb{% else %}python-mysqldb{% endif %}" 40 | when: 41 | - mysql_python_package_debian is not defined 42 | - ansible_distribution != "Ubuntu" 43 | - ansible_distribution_major_version|int < 20 44 | 45 | - include_role: 46 | name: dev-sec.mysql 47 | 48 | - name: create a user with an empty password 49 | community.mysql.mysql_query: 50 | query: 51 | - "CREATE USER foo@bar;" 52 | login_unix_socket: "{{ login_unix_socket | default(omit) }}" 53 | vars: 54 | overwrite_global_mycnf: false 55 | mysql_root_password: iloverandompasswordsbutthiswilldo 56 | mysql_user_password: iloverandompasswordsbutthiswilldo 57 | mysql_config_file: /etc/mysql/mariadb.cnf 58 | mysql_root_password_update: true 59 | -------------------------------------------------------------------------------- /roles/os_hardening/templates/etc/initramfs-tools/modules.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | # This file contains the names of kernel modules that should be loaded at boot time, one per line. Lines beginning with "#" are ignored. 5 | # 6 | # A list of all available kernel modules kann be found with `find /lib/modules/$(uname -r)/kernel/` 7 | # We will sort by folder. 8 | 9 | 10 | # Arch 11 | # ---- 12 | # 13 | # Modules for certains builds, contains support modules and some CPU-specific optimizations. 14 | 15 | {% if ansible_facts.architecture == 'x86_64' %} 16 | # Optimize for x86_64 cryptographic features 17 | twofish-x86_64-3way 18 | twofish-x86_64 19 | aes-x86_64 20 | salsa20-x86_64 21 | blowfish-x86_64 22 | {% endif %} 23 | 24 | {% if 'AuthenticAMD' in ansible_facts.processor %} 25 | # AMD-specific optimizations 26 | kvm-amd 27 | {% else %} 28 | # Intel-specific optimizations 29 | ghash-clmulni-intel 30 | aesni-intel 31 | kvm-intel 32 | {% endif %} 33 | 34 | kvm 35 | 36 | # Crypto 37 | # ------ 38 | 39 | # Some core modules which comprise strong cryptography. 40 | blowfish_common 41 | blowfish_generic 42 | ctr 43 | cts 44 | lrw 45 | lzo 46 | rmd160 47 | rmd256 48 | rmd320 49 | serpent 50 | sha512_generic 51 | twofish_common 52 | twofish_generic 53 | xts 54 | zlib 55 | 56 | 57 | # Drivers 58 | # ------- 59 | 60 | # Basics 61 | lp 62 | rtc 63 | loop 64 | 65 | # Filesystems 66 | ext2 67 | btrfs 68 | 69 | {% if os_desktop_enable %} 70 | # Desktop 71 | psmouse 72 | snd 73 | snd_ac97_codec 74 | snd_intel8x0 75 | snd_page_alloc 76 | snd_pcm 77 | snd_timer 78 | soundcore 79 | usbhid 80 | {% endif %} 81 | 82 | # Lib 83 | # --- 84 | xz 85 | 86 | 87 | # Net 88 | # --- 89 | 90 | # All packets needed for netfilter rules (ie iptables, ebtables). 91 | ip_tables 92 | x_tables 93 | iptable_filter 94 | iptable_nat 95 | 96 | # Targets 97 | ipt_LOG 98 | ipt_REJECT 99 | 100 | # Modules 101 | xt_connlimit 102 | xt_tcpudp 103 | xt_recent 104 | xt_limit 105 | xt_conntrack 106 | nf_conntrack 107 | nf_conntrack_ipv4 108 | nf_defrag_ipv4 109 | xt_state 110 | nf_nat 111 | 112 | # Addons 113 | xt_pknock 114 | -------------------------------------------------------------------------------- /roles/os_hardening/templates/etc/default/ufw.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | # /etc/default/ufw 5 | # 6 | 7 | # Set to yes to apply rules to support IPv6 (no means only IPv6 on loopback 8 | # accepted). You will need to 'disable' and then 'enable' the firewall for 9 | # the changes to take affect. 10 | IPV6={{ 'yes' if ufw_enable_ipv6 else 'no' }} 11 | 12 | # Set the default input policy to ACCEPT, DROP, or REJECT. Please note that if 13 | # you change this you will most likely want to adjust your rules. 14 | DEFAULT_INPUT_POLICY="{{ ufw_default_input_policy }}" 15 | 16 | # Set the default output policy to ACCEPT, DROP, or REJECT. Please note that if 17 | # you change this you will most likely want to adjust your rules. 18 | DEFAULT_OUTPUT_POLICY="{{ ufw_default_output_policy }}" 19 | 20 | # Set the default forward policy to ACCEPT, DROP or REJECT. Please note that 21 | # if you change this you will most likely want to adjust your rules 22 | DEFAULT_FORWARD_POLICY="{{ ufw_default_forward_policy }}" 23 | 24 | # Set the default application policy to ACCEPT, DROP, REJECT or SKIP. Please 25 | # note that setting this to ACCEPT may be a security risk. See 'man ufw' for 26 | # details 27 | DEFAULT_APPLICATION_POLICY="{{ ufw_default_application_policy }}" 28 | 29 | # By default, ufw only touches its own chains. Set this to 'yes' to have ufw 30 | # manage the built-in chains too. Warning: setting this to 'yes' will break 31 | # non-ufw managed firewall rules 32 | MANAGE_BUILTINS="{{ ufw_manage_builtins }}" 33 | 34 | # 35 | # IPT backend 36 | # 37 | # only enable if using iptables backend and want to overwrite /etc/sysctl.conf 38 | {% if ufw_ipt_sysctl == '' %}#{% endif %}IPT_SYSCTL={{ ufw_ipt_sysctl }} 39 | 40 | # Extra connection tracking modules to load. Complete list can be found in 41 | # net/netfilter/Kconfig of your kernel source. Some common modules: 42 | # nf_conntrack_irc, nf_nat_irc: DCC (Direct Client to Client) support 43 | # nf_conntrack_netbios_ns: NetBIOS (samba) client support 44 | # nf_conntrack_pptp, nf_nat_pptp: PPTP over stateful firewall/NAT 45 | # nf_conntrack_ftp, nf_nat_ftp: active FTP support 46 | # nf_conntrack_tftp, nf_nat_tftp: TFTP support (server side) 47 | IPT_MODULES="{{ ufw_ipt_modules }}" 48 | -------------------------------------------------------------------------------- /.github/workflows/ssh_hardening.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "devsec.ssh_hardening" 3 | on: # yamllint disable-line rule:truthy 4 | workflow_dispatch: 5 | push: 6 | paths: 7 | - 'roles/ssh_hardening/**' 8 | - 'molecule/ssh_hardening/**' 9 | - '.github/workflows/ssh_hardening.yml' 10 | pull_request: 11 | paths: 12 | - 'roles/ssh_hardening/**' 13 | - 'molecule/ssh_hardening/**' 14 | - '.github/workflows/ssh_hardening.yml' 15 | jobs: 16 | build: 17 | runs-on: ubuntu-18.04 18 | env: 19 | PY_COLORS: 1 20 | ANSIBLE_FORCE_COLOR: 1 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | molecule_distro: 25 | - centos7 26 | - centos8 27 | - rocky8 28 | - fedora 29 | - ubuntu1804 30 | - ubuntu2004 31 | - debian9 32 | - debian10 33 | - amazon 34 | # - arch # needs to be fixed 35 | # - opensuse_tumbleweed # baseline is not compatible with suse 36 | steps: 37 | - name: Checkout repo 38 | uses: actions/checkout@v2 39 | with: 40 | path: ansible_collections/devsec/hardening 41 | submodules: true 42 | 43 | - name: Set up Python 3.7 44 | uses: actions/setup-python@v1 45 | with: 46 | python-version: 3.7 47 | 48 | - name: Install dependencies 49 | run: | 50 | sudo apt install git 51 | python -m pip install --no-cache-dir --upgrade pip 52 | pip install -r requirements.txt 53 | working-directory: ansible_collections/devsec/hardening 54 | 55 | - name: Create default collection path symlink 56 | run: | 57 | mkdir -p /home/runner/.ansible 58 | ln -s /home/runner/work/ansible-os-hardening/ansible-os-hardening /home/runner/.ansible/collections 59 | 60 | - name: Test with molecule 61 | run: | 62 | if [ "$MOLECULE_DISTRO" = "opensuse_tumbleweed" ]; then 63 | export MOLECULE_DOCKER_COMMAND="/usr/lib/systemd/systemd" 64 | fi 65 | molecule --version 66 | molecule test -s ssh_hardening 67 | env: 68 | MOLECULE_DISTRO: ${{ matrix.molecule_distro }} 69 | working-directory: ansible_collections/devsec/hardening 70 | -------------------------------------------------------------------------------- /roles/mysql_hardening/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Fetch OS dependent variables 3 | include_vars: 4 | file: '{{ item }}' 5 | name: 'os_vars' 6 | with_first_found: 7 | - files: 8 | - '{{ ansible_facts.distribution }}_{{ ansible_facts.distribution_major_version }}.yml' 9 | - '{{ ansible_facts.distribution }}.yml' 10 | - '{{ ansible_facts.os_family }}_{{ ansible_facts.distribution_major_version }}.yml' 11 | - '{{ ansible_facts.os_family }}.yml' 12 | skip: true 13 | tags: always 14 | 15 | # we only override variables with our default if they have not been specified already. 16 | # by default the lookup functions finds all varnames containing the string, therefore 17 | # we add ^ and $ to denote start and end of string, so this returns only exact maches. 18 | - name: Set OS dependent variables, if not already defined by user 19 | set_fact: 20 | '{{ item.key }}': '{{ item.value }}' 21 | when: "not lookup('varnames', '^' + item.key + '$')" 22 | with_dict: '{{ os_vars }}' 23 | tags: always 24 | 25 | - name: Gather package facts to check for mysql/mariadb version 26 | ansible.builtin.package_facts: 27 | manager: auto 28 | 29 | - name: Check if MySQL or MariaDB is used 30 | set_fact: 31 | mysql_distribution: "{{ ansible_facts.packages['mysql-server'] is defined | ternary('mysql', 'mariadb') }}" 32 | 33 | - name: Check which MySQL/MariaDB version is used 34 | community.mysql.mysql_info: 35 | filter: version 36 | login_unix_socket: "{{ login_unix_socket | default(omit) }}" 37 | register: mysql_version 38 | 39 | # see https://stackoverflow.com/a/59451077/2953919 for the 40 | # dict2items and vice versa magic 41 | - name: Drop the secure-auth parameter on MySQL >=8.0.3 (not mariadb) 42 | set_fact: 43 | mysql_hardening_options: "{{ mysql_hardening_options| dict2items | rejectattr('key', 'search', 'secure-auth') | list | items2dict }}" 44 | when: 45 | - mysql_version.version.full is version('8.0.3', '>=') 46 | - mysql_distribution == "mysql" 47 | 48 | - include: configure.yml 49 | when: mysql_hardening_enabled | bool 50 | tags: 51 | - mysql_hardening 52 | 53 | - include: mysql_secure_installation.yml 54 | when: mysql_hardening_enabled | bool 55 | tags: 56 | - mysql_hardening 57 | - mysql_secure_installation 58 | -------------------------------------------------------------------------------- /roles/mysql_hardening/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # switcher to enable/disable role 3 | mysql_hardening_enabled: true 4 | 5 | mysql_daemon_enabled: true 6 | 7 | mysql_hardening_restart_mysql: true 8 | 9 | # general configuration 10 | mysql_datadir: '/var/lib/mysql' 11 | mysql_hardening_mysql_hardening_conf_file: '{{mysql_hardening_mysql_confd_dir}}/hardening.cnf' 12 | # You have to change this to your own strong enough mysql root password 13 | mysql_root_password: '-----====>SetR00tPa$$wordH3r3!!!<====-----' 14 | # There .my.cnf with mysql root credentials will be installed 15 | mysql_user_home: "{{ ansible_env.HOME}}" 16 | 17 | # ensure the following parameters are set properly 18 | mysql_remove_remote_root: true 19 | mysql_remove_anonymous_users: true 20 | mysql_remove_test_database: true 21 | 22 | # @see http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_skip-show-database 23 | mysql_hardening_skip_show_database: true 24 | 25 | # @see https://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_skip-grant-tables 26 | mysql_hardening_skip_grant_tables: false 27 | 28 | # @see http://www.symantec.com/connect/articles/securing-mysql-step-step 29 | # @see http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_chroot 30 | mysql_hardening_chroot: "" 31 | 32 | mysql_hardening_options: 33 | # @see http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_safe-user-create 34 | safe-user-create: 1 35 | 36 | # @see http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option-mysqld-secure-auth 37 | secure-auth: 1 38 | 39 | # @see http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option-mysqld-symbolic-links 40 | skip-symbolic-links: 1 41 | 42 | # @see http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar-local-infile 43 | local-infile: 0 44 | 45 | # @see https://dev.mysql.com/doc/refman/5.7/en/server-options.html#option-mysqld-allow-suspicious-udfs 46 | allow-suspicious-udfs: 0 47 | 48 | # @see https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar-automatic-sp-privileges 49 | automatic-sp-privileges: 0 50 | 51 | # @see https://dev.mysql.com/doc/refman/5.7/en/server-options.html#option-mysqld-secure-file-priv 52 | secure-file-priv: '/tmp' 53 | # @see https://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_user 54 | user: '{{mysql_hardening_user}}' 55 | -------------------------------------------------------------------------------- /molecule/ssh_hardening/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: wrapper playbook for kitchen testing "ansible-ssh-hardening" with default settings 3 | hosts: all 4 | become: true 5 | environment: 6 | http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" 7 | https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" 8 | no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" 9 | tasks: 10 | - name: use python3 11 | set_fact: 12 | ansible_python_interpreter: /usr/bin/python3 13 | when: ansible_facts.distribution == 'Fedora' 14 | 15 | - name: install packages 16 | yum: 17 | name: 18 | - openssh-clients 19 | - openssh-server 20 | - libselinux-python 21 | state: present 22 | update_cache: true 23 | ignore_errors: true 24 | 25 | - name: install packages 26 | dnf: 27 | name: 28 | - openssh-clients 29 | - openssh-server 30 | - procps-ng 31 | state: present 32 | update_cache: true 33 | ignore_errors: true 34 | 35 | - name: install packages 36 | apt: 37 | name: 38 | - openssh-client 39 | - openssh-server 40 | state: present 41 | update_cache: true 42 | ignore_errors: true 43 | 44 | - name: install required tools on SuSE 45 | # cannot use zypper module, since it depends on python-xml 46 | shell: "zypper -n install python-xml" 47 | when: ansible_facts.os_family == 'Suse' 48 | 49 | - name: install packages 50 | zypper: 51 | name: 52 | - "openssh" 53 | when: ansible_facts.os_family == 'Suse' 54 | 55 | - name: install packages 56 | pacman: 57 | name: 58 | - "openssh" 59 | - "awk" 60 | state: present 61 | update_cache: true 62 | ignore_errors: true 63 | 64 | - name: created needed directory 65 | file: 66 | path: "/var/run/sshd" 67 | state: directory 68 | 69 | - name: create ssh host keys 70 | command: "ssh-keygen -A" 71 | when: not ((ansible_facts.os_family in ['Oracle Linux', 'RedHat']) and ansible_facts.distribution_major_version < '7') or 72 | ansible_facts.distribution == "Fedora" or 73 | ansible_facts.distribution == "Amazon" or 74 | ansible_facts.os_family == "Suse" 75 | changed_when: false 76 | ignore_errors: true 77 | -------------------------------------------------------------------------------- /roles/ssh_hardening/tasks/selinux.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install selinux dependencies when selinux is installed 3 | package: 4 | name: '{{ ssh_selinux_packages }}' 5 | state: present 6 | 7 | - name: Authorize {{ ssh_server_ports }} ports for selinux 8 | seport: 9 | ports: '{{ item }}' 10 | proto: tcp 11 | setype: ssh_port_t 12 | state: present 13 | loop: '{{ ssh_server_ports }}' 14 | 15 | - name: Check if ssh_password module is already installed 16 | shell: 'set -o pipefail && semodule -l | grep ssh_password' 17 | args: 18 | executable: /bin/bash 19 | register: ssh_password_module 20 | failed_when: false 21 | changed_when: false 22 | check_mode: false 23 | 24 | # The following tasks only get executed when selinux is in state enforcing, 25 | # UsePam is 'no' and the ssh_password module is not installed. See this issue for 26 | # more info: https://github.com/hardening-io/ansible-ssh-hardening/issues/23 27 | - when: 28 | - not (ssh_use_pam | bool) 29 | - ('ssh_password' not in ssh_password_module.stdout) 30 | block: 31 | - name: Create selinux custom policy drop folder 32 | file: 33 | path: '{{ ssh_custom_selinux_dir }}' 34 | state: 'directory' 35 | owner: 'root' 36 | group: 'root' 37 | mode: '0750' 38 | 39 | - name: Distributing custom selinux policies 40 | copy: 41 | src: 'ssh_password' 42 | dest: '{{ ssh_custom_selinux_dir }}' 43 | owner: 'root' 44 | group: 'root' 45 | mode: '0600' 46 | 47 | - name: Check and compile policy 48 | command: checkmodule -M -m -o {{ ssh_custom_selinux_dir }}/ssh_password.mod {{ ssh_custom_selinux_dir }}/ssh_password 49 | 50 | - name: Create selinux policy module package 51 | command: semodule_package -o {{ ssh_custom_selinux_dir }}/ssh_password.pp -m {{ ssh_custom_selinux_dir }}/ssh_password.mod 52 | 53 | - name: Install selinux policy 54 | command: semodule -i {{ ssh_custom_selinux_dir }}/ssh_password.pp 55 | 56 | 57 | # The following tasks only get executed when selinux is installed, UsePam is 58 | # 'yes' and the ssh_password module is installed. See 59 | # http://danwalsh.livejournal.com/12333.html for more info 60 | - name: Remove selinux-policy when Pam is used, because Allowing sshd to read the shadow file directly is considered a potential security risk 61 | command: semodule -r ssh_password 62 | when: 63 | - ssh_use_pam | bool 64 | - ('ssh_password' in ssh_password_module.stdout) 65 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/sysctl.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Protect sysctl.conf 3 | file: 4 | path: '/etc/sysctl.conf' 5 | owner: 'root' 6 | group: 'root' 7 | mode: '0440' 8 | state: touch 9 | modification_time: preserve 10 | access_time: preserve 11 | 12 | - name: Set Daemon umask, do config for rhel-family | NSA 2.2.4.1 13 | template: 14 | src: 'etc/sysconfig/rhel_sysconfig_init.j2' 15 | dest: '/etc/sysconfig/init' 16 | owner: 'root' 17 | group: 'root' 18 | mode: '0544' 19 | when: ansible_facts.os_family == 'RedHat' 20 | 21 | - name: Install initramfs-tools 22 | apt: 23 | name: 'initramfs-tools' 24 | state: 'present' 25 | update_cache: true 26 | when: 27 | - ansible_facts.os_family == 'Debian' 28 | - os_security_kernel_enable_module_loading 29 | 30 | - name: Rebuild initramfs with starting pack of modules, if module loading at runtime is disabled 31 | template: 32 | src: 'etc/initramfs-tools/modules.j2' 33 | dest: '/etc/initramfs-tools/modules' 34 | owner: 'root' 35 | group: 'root' 36 | mode: '0440' 37 | notify: 38 | - Update-initramfs 39 | when: 40 | - ansible_facts.os_family == 'Debian' 41 | - os_security_kernel_enable_module_loading 42 | register: initramfs 43 | 44 | - name: Change sysctls 45 | block: 46 | - name: Create a combined sysctl-dict if overwrites are defined 47 | set_fact: 48 | sysctl_config: '{{ sysctl_config | combine(sysctl_overwrite) }}' 49 | when: sysctl_overwrite | default() 50 | 51 | - name: Change various sysctl-settings, look at the sysctl-vars file for documentation 52 | sysctl: 53 | name: '{{ item.key }}' 54 | value: '{{ item.value }}' 55 | sysctl_set: true 56 | state: present 57 | reload: true 58 | ignoreerrors: true 59 | with_dict: '{{ sysctl_config }}' 60 | 61 | - name: Change various sysctl-settings on Amazon Linux, look at the sysctl-vars file for documentation 62 | sysctl: 63 | name: '{{ item.key }}' 64 | value: '{{ item.value }}' 65 | state: present 66 | reload: true 67 | ignoreerrors: true 68 | with_dict: '{{ sysctl_rhel_config }}' 69 | when: ansible_facts.distribution == 'Amazon' 70 | 71 | when: ansible_virtualization_type not in ['docker', 'lxc', 'openvz'] 72 | 73 | - name: Apply ufw defaults 74 | template: 75 | src: 'etc/default/ufw.j2' 76 | dest: '/etc/default/ufw' 77 | mode: '0644' 78 | when: 79 | - ufw_manage_defaults 80 | - ansible_facts.os_family == 'Debian' 81 | tags: ufw 82 | -------------------------------------------------------------------------------- /roles/os_hardening/templates/etc/pam.d/rhel_auth.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | #%PAM-1.0 5 | auth required pam_env.so 6 | auth required pam_faildelay.so delay=2000000 7 | {% if os_auth_retries > 0 %} 8 | auth required pam_faillock.so preauth silent audit even_deny_root deny={{ os_auth_retries }} unlock_time={{ os_auth_lockout_time }} 9 | {% endif %} 10 | {% if (os_auth_pam_sssd_enable | bool) %} 11 | auth [default=1 ignore=ignore success=ok] pam_succeed_if.so uid >= 1000 quiet 12 | auth [default=1 ignore=ignore success=ok] pam_localuser.so 13 | {% endif %} 14 | auth sufficient pam_unix.so nullok try_first_pass 15 | {% if (os_auth_pam_sssd_enable | bool) %} 16 | auth [default=1 ignore=ignore success=ok] pam_succeed_if.so uid >= 1000 quiet 17 | auth sufficient pam_sss.so forward_pass 18 | {% endif %} 19 | {% if os_auth_retries > 0 %} 20 | auth required pam_faillock.so authfail audit even_deny_root deny={{ os_auth_retries }} unlock_time={{ os_auth_lockout_time }} 21 | {% endif %} 22 | auth required pam_deny.so 23 | 24 | {% if os_auth_retries > 0 %} 25 | account required pam_faillock.so 26 | {% endif %} 27 | account required pam_unix.so 28 | account sufficient pam_localuser.so 29 | account sufficient pam_succeed_if.so uid < 1000 quiet 30 | {% if (os_auth_pam_sssd_enable | bool) %} 31 | account [default=bad success=ok user_unknown=ignore] pam_sss.so 32 | {% endif %} 33 | account required pam_permit.so 34 | 35 | {% if (os_auth_pam_passwdqc_enable | bool) %} 36 | password requisite pam_pwquality.so {{ os_auth_pam_pwquality_options }} 37 | {% endif %} 38 | {# NSA 2.3.3.6 Limit Password Reuse #} 39 | password requisite pam_pwhistory.so remember=5 use_authtok 40 | {# NSA 2.3.3.5 Upgrade Password Hashing Algorithm to SHA-512 #} 41 | password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok rounds={{ os_sha_crypt_min_rounds }} 42 | {% if (os_auth_pam_sssd_enable | bool) %} 43 | password sufficient pam_sss.so use_authtok 44 | {% endif %} 45 | password required pam_deny.so 46 | 47 | session optional pam_keyinit.so revoke 48 | session required pam_limits.so 49 | -session optional pam_systemd.so 50 | session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid 51 | session required pam_unix.so 52 | {% if (os_auth_pam_sssd_enable | bool) %} 53 | session optional pam_sss.so 54 | {% endif %} 55 | -------------------------------------------------------------------------------- /roles/os_hardening/templates/etc/security/faillock.conf.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | # 4 | # Configuration for locking the user after multiple failed 5 | # authentication attempts. 6 | # 7 | # The directory where the user files with the failure records are kept. 8 | # The default is /var/run/faillock. 9 | # dir = /var/run/faillock 10 | # 11 | # Will log the user name into the system log if the user is not found. 12 | # Enabled if option is present. 13 | # audit 14 | # 15 | # Don't print informative messages. 16 | # Enabled if option is present. 17 | # silent 18 | # 19 | # Don't log informative messages via syslog. 20 | # Enabled if option is present. 21 | # no_log_info 22 | # 23 | # Only track failed user authentications attempts for local users 24 | # in /etc/passwd and ignore centralized (AD, IdM, LDAP, etc.) users. 25 | # The `faillock` command will also no longer track user failed 26 | # authentication attempts. Enabling this option will prevent a 27 | # double-lockout scenario where a user is locked out locally and 28 | # in the centralized mechanism. 29 | # Enabled if option is present. 30 | # local_users_only 31 | # 32 | # Deny access if the number of consecutive authentication failures 33 | # for this user during the recent interval exceeds n tries. 34 | # The default is 3. 35 | deny = {{ os_auth_retries }} 36 | # 37 | # The length of the interval during which the consecutive 38 | # authentication failures must happen for the user account 39 | # lock out is n seconds. 40 | # The default is 900 (15 minutes). 41 | # fail_interval = 900 42 | # 43 | # The access will be re-enabled after n seconds after the lock out. 44 | # The value 0 has the same meaning as value `never` - the access 45 | # will not be re-enabled without resetting the faillock 46 | # entries by the `faillock` command. 47 | # The default is 600 (10 minutes). 48 | unlock_time = {{ os_auth_lockout_time }} 49 | # 50 | # Root account can become locked as well as regular accounts. 51 | # Enabled if option is present. 52 | even_deny_root 53 | # 54 | # This option implies the `even_deny_root` option. 55 | # Allow access after n seconds to root account after the 56 | # account is locked. In case the option is not specified 57 | # the value is the same as of the `unlock_time` option. 58 | # root_unlock_time = 900 59 | # 60 | # If a group name is specified with this option, members 61 | # of the group will be handled by this module the same as 62 | # the root account (the options `even_deny_root>` and 63 | # `root_unlock_time` will apply to them. 64 | # By default, the option is not set. 65 | # admin_group = 66 | -------------------------------------------------------------------------------- /molecule/os_hardening/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: all 4 | become: true 5 | environment: 6 | http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" 7 | https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" 8 | no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" 9 | roles: 10 | - geerlingguy.git 11 | tasks: 12 | - name: install fake SuSE-release for cinc compatibility 13 | copy: 14 | content: | 15 | openSUSE Faked Enterprise 2020 (x86_64) 16 | VERSION = 2020 17 | CODENAME = Faked Feature 18 | dest: /etc/SuSE-release 19 | owner: root 20 | group: root 21 | mode: '0444' 22 | when: ansible_facts.os_family == 'Suse' 23 | 24 | - name: install git for SuSE since geerlinguy.git does not support it 25 | zypper: 26 | name: git 27 | state: present 28 | when: ansible_facts.os_family == 'Suse' 29 | 30 | - name: Run the equivalent of "apt-get update" as a separate step 31 | apt: 32 | update_cache: true 33 | when: ansible_facts.os_family == 'Debian' 34 | 35 | - name: install required tools on debian 36 | apt: 37 | name: procps 38 | when: ansible_facts.os_family == 'Debian' 39 | 40 | - name: include PAM tests 41 | include: verify_pam.yml 42 | when: ansible_facts.distribution in ['Debian', 'Ubuntu'] or ansible_facts.os_family == 'RedHat' 43 | 44 | - name: download cinc-auditor 45 | get_url: 46 | url: https://omnitruck.cinc.sh/install.sh 47 | dest: /tmp/install.sh 48 | mode: '0775' 49 | 50 | - name: install cinc-auditor 51 | shell: "bash /tmp/install.sh -s -- -P cinc-auditor -v 4" 52 | 53 | - name: Execute cinc-auditor tests 54 | command: "/opt/cinc-auditor/bin/cinc-auditor exec --no-show-progress --no-color --no-distinct-exit supermarket://dev-sec/linux-baseline" 55 | register: test_results 56 | changed_when: false 57 | ignore_errors: true 58 | 59 | - name: Display details about the cinc-auditor results 60 | debug: 61 | msg: "{{ test_results.stdout_lines }}" 62 | 63 | - name: Fail when tests fail 64 | fail: 65 | msg: "Inspec failed to validate" 66 | when: test_results.rc != 0 67 | 68 | # test if variable can be overridden 69 | - name: workaround for https://github.com/ansible/ansible/issues/66304 70 | set_fact: 71 | ansible_virtualization_type: "docker" 72 | os_env_umask: "027 #override" 73 | 74 | - include_role: 75 | name: os_hardening 76 | 77 | - name: verify os_env_umask 78 | shell: 79 | cmd: "grep '027 #override' /etc/login.defs" 80 | changed_when: false 81 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/hardening.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Fetch OS dependent variables 3 | include_vars: 4 | file: '{{ item }}' 5 | name: 'os_vars' 6 | with_first_found: 7 | - files: 8 | - '{{ ansible_facts.distribution }}_{{ ansible_facts.distribution_major_version }}.yml' 9 | - '{{ ansible_facts.distribution }}.yml' 10 | - '{{ ansible_facts.os_family }}_{{ ansible_facts.distribution_major_version }}.yml' 11 | - '{{ ansible_facts.os_family }}.yml' 12 | skip: true 13 | tags: always 14 | 15 | # we only override variables with our default, if they have not been specified already 16 | # by default the lookup functions finds all varnames containing the string, therefore 17 | # we add ^ and $ to denote start and end of string, so this returns only exact matches 18 | - name: Set OS dependent variables, if not already defined by user 19 | set_fact: 20 | '{{ item.key }}': '{{ item.value }}' 21 | when: "not lookup('varnames', '^' + item.key + '$')" 22 | with_dict: '{{ os_vars }}' 23 | tags: always 24 | 25 | - import_tasks: auditd.yml 26 | tags: auditd 27 | when: os_auditd_enabled | bool 28 | 29 | - import_tasks: cron.yml 30 | tags: cron 31 | when: os_cron_enabled | bool 32 | 33 | - import_tasks: limits.yml 34 | tags: limits 35 | when: os_limits_enabled | bool 36 | 37 | - import_tasks: login_defs.yml 38 | tags: login_defs 39 | when: os_login_defs_enabled | bool 40 | 41 | - import_tasks: minimize_access.yml 42 | tags: minimize_access 43 | when: os_minimize_access_enabled | bool 44 | 45 | - import_tasks: pam.yml 46 | tags: pam 47 | when: os_pam_enabled | bool 48 | 49 | - import_tasks: modprobe.yml 50 | tags: modprobe 51 | when: os_modprobe_enabled | bool 52 | 53 | - import_tasks: profile.yml 54 | tags: profile 55 | when: os_profile_enabled | bool 56 | 57 | - import_tasks: securetty.yml 58 | tags: securetty 59 | when: os_securetty_enabled | bool 60 | 61 | - import_tasks: suid_sgid.yml 62 | when: os_security_suid_sgid_enforce | bool 63 | tags: suid_sgid 64 | 65 | - import_tasks: sysctl.yml 66 | tags: sysctl 67 | when: os_sysctl_enabled | bool 68 | 69 | - import_tasks: user_accounts.yml 70 | tags: user_accounts 71 | when: os_user_accounts_enabled | bool 72 | 73 | - import_tasks: rhosts.yml 74 | tags: rhosts 75 | when: os_rhosts_enabled | bool 76 | 77 | - import_tasks: yum.yml 78 | tags: yum 79 | when: 80 | - ansible_facts.os_family == 'RedHat' 81 | - os_yum_enabled | bool 82 | 83 | - import_tasks: apt.yml 84 | tags: apt 85 | when: 86 | - ansible_facts.os_family == 'Debian' 87 | - os_apt_enabled | bool 88 | 89 | - import_tasks: selinux.yml 90 | tags: selinux 91 | when: 92 | - ansible_facts.selinux.status == 'enabled' 93 | - os_selinux_enabled | bool 94 | -------------------------------------------------------------------------------- /.github/workflows/mysql_hardening.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "devsec.mysql_hardening" 3 | on: # yamllint disable-line rule:truthy 4 | workflow_dispatch: 5 | push: 6 | paths: 7 | - 'roles/mysql_hardening/**' 8 | - 'molecule/mysql_hardening/**' 9 | - '.github/workflows/mysql_hardening.yml' 10 | pull_request: 11 | paths: 12 | - 'roles/mysql_hardening/**' 13 | - 'molecule/mysql_hardening/**' 14 | - '.github/workflows/mysql_hardening.yml' 15 | jobs: 16 | build: 17 | runs-on: ubuntu-18.04 18 | env: 19 | PY_COLORS: 1 20 | ANSIBLE_FORCE_COLOR: 1 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | molecule_distro: 25 | - centos7 26 | - centos8 27 | - rocky8 28 | - ubuntu1804 29 | - ubuntu2004 30 | - debian9 31 | - debian10 32 | # - amazon # geerlingguy.mysql does not support fedora 33 | # - arch # needs to be fixed 34 | # - opensuse_tumbleweed # needs to be fixed 35 | # - fedora # geerlingguy.mysql does not support fedora 36 | steps: 37 | - name: Checkout repo 38 | uses: actions/checkout@v2 39 | with: 40 | path: ansible_collections/devsec/hardening 41 | submodules: true 42 | 43 | - name: Set up Python 3.7 44 | uses: actions/setup-python@v1 45 | with: 46 | python-version: 3.7 47 | 48 | - name: Install dependencies 49 | run: | 50 | sudo apt install git 51 | python -m pip install --no-cache-dir --upgrade pip 52 | pip install -r requirements.txt 53 | working-directory: ansible_collections/devsec/hardening 54 | 55 | - name: Create default collection path symlink 56 | run: | 57 | mkdir -p /home/runner/.ansible 58 | ln -s /home/runner/work/ansible-os-hardening/ansible-os-hardening /home/runner/.ansible/collections 59 | 60 | # that was a hard one to fix. robert did it thankfully 61 | # https://github.com/robertdebock/ansible-role-mysql/commit/7562e99099b06282391ab7ed102b393a0406d212 62 | - name: disable apparmor on debian systems 63 | run: | 64 | set -x 65 | sudo apt-get install apparmor-profiles 66 | sudo ln -s /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/ 67 | sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.mysqld 68 | if: ${{ startsWith(matrix.molecule_distro, 'Debian') }} 69 | 70 | - name: Test with molecule 71 | run: | 72 | molecule --version 73 | molecule test -s mysql_hardening 74 | env: 75 | MOLECULE_DISTRO: ${{ matrix.molecule_distro }} 76 | working-directory: ansible_collections/devsec/hardening 77 | -------------------------------------------------------------------------------- /roles/os_hardening/templates/etc/libuser.conf.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | # See libuser.conf(5) for more information. 5 | 6 | # Do not modify the default module list if you care about unattended calls 7 | # to programs (i.e., scripts) working! 8 | 9 | [import] 10 | # Data from these files is used when libuser.conf does not define a value. 11 | # The mapping is documented in the man page. 12 | login_defs = /etc/login.defs 13 | default_useradd = /etc/default/useradd 14 | 15 | [defaults] 16 | # The default (/usr/lib*/libuser) is usually correct 17 | # moduledir = /your/custom/directory 18 | 19 | # The following variables are usually imported: 20 | # skeleton = /etc/skel 21 | # mailspooldir = /var/mail 22 | 23 | # NSA 2.3.3.5 Upgrade Password Hashing Algorithm to SHA-512 24 | crypt_style = sha512 25 | 26 | modules = files shadow 27 | create_modules = files shadow 28 | # modules = files shadow ldap 29 | # create_modules = ldap 30 | 31 | [userdefaults] 32 | LU_USERNAME = %n 33 | # LU_UIDNUMBER = 500 34 | LU_GIDNUMBER = %u 35 | # LU_USERPASSWORD = !! 36 | # LU_GECOS = %n 37 | # LU_HOMEDIRECTORY = /home/%n 38 | # LU_LOGINSHELL = /bin/bash 39 | 40 | # LU_SHADOWNAME = %n 41 | # LU_SHADOWPASSWORD = !! 42 | # LU_SHADOWLASTCHANGE = %d 43 | # LU_SHADOWMIN = 0 44 | # LU_SHADOWMAX = 99999 45 | # LU_SHADOWWARNING = 7 46 | # LU_SHADOWINACTIVE = -1 47 | # LU_SHADOWEXPIRE = -1 48 | # LU_SHADOWFLAG = -1 49 | 50 | [groupdefaults] 51 | LU_GROUPNAME = %n 52 | # LU_GIDNUMBER = 500 53 | # LU_GROUPPASSWORD = !! 54 | # LU_MEMBERUID = 55 | # LU_ADMINISTRATORUID = 56 | 57 | [files] 58 | # This is useful for the case where some master files are used to 59 | # populate a different NSS mechanism which this workstation uses. 60 | # directory = /etc 61 | 62 | [shadow] 63 | # This is useful for the case where some master files are used to 64 | # populate a different NSS mechanism which this workstation uses. 65 | # directory = /etc 66 | 67 | [ldap] 68 | # Setting these is always necessary. 69 | # server = ldap 70 | # basedn = dc=example,dc=com 71 | 72 | # Setting these is rarely necessary, since it's usually correct. 73 | # userBranch = ou=People 74 | # groupBranch = ou=Group 75 | 76 | # Set only if your administrative user uses simple bind operations to 77 | # connect to the server. 78 | # binddn = cn=Manager,dc=example,dc=com 79 | 80 | # Set this only if the default user (as determined by SASL) is incorrect 81 | # for SASL bind operations. Usually, it's correct, so you'll rarely need 82 | # to set these. 83 | # user = Manager 84 | # authuser = Manager 85 | 86 | [sasl] 87 | # Set these only if your sasldb is only used by a particular application, and 88 | # in a particular domain. The default (all applications, all domains) is 89 | # probably correct for most installations. 90 | # appname = imap 91 | # domain = EXAMPLE.COM 92 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: New release 3 | 4 | on: # yamllint disable-line rule:truthy 5 | workflow_dispatch: 6 | push: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | generate_changelog: 12 | runs-on: ubuntu-latest 13 | name: create release draft 14 | steps: 15 | - uses: actions/checkout@v2.3.4 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: 'Get Previous tag' 20 | id: previoustag 21 | uses: "WyriHaximus/github-action-get-previous-tag@master" 22 | env: 23 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 24 | 25 | - name: calculate next version 26 | id: version 27 | uses: patrickjahns/version-drafter-action@v1 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | 31 | - name: Generate changelog 32 | uses: charmixer/auto-changelog-action@v1 33 | with: 34 | token: ${{ secrets.GITHUB_TOKEN }} 35 | future_release: ${{ steps.version.outputs.next-version }} 36 | # this excludes all versions prior to the collection-release 37 | # since they break the changelog generation with the error: 38 | # "No common ancestor between ... and $version" 39 | exclude_tags_regex: '[0-6]\.\d\.\d' 40 | issue_line_labels: mysql_hardening,os_hardening,ssh_hardening,nginx_hardening 41 | 42 | - name: push changelog 43 | uses: github-actions-x/commit@v2.6 44 | with: 45 | github-token: ${{ secrets.GITHUB_TOKEN }} 46 | push-branch: 'master' 47 | commit-message: 'update changelog' 48 | force-add: 'true' 49 | files: CHANGELOG.md 50 | name: dev-sec CI 51 | email: hello@dev-sec.io 52 | 53 | # do a second checkout to prevent race situation 54 | # changelog gets updated but action works on old commit id 55 | - uses: actions/checkout@v2.3.4 56 | with: 57 | ref: master 58 | 59 | - name: Generate changelog for the release 60 | run: | 61 | sed '/## \[${{ steps.previoustag.outputs.tag }}\]/Q' CHANGELOG.md > CHANGELOGRELEASE.md 62 | 63 | - name: Read CHANGELOG.md 64 | id: package 65 | uses: juliangruber/read-file-action@v1 66 | with: 67 | path: ./CHANGELOGRELEASE.md 68 | 69 | - name: Create Release draft 70 | id: create_release 71 | uses: actions/create-release@v1 72 | env: 73 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 74 | with: 75 | release_name: ${{ steps.version.outputs.next-version }} 76 | tag_name: ${{ steps.version.outputs.next-version }} 77 | body: | 78 | ${{ steps.package.outputs.content }} 79 | draft: true 80 | -------------------------------------------------------------------------------- /roles/nginx_hardening/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create additional configuration 3 | template: 4 | src: "hardening.conf.j2" 5 | dest: "/etc/nginx/conf.d/90.hardening.conf" 6 | mode: '0600' 7 | owner: "root" 8 | group: "root" 9 | notify: Restart nginx 10 | 11 | - name: Change configuration in main nginx.conf 12 | lineinfile: 13 | dest: "/etc/nginx/nginx.conf" 14 | regexp: '^\s*server_tokens' 15 | line: " server_tokens {{ nginx_server_tokens }};" 16 | insertafter: "http {" 17 | mode: '0640' 18 | owner: "root" 19 | group: "root" 20 | notify: Restart nginx 21 | 22 | - name: Change ssl_protocols in main nginx.conf 23 | lineinfile: 24 | dest: "/etc/nginx/nginx.conf" 25 | regexp: '^\s*ssl_protocols' 26 | line: " ssl_protocols {{ nginx_ssl_protocols }};" 27 | insertafter: "http {" 28 | mode: '0640' 29 | owner: "root" 30 | group: "root" 31 | notify: Restart nginx 32 | 33 | - name: Change ssl_prefer_server_ciphers in main nginx.conf 34 | lineinfile: 35 | dest: "/etc/nginx/nginx.conf" 36 | regexp: '^\s*ssl_prefer_server_ciphers' 37 | line: " ssl_prefer_server_ciphers {{ nginx_ssl_prefer_server_ciphers }};" 38 | insertafter: "http {" 39 | mode: '0640' 40 | owner: "root" 41 | group: "root" 42 | notify: Restart nginx 43 | 44 | - name: Change client_max_body_size in main nginx.conf 45 | lineinfile: 46 | dest: "/etc/nginx/nginx.conf" 47 | regexp: '^\s*client_max_body_size' 48 | line: " client_max_body_size {{ nginx_client_max_body_size }};" 49 | insertafter: "http {" 50 | mode: '0640' 51 | owner: "root" 52 | group: "root" 53 | notify: Restart nginx 54 | 55 | - name: Change client_body_buffer_size in main nginx.conf 56 | lineinfile: 57 | dest: "/etc/nginx/nginx.conf" 58 | regexp: '^\s*client_body_buffer_size' 59 | line: " client_body_buffer_size {{ nginx_client_body_buffer_size }};" 60 | insertafter: "http {" 61 | mode: '0640' 62 | owner: "root" 63 | group: "root" 64 | notify: Restart nginx 65 | 66 | - name: Change keepalive_timeout in main nginx.conf 67 | lineinfile: 68 | dest: "/etc/nginx/nginx.conf" 69 | regexp: '^\s*keepalive_timeout' 70 | line: " keepalive_timeout {{ nginx_keepalive_timeout }};" 71 | insertafter: "http {" 72 | mode: '0640' 73 | owner: "root" 74 | group: "root" 75 | notify: Restart nginx 76 | 77 | - name: Remove default.conf 78 | file: 79 | path: "{{ item }}" 80 | state: absent 81 | when: nginx_remove_default_site 82 | notify: Restart nginx 83 | loop: 84 | - "/etc/nginx/conf.d/default.conf" 85 | - "/etc/nginx/sites-enabled/default" 86 | 87 | - name: Generate dh group 88 | openssl_dhparam: 89 | path: "/etc/nginx/dh{{ nginx_dh_size }}.pem" 90 | size: "{{ nginx_dh_size }}" 91 | mode: '0640' 92 | owner: "root" 93 | group: "root" 94 | notify: Restart nginx 95 | -------------------------------------------------------------------------------- /molecule/os_hardening/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: wrapper playbook for kitchen testing "ansible-os-hardening" with custom vars for testing 3 | hosts: all 4 | become: true 5 | environment: 6 | http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" 7 | https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" 8 | no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" 9 | collections: 10 | - devsec.hardening 11 | tasks: 12 | - name: workaround for https://github.com/ansible/ansible/issues/66304 13 | set_fact: 14 | ansible_virtualization_type: "docker" 15 | - include_role: 16 | name: os_hardening 17 | vars: 18 | os_security_users_allow: change_user 19 | os_security_kernel_enable_core_dump: true 20 | os_security_suid_sgid_remove_from_unknown: true 21 | os_auth_pam_passwdqc_enable: false 22 | os_auth_lockout_time: 15 23 | os_desktop_enable: true 24 | os_env_extra_user_paths: ['/home'] 25 | os_auth_allow_homeless: true 26 | os_security_suid_sgid_blacklist: ['/bin/umount'] 27 | os_security_suid_sgid_whitelist: ['/usr/bin/rlogin'] 28 | os_filesystem_whitelist: [] 29 | sysctl_config: 30 | net.ipv4.ip_forward: 0 31 | net.ipv6.conf.all.forwarding: 0 32 | net.ipv6.conf.all.accept_ra: 0 33 | net.ipv6.conf.default.accept_ra: 0 34 | net.ipv4.conf.all.rp_filter: 1 35 | net.ipv4.conf.default.rp_filter: 1 36 | net.ipv4.icmp_echo_ignore_broadcasts: 1 37 | net.ipv4.icmp_ignore_bogus_error_responses: 1 38 | net.ipv4.icmp_ratelimit: 100 39 | net.ipv4.icmp_ratemask: 88089 40 | net.ipv4.conf.all.arp_ignore: 1 41 | net.ipv4.conf.all.arp_announce: 2 42 | net.ipv4.conf.all.shared_media: 1 43 | net.ipv4.conf.default.shared_media: 1 44 | net.ipv4.conf.all.accept_source_route: 0 45 | net.ipv4.conf.default.accept_source_route: 0 46 | net.ipv4.conf.default.accept_redirects: 0 47 | net.ipv4.conf.all.accept_redirects: 0 48 | net.ipv4.conf.all.secure_redirects: 0 49 | net.ipv4.conf.default.secure_redirects: 0 50 | net.ipv6.conf.default.accept_redirects: 0 51 | net.ipv6.conf.all.accept_redirects: 0 52 | net.ipv4.conf.all.send_redirects: 0 53 | net.ipv4.conf.default.send_redirects: 0 54 | net.ipv4.conf.all.log_martians: 1 55 | net.ipv6.conf.default.router_solicitations: 0 56 | net.ipv6.conf.default.accept_ra_rtr_pref: 0 57 | net.ipv6.conf.default.accept_ra_pinfo: 0 58 | net.ipv6.conf.default.accept_ra_defrtr: 0 59 | net.ipv6.conf.default.conf: 0 60 | net.ipv6.conf.default.dad_transmits: 0 61 | net.ipv6.conf.default.max_addresses: 1 62 | kernel.sysrq: 0 63 | fs.suid_dumpable: 0 64 | kernel.randomize_va_space: 2 65 | 66 | # - name: wrapper playbook for kitchen testing "ansible-os-hardening" 67 | # hosts: all 68 | # become: true 69 | # collections: 70 | # - devsec.hardening 71 | # vars: 72 | # os_auditd_enabled: false 73 | # tasks: 74 | # - name: set ansible_python_interpreter to "/usr/bin/python3" on fedora 75 | # set_fact: 76 | # ansible_python_interpreter: "/usr/bin/python3" 77 | # when: ansible_facts.distribution == 'Fedora' 78 | # 79 | # - name: Run the equivalent of "apt-get update" as a separate step 80 | # apt: 81 | # update_cache: yes 82 | # when: ansible_facts.os_family == 'Debian' 83 | # 84 | # - include_role: 85 | # name: os_hardening 86 | -------------------------------------------------------------------------------- /roles/os_hardening/tasks/pam_debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install the package for strong password checking 3 | apt: 4 | name: 'libpam-passwdqc' 5 | state: 'present' 6 | update_cache: 'yes' 7 | when: 8 | - os_auth_pam_passwdqc_enable 9 | 10 | - name: Configure passwdqc 11 | template: 12 | src: 'usr/share/pam-configs/pam_passwdqc.j2' 13 | dest: '{{ passwdqc_path }}' 14 | mode: '0644' 15 | owner: 'root' 16 | group: 'root' 17 | when: 18 | - os_auth_pam_passwdqc_enable 19 | 20 | - name: Install tally2 21 | apt: 22 | name: 'libpam-modules' 23 | state: 'present' 24 | when: 25 | - os_auth_retries > 0 26 | 27 | - name: Manage tally on Debian stable 28 | block: 29 | - name: Configure tally2 30 | template: 31 | src: 'usr/share/pam-configs/pam_tally2.j2' 32 | dest: '{{ tally2_path }}' 33 | mode: '0644' 34 | owner: 'root' 35 | group: 'root' 36 | when: 37 | - os_auth_retries > 0 38 | 39 | - name: Delete tally2 when retries is 0 40 | file: 41 | path: '{{ tally2_path }}' 42 | state: 'absent' 43 | when: 44 | - os_auth_retries == 0 45 | when: 46 | - "'libpam-modules' in ansible_facts.packages" 47 | - "ansible_facts.packages['libpam-modules'][0].version is version('1.4.0', '<')" 48 | 49 | - name: Manage tally/faillock on Debian unstable 50 | block: 51 | - name: Delete tally2 52 | file: 53 | path: '{{ tally2_path }}' 54 | state: 'absent' 55 | 56 | - name: create tally directory 57 | file: 58 | path: '/var/run/faillock' 59 | state: 'directory' 60 | mode: '0755' 61 | owner: 'root' 62 | group: 'root' 63 | 64 | - name: Configure faillock 65 | template: 66 | src: 'etc/security/faillock.conf.j2' 67 | dest: '/etc/security/faillock.conf' 68 | mode: '0644' 69 | owner: 'root' 70 | group: 'root' 71 | 72 | - name: Configure faillock pam 73 | template: 74 | src: 'usr/share/pam-configs/pam_faillock.j2' 75 | dest: '/usr/share/pam-configs/faillock' 76 | mode: '0644' 77 | owner: 'root' 78 | group: 'root' 79 | when: 80 | - os_auth_retries > 0 81 | 82 | - name: Configure faillock pam authfail 83 | template: 84 | src: 'usr/share/pam-configs/pam_faillock_authfail.j2' 85 | dest: '/usr/share/pam-configs/faillock_authfail' 86 | mode: '0644' 87 | owner: 'root' 88 | group: 'root' 89 | when: 90 | - os_auth_retries > 0 91 | 92 | - name: Delete faillock when retries is 0 93 | file: 94 | path: '/usr/share/pam-configs/faillock' 95 | state: 'absent' 96 | when: 97 | - os_auth_retries == 0 98 | 99 | - name: Delete faillock authfail when retries is 0 100 | file: 101 | path: '/usr/share/pam-configs/faillock_authfail' 102 | state: 'absent' 103 | when: 104 | - os_auth_retries == 0 105 | when: 106 | - "'libpam-modules' in ansible_facts.packages" 107 | - "ansible_facts.packages['libpam-modules'][0].version is version('1.4.0', '>=')" 108 | 109 | - name: Update pam on Debian systems 110 | command: 'pam-auth-update --package' 111 | environment: 112 | DEBIAN_FRONTEND: noninteractive 113 | changed_when: false 114 | 115 | - name: Remove passwdqc 116 | apt: 117 | name: 'libpam-passwdqc' 118 | state: 'absent' 119 | when: 120 | - not os_auth_pam_passwdqc_enable 121 | -------------------------------------------------------------------------------- /roles/nginx_hardening/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.1.0](https://github.com/dev-sec/ansible-nginx-hardening/tree/2.1.0) (2018-11-18) 4 | 5 | [Full Changelog](https://github.com/dev-sec/ansible-nginx-hardening/compare/2.0.0...2.1.0) 6 | 7 | **Merged pull requests:** 8 | 9 | - add ubuntu 18.04 support [\#20](https://github.com/dev-sec/ansible-nginx-hardening/pull/20) ([rndmh3ro](https://github.com/rndmh3ro)) 10 | - updated minimum required ansible version to 2.5 in README, as the used 'loop' keyword was introduced in version 2.5 [\#19](https://github.com/dev-sec/ansible-nginx-hardening/pull/19) ([szEvEz](https://github.com/szEvEz)) 11 | 12 | ## [2.0.0](https://github.com/dev-sec/ansible-nginx-hardening/tree/2.0.0) (2018-09-08) 13 | 14 | [Full Changelog](https://github.com/dev-sec/ansible-nginx-hardening/compare/1.0.2...2.0.0) 15 | 16 | **Implemented enhancements:** 17 | 18 | - Update readme to include baselines [\#10](https://github.com/dev-sec/ansible-nginx-hardening/issues/10) 19 | - Update testing, remove useless params, style update [\#18](https://github.com/dev-sec/ansible-nginx-hardening/pull/18) ([rndmh3ro](https://github.com/rndmh3ro)) 20 | - Update README.md [\#14](https://github.com/dev-sec/ansible-nginx-hardening/pull/14) ([vishesh92](https://github.com/vishesh92)) 21 | - Add comment filter to {{ansible\_managed}} string [\#12](https://github.com/dev-sec/ansible-nginx-hardening/pull/12) ([fazlearefin](https://github.com/fazlearefin)) 22 | - use new Docker images [\#8](https://github.com/dev-sec/ansible-nginx-hardening/pull/8) ([rndmh3ro](https://github.com/rndmh3ro)) 23 | 24 | **Fixed bugs:** 25 | 26 | - Running kitchen verify asks for 'roots' password [\#11](https://github.com/dev-sec/ansible-nginx-hardening/issues/11) 27 | - Fix duplicate ssl_prefer_server_ciphers error [\#16](https://github.com/dev-sec/ansible-nginx-hardening/pull/16) ([oakey-b1](https://github.com/oakey-b1)) 28 | 29 | ## [1.0.2](https://github.com/dev-sec/ansible-nginx-hardening/tree/1.0.2) (2016-10-24) 30 | 31 | [Full Changelog](https://github.com/dev-sec/ansible-nginx-hardening/compare/1.0.1...1.0.2) 32 | 33 | **Fixed bugs:** 34 | 35 | - Syntax Error while loading YAML in defaults/main.yml [\#6](https://github.com/dev-sec/ansible-nginx-hardening/issues/6) 36 | 37 | **Merged pull requests:** 38 | 39 | - remove tabs. fix \#6 [\#7](https://github.com/dev-sec/ansible-nginx-hardening/pull/7) ([rndmh3ro](https://github.com/rndmh3ro)) 40 | 41 | ## [1.0.1](https://github.com/dev-sec/ansible-nginx-hardening/tree/1.0.1) (2016-09-23) 42 | 43 | [Full Changelog](https://github.com/dev-sec/ansible-nginx-hardening/compare/1.0.0...1.0.1) 44 | 45 | **Fixed bugs:** 46 | 47 | - ssl_dhparam [\#4](https://github.com/dev-sec/ansible-nginx-hardening/issues/4) 48 | 49 | **Closed issues:** 50 | 51 | - Make the owner /etc/nginx configurabe [\#3](https://github.com/dev-sec/ansible-nginx-hardening/issues/3) 52 | - Running worker process as non-privileged user \(1 failed\) [\#2](https://github.com/dev-sec/ansible-nginx-hardening/issues/2) 53 | 54 | **Merged pull requests:** 55 | 56 | - create dhparam file. fix \#4 [\#5](https://github.com/dev-sec/ansible-nginx-hardening/pull/5) ([rndmh3ro](https://github.com/rndmh3ro)) 57 | - improve gemfile, update readme for local tests [\#1](https://github.com/dev-sec/ansible-nginx-hardening/pull/1) ([chris-rock](https://github.com/chris-rock)) 58 | 59 | ## [1.0.0](https://github.com/dev-sec/ansible-nginx-hardening/tree/1.0.0) (2016-08-11) 60 | 61 | [Full Changelog](https://github.com/dev-sec/ansible-nginx-hardening/compare/1b9dcf16cfbf45ff5f50cd83509245d1527f9fd0...1.0.0) 62 | 63 | \* _This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)_ 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible Collection - devsec.hardening 2 | 3 | ![devsec.os_hardening](https://github.com/dev-sec/ansible-os-hardening/workflows/devsec.os_hardening/badge.svg) 4 | ![devsec.ssh_hardening](https://github.com/dev-sec/ansible-os-hardening/workflows/devsec.ssh_hardening/badge.svg) 5 | ![devsec.nginx_hardening](https://github.com/dev-sec/ansible-os-hardening/workflows/devsec.nginx_hardening/badge.svg) 6 | ![devsec.mysql_hardening](https://github.com/dev-sec/ansible-os-hardening/workflows/devsec.mysql_hardening/badge.svg) 7 | 8 | ## Description 9 | 10 | This collection provides battle tested hardening for: 11 | 12 | - Linux operating systems: 13 | - CentOS 7/8 14 | - Rocky Linux 8 15 | - Debian 9/10 16 | - Ubuntu 16.04/18.04/20.04 17 | - Amazon Linux (some roles supported) 18 | - Arch Linux (some roles supported) 19 | - Fedora (some roles supported) 20 | - Suse Tumbleweed (some roles supported) 21 | - MySQL 22 | - MariaDB >= 5.5.65, >= 10.1.45, >= 10.3.17 23 | - MySQL >= 5.7.31, >= 8.0.3 24 | - Nginx 1.0.16 or later 25 | - OpenSSH 5.3 and later 26 | 27 | The hardening is intended to be compliant with the Inspec DevSec Baselines: 28 | 29 | - https://github.com/dev-sec/linux-baseline 30 | - https://github.com/dev-sec/mysql-baseline 31 | - https://github.com/dev-sec/nginx-baseline 32 | - https://github.com/dev-sec/ssh-baseline 33 | 34 | ## Looking for the old ansible-os-hardening role? 35 | 36 | This role is now part of the hardening-collection. You can find the old role in the branch `legacy`. 37 | 38 | ## Minimum required Ansible-version 39 | 40 | - Ansible >= 2.9.10 41 | 42 | ## Included content 43 | 44 | - [os_hardening](roles/os_hardening/) 45 | - [mysql_hardening](roles/mysql_hardening/) 46 | - [nginx_hardening](roles/nginx_hardening/) 47 | - [ssh_hardening](roles/ssh_hardening/) 48 | 49 | In progress, not working: 50 | 51 | - [apache_hardening](roles/apache_hardening/) 52 | - [windows_hardening](roles/windows_hardening/) 53 | 54 | ## Installation 55 | 56 | Install the collection via ansible-galaxy: 57 | 58 | `ansible-galaxy collection install devsec.hardening` 59 | 60 | ## Using this collection 61 | 62 | Please refer to the examples in the readmes of the role. 63 | 64 | See [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details. 65 | 66 | ## Contributing to this collection 67 | 68 | See the [contributor guideline](CONTRIBUTING.md). 69 | 70 | ## Release notes 71 | 72 | See the [changelog](https://github.com/dev-sec/ansible-os-hardening/tree/master/CHANGELOG.md). 73 | 74 | ## Roadmap 75 | 76 | Todos: 77 | 78 | - Work on [apache_hardening](roles/apache_hardening/) and [windows_hardening](roles/windows_hardening/). 79 | - Add support for more operating systems, 80 | 81 | ## More information 82 | 83 | General information: 84 | 85 | - [Ansible Collection overview](https://github.com/ansible-collections/overview) 86 | - [Ansible User guide](https://docs.ansible.com/ansible/latest/user_guide/index.html) 87 | - [Ansible Developer guide](https://docs.ansible.com/ansible/latest/dev_guide/index.html) 88 | - [Ansible Collections Checklist](https://github.com/ansible-collections/overview/blob/master/collection_requirements.rst) 89 | - [Ansible Community code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) 90 | - [The Bullhorn (the Ansible Contributor newsletter)](https://us19.campaign-archive.com/home/?u=56d874e027110e35dea0e03c1&id=d6635f5420) 91 | - [Changes impacting Contributors](https://github.com/ansible-collections/overview/issues/45) 92 | 93 | ## Licensing 94 | 95 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 96 | 97 | http://www.apache.org/licenses/LICENSE-2.0 98 | 99 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 100 | -------------------------------------------------------------------------------- /roles/mysql_hardening/README.md: -------------------------------------------------------------------------------- 1 | # devsec.mysql_hardening 2 | 3 | ![devsec.mysql_hardening](https://github.com/dev-sec/ansible-os-hardening/workflows/devsec.mysql_hardening/badge.svg) 4 | 5 | ## Description 6 | 7 | This role provides security configurations for MySQL and its derivates. It is intended to set up production-ready MySQL instances that are configured with minimal surface for attackers. Furthermore it is intended to be compliant with the [DevSec MySQL Baseline](https://github.com/dev-sec/mysql-baseline). 8 | 9 | It configures: 10 | 11 | - Permissions for the various configuration files and folders 12 | - Removes anonymous users, users without a password or authentication_string and test databases 13 | - various hardening options inside MySQL 14 | 15 | ## Requirements 16 | 17 | - Ansible 2.9.0 18 | - An existing MySQL installation 19 | 20 | ### Example playbook 21 | 22 | ```yml 23 | - hosts: localhost 24 | collections: 25 | - devsec.hardening 26 | roles: 27 | - mysql_hardening 28 | ``` 29 | 30 | This role expects an existing installation of MySQL or MariaDB. Please ensure that the following variables are set accordingly: 31 | 32 | - `mysql_hardening_enabled: yes` role is enabled by default and can be disabled without removing it from a playbook. You can use conditional variable, for example: `mysql_hardening_enabled: "{{ true if mysql_enabled else false }}"` 33 | - `mysql_hardening_user: 'mysql'` The user that mysql runs as. 34 | - `mysql_datadir: '/var/lib/mysql'` The MySQL data directory 35 | - `mysql_hardening_mysql_hardening_conf_file: '/etc/mysql/conf.d/hardening.cnf'` The path to the configuration file where the hardening will be performed 36 | 37 | ## Role Variables 38 | 39 | - `mysql_hardening_chroot` 40 | - Default: "" 41 | - Description: [chroot](http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_chroot) 42 | - `mysql_hardening_options.safe-user-create` 43 | - Default: 1 44 | - Description: [safe-user-create](http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_safe-user-create) 45 | - `mysql_hardening_options.secure-auth` 46 | - Default: 1 47 | - Description: [secure-auth](http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_secure-auth) 48 | - `mysql_hardening_options.skip-symbolic-links` 49 | - Default: 1 50 | - Description: [skip-symbolic-links](http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_symbolic-links) 51 | - `mysql_hardening_skip_grant_tables:` 52 | - Default: false 53 | - Description: [skip-grant-tables](https://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_skip-grant-tables) 54 | - `mysql_hardening_skip_show_database` 55 | - Default: 1 56 | - Description: [skip-show-database](http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_skip-show-database) 57 | - `mysql_hardening_options.local-infile` 58 | - Default: 0 59 | - Description: [local-infile](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_local_infile) 60 | - `mysql_hardening_options.allow-suspicious-udfs` 61 | - Default: 0 62 | - Description: [allow-suspicious-udfs](https://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_allow-suspicious-udfs) 63 | - `mysql_hardening_chroot.automatic-sp-privileges` 64 | - Default: 0 65 | - Description: [automatic_sp_privileges](https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_automatic_sp_privileges) 66 | - `mysql_hardening_options.secure-file-priv` 67 | - Default: /tmp 68 | - Description: [secure-file-priv](https://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_secure-file-priv) 69 | - `mysql_allow_remote_root` 70 | - Default: false 71 | - Description: delete remote root users 72 | - `mysql_remove_anonymous_users` 73 | - Default: true 74 | - Description: remove users without authentication 75 | - `mysql_remove_test_database` 76 | - Default: true 77 | - Description: remove test database 78 | - `mysql_hardening_restart_mysql` 79 | - Default: true 80 | - Description: Restart mysql after running this role 81 | 82 | Further information is available at [Deutsche Telekom (German)](http://www.telekom.com/static/-/155996/7/technische-sicherheitsanforderungen-si) and [Symantec](http://www.symantec.com/connect/articles/securing-mysql-step-step) 83 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor Guideline 2 | 3 | This document provides an overview of how you can participate in improving this project or extending it. We are grateful for all your help: bug reports and fixes, code contributions, documentation or ideas. Feel free to join, we appreciate your support!! 4 | 5 | ## Communication 6 | 7 | ### GitHub repositories 8 | 9 | Much of the issues, goals and ideas are tracked in the respective projects in GitHub. Please use this channel to report bugs and post ideas. 10 | 11 | ## git and GitHub 12 | 13 | In order to contribute code please: 14 | 15 | 1. Fork the project on GitHub 16 | 2. Clone the project 17 | 3. Add changes (and tests) 18 | 4. Commit and push 19 | 5. Create a merge-request 20 | 21 | To have your code merged, see the expectations listed below. 22 | 23 | You can find a well-written guide [here](https://help.github.com/articles/fork-a-repo). 24 | 25 | Please follow common commit best-practices. Be explicit, have a short summary, a well-written description and references. This is especially important for the merge-request. 26 | 27 | Some great guidelines can be found [here](https://wiki.openstack.org/wiki/GitCommitMessages) and [here](http://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message). 28 | 29 | ## Expectations 30 | 31 | ### Don't reinvent the wheel 32 | 33 | This hardening project doesn't intend to reinvent the configuration stack for services. Aim to use official configuration projects first and provide hardening as a layer on top. The goal is remove the need for a user to configure all aspects of services and maintain security configuration. This way, the user can still configure a service using the interface provided by the official project. 34 | 35 | - For Chef refer to the official [opscode community cookbooks](http://community.opscode.com/cookbooks). 36 | - For Puppet head to the [Puppet Forge](https://forge.puppetlabs.com/) and take a node of the Puppet supported modules. 37 | - For Ansible check the [Ansible Module Index](http://docs.ansible.com/list_of_all_modules.html) 38 | 39 | These projects are generally hosted on GitHub as well. 40 | 41 | In some cases, we in fact create the full rollout stack, but this is generally the exception ([os-hardening](https://github.com/TelekomLabs/chef-os-hardening), [nginx-hardening](https://github.com/TelekomLabs/chef-nginx-hardening)). 42 | 43 | ### Be explicit 44 | 45 | - Please avoid using nonsensical property and variable names. 46 | - Use self-describing attribute names for user configuration. 47 | - In case of failures, communicate what happened and why a failure occurs to the user. Make it easy to track the code or action that produced the error. Try to catch and handle errors if possible to provide improved failure messages. 48 | 49 | ### Add tests 50 | 51 | The security review of this project is done using integration tests. 52 | 53 | Whenever you add a new security configuration, please start by writing a test that checks for this configuration. For example: If you want to set a new attribute in a configuration file, write a test that expects the value to be set first. Then implement your change. 54 | 55 | You may add a new feature request by creating a test for whatever value you need. 56 | 57 | All tests will be reviewed internally for their validity and overall project direction. 58 | 59 | ### Document your code 60 | 61 | As code is more often read than written, please provide documentation in all projects. 62 | 63 | Adhere to the respective guidelines for documentation: 64 | 65 | - Chef generally documents code based explicit readme files. For code documentation please use [yard-chef](https://github.com/rightscale/yard-chef) 66 | - [Puppet module documentation](http://docs.puppetlabs.com/puppet/latest/reference/modules_documentation.html) 67 | 68 | ### Follow coding styles 69 | 70 | We generally include test for coding guidelines: 71 | 72 | - Chef follows [Foodcritic](http://acrmp.github.io/foodcritic/) 73 | - Puppet is checked with [puppet-lint](http://puppet-lint.com/checks/) 74 | - Ansible is checked by running the playbook with the syntax-check option, e.g. `ansible-playbook foo.yml --syntax-check` 75 | 76 | Remember: Code is generally read much more often than written. 77 | 78 | ### Use Markdown 79 | 80 | Wherever possible, please refrain from any other formats and stick to simple markdown. 81 | -------------------------------------------------------------------------------- /roles/os_hardening/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # SYSTEM CONFIGURATION 3 | # ==================== 4 | # These are not meant to be modified by the user 5 | 6 | # suid and sgid blacklists and whitelists 7 | # --------------------------------------- 8 | # don't change values in the system_blacklist/whitelist 9 | # adjust values for blacklist/whitelist instead, they can override system_blacklist/whitelist 10 | 11 | # list of suid/sgid entries that must be removed 12 | os_security_suid_sgid_system_blacklist: 13 | # blacklist as provided by NSA 14 | - '/usr/bin/rcp' 15 | - '/usr/bin/rlogin' 16 | - '/usr/bin/rsh' 17 | # sshd must not use host-based authentication (see ssh cookbook) 18 | - '/usr/libexec/openssh/ssh-keysign' 19 | - '/usr/lib/openssh/ssh-keysign' 20 | # misc others 21 | - '/sbin/netreport' # not normally required for user 22 | - '/usr/sbin/usernetctl' # modify interfaces via functional accounts 23 | # connecting to ... 24 | - '/usr/sbin/userisdnctl' # no isdn... 25 | - '/usr/sbin/pppd' # no ppp / dsl ... 26 | # lockfile 27 | - '/usr/bin/lockfile' 28 | - '/usr/bin/mail-lock' 29 | - '/usr/bin/mail-unlock' 30 | - '/usr/bin/mail-touchlock' 31 | - '/usr/bin/dotlockfile' 32 | # need more investigation blacklist for now 33 | - '/usr/bin/arping' 34 | - '/usr/sbin/uuidd' 35 | - '/usr/bin/mtr' # investigate current state... 36 | - '/usr/lib/evolution/camel-lock-helper-1.2' # investigate current state... 37 | - '/usr/lib/pt_chown' # pseudo-tty needed? 38 | - '/usr/lib/eject/dmcrypt-get-device' 39 | - '/usr/lib/mc/cons.saver' # midnight commander screensaver 40 | 41 | # list of suid/sgid entries that can remain untouched 42 | os_security_suid_sgid_system_whitelist: 43 | # whitelist as provided by NSA 44 | - '/bin/mount' 45 | - '/bin/ping' 46 | - '/bin/su' 47 | - '/usr/bin/su' 48 | - '/bin/umount' 49 | - '/sbin/pam_timestamp_check' 50 | - '/sbin/unix_chkpwd' 51 | - '/usr/bin/at' 52 | - '/usr/bin/gpasswd' 53 | - '/usr/bin/locate' 54 | - '/usr/bin/newgrp' 55 | - '/usr/bin/passwd' 56 | - '/usr/bin/ssh-agent' 57 | - '/usr/libexec/utempter/utempter' 58 | - '/usr/sbin/lockdev' 59 | - '/usr/sbin/sendmail.sendmail' 60 | - '/usr/bin/expiry' 61 | # whitelist ipv6 62 | - '/bin/ping6' 63 | - '/usr/bin/traceroute6.iputils' 64 | # whitelist nfs 65 | - '/sbin/mount.nfs' 66 | - '/sbin/umount.nfs' 67 | # whitelist nfs4 68 | - '/sbin/mount.nfs4' 69 | - '/sbin/umount.nfs4' 70 | # whitelist cron 71 | - '/usr/bin/crontab' 72 | # whitelist consolemssaging 73 | - '/usr/bin/wall' 74 | - '/usr/bin/write' 75 | # whitelist: only SGID with utmp group for multi-session access 76 | # impact is limited; installation/usage has some remaining risk 77 | - '/usr/bin/screen' 78 | # whitelist locate 79 | - '/usr/bin/mlocate' 80 | # whitelist usermanagement 81 | - '/usr/bin/chage' 82 | - '/usr/bin/chfn' 83 | - '/usr/bin/chsh' 84 | # whitelist fuse 85 | - '/bin/fusermount' 86 | # whitelist pkexec 87 | - '/usr/bin/pkexec' 88 | # whitelist sudo 89 | - '/usr/bin/sudo' 90 | - '/usr/bin/sudoedit' 91 | # whitelist postfix 92 | - '/usr/sbin/postdrop' 93 | - '/usr/sbin/postqueue' 94 | # whitelist apache 95 | - '/usr/sbin/suexec' 96 | # whitelist squid 97 | - '/usr/lib/squid/ncsa_auth' 98 | - '/usr/lib/squid/pam_auth' 99 | # whitelist kerberos 100 | - '/usr/kerberos/bin/ksu' 101 | # whitelist pam_caching 102 | - '/usr/sbin/ccreds_validate' 103 | # whitelist Xorg 104 | - '/usr/bin/Xorg' # xorg 105 | - '/usr/bin/X' # xorg 106 | - '/usr/lib/dbus-1.0/dbus-daemon-launch-helper' # freedesktop ipc 107 | - '/usr/lib/vte/gnome-pty-helper' # gnome 108 | - '/usr/lib/libvte9/gnome-pty-helper' # gnome 109 | - '/usr/lib/libvte-2.90-9/gnome-pty-helper' # gnome 110 | 111 | # system accounts that do not get their login disabled and pasword changed 112 | os_always_ignore_users: ['root', 'sync', 'shutdown', 'halt'] 113 | -------------------------------------------------------------------------------- /roles/mysql_hardening/tasks/mysql_secure_installation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Fail the role if the mysql root password was not set 3 | fail: 4 | msg: 'ERROR - you have to change default mysql_root_password' 5 | when: mysql_root_password == '-----====>SetR00tPa$$wordH3r3!!!<====-----' 6 | 7 | - name: ensure that the root password is present 8 | community.mysql.mysql_user: 9 | name: 'root' 10 | host_all: true 11 | password: '{{ mysql_root_password | mandatory }}' 12 | state: present 13 | login_unix_socket: "{{ login_unix_socket | default(omit) }}" 14 | 15 | - name: Install .my.cnf with credentials 16 | template: 17 | src: 'my.cnf.j2' 18 | dest: '{{ mysql_user_home }}/.my.cnf' 19 | mode: '0400' 20 | tags: my_cnf 21 | 22 | - name: ensure that the test database is absent 23 | community.mysql.mysql_db: 24 | name: test 25 | state: absent 26 | login_unix_socket: "{{ login_unix_socket | default(omit) }}" 27 | when: mysql_remove_test_database 28 | 29 | - name: ensure that anonymous users are absent 30 | community.mysql.mysql_user: 31 | name: '' 32 | state: absent 33 | host_all: true 34 | login_unix_socket: "{{ login_unix_socket | default(omit) }}" 35 | when: mysql_remove_anonymous_users 36 | 37 | - name: ensure that root can only login from localhost 38 | community.mysql.mysql_query: 39 | query: 40 | - DELETE 41 | FROM mysql.user 42 | WHERE USER='root' 43 | AND HOST NOT IN ('localhost', 44 | '127.0.0.1', 45 | '::1') 46 | login_unix_socket: "{{ login_unix_socket | default(omit) }}" 47 | when: mysql_remove_remote_root 48 | 49 | - name: get all users that have no authentication_string on MySQL version >= 5.7.6 or Mariadb version >= 10.4.0 50 | community.mysql.mysql_query: 51 | query: 52 | - SELECT GROUP_CONCAT(QUOTE(USER), '@', QUOTE(HOST) SEPARATOR ', ') AS users 53 | FROM mysql.user 54 | WHERE (length(authentication_string)=0 55 | OR authentication_string="") 56 | AND USER NOT IN ('mysql.sys', 57 | 'mysqlxsys', 58 | 'mariadb.sys'); 59 | login_unix_socket: "{{ login_unix_socket | default(omit) }}" 60 | register: mysql_users_wo_passwords_or_auth_string 61 | when: 62 | - (mysql_distribution == "mysql" and mysql_version.version.full is version('5.7.6', '>=')) or 63 | (mysql_distribution == "mariadb" and mysql_version.version.full is version('10.4.0', '>=')) 64 | 65 | - name: get all users that have no password or authentication_string on MySQL version < 5.7.6 or Mariadb version < 10.4.0 66 | community.mysql.mysql_query: 67 | query: 68 | - SELECT GROUP_CONCAT(QUOTE(USER), '@', QUOTE(HOST) SEPARATOR ', ') AS users 69 | FROM mysql.user 70 | WHERE (length(password)=0 71 | OR password="") 72 | AND (length(authentication_string)=0 73 | OR authentication_string="") 74 | AND USER NOT IN ('mysql.sys', 75 | 'mysqlxsys', 76 | 'mariadb.sys'); 77 | login_unix_socket: "{{ login_unix_socket | default(omit) }}" 78 | register: mysql_users_wo_passwords 79 | when: 80 | - (mysql_distribution == "mysql" and mysql_version.version.full is version('5.7.6', '<')) or 81 | (mysql_distribution == "mariadb" and mysql_version.version.full is version('10.4.0', '<')) 82 | 83 | - name: create a fact for users without password or authentication_string 84 | set_fact: 85 | users_wo_auth: "{{ mysql_users_wo_passwords_or_auth_string.query_result.0.0 | community.general.json_query('users') }}" 86 | when: 87 | - mysql_users_wo_passwords_or_auth_string.query_result is defined 88 | - mysql_users_wo_passwords_or_auth_string.query_result != "" # noqa empty-string-compare 89 | 90 | - name: create a fact for users without password 91 | set_fact: 92 | users_wo_auth: "{{ mysql_users_wo_passwords.query_result.0.0 | community.general.json_query('users') }}" 93 | when: 94 | - mysql_users_wo_passwords.query_result is defined 95 | - mysql_users_wo_passwords.query_result != "" # noqa empty-string-compare 96 | 97 | - name: ensure that there are no users without password or authentication_string 98 | community.mysql.mysql_query: 99 | query: 100 | - "DROP USER {{ users_wo_auth }}" 101 | login_unix_socket: "{{ login_unix_socket | default(omit) }}" 102 | when: 103 | - users_wo_auth is defined 104 | - users_wo_auth != "" # noqa empty-string-compare 105 | -------------------------------------------------------------------------------- /roles/ssh_hardening/tasks/hardening.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Fetch OS dependent variables 3 | include_vars: 4 | file: '{{ item }}' 5 | name: 'os_vars' 6 | with_first_found: 7 | - files: 8 | - '{{ ansible_facts.distribution }}_{{ ansible_facts.distribution_major_version }}.yml' 9 | - '{{ ansible_facts.distribution }}.yml' 10 | - '{{ ansible_facts.os_family }}_{{ ansible_facts.distribution_major_version }}.yml' 11 | - '{{ ansible_facts.os_family }}.yml' 12 | skip: true 13 | tags: always 14 | 15 | # we only override variables with our default, if they have not been specified already 16 | # by default the lookup functions finds all varnames containing the string, therefore 17 | # we add ^ and $ to denote start and end of string, so this returns only exact matches 18 | - name: Set OS dependent variables, if not already defined by user 19 | set_fact: 20 | '{{ item.key }}': '{{ item.value }}' 21 | when: "not lookup('varnames', '^' + item.key + '$')" 22 | with_dict: '{{ os_vars }}' 23 | tags: always 24 | 25 | - name: Get openssh-version 26 | command: ssh -V 27 | register: sshd_version_raw 28 | changed_when: false 29 | check_mode: false 30 | 31 | - name: Parse openssh-version 32 | set_fact: 33 | sshd_version: "{{ sshd_version_raw.stderr | regex_replace('.*_([0-9]*.[0-9]).*', '\\1') }}" 34 | 35 | - name: Set default for ssh_host_key_files if not supplied 36 | include_tasks: crypto_hostkeys.yml 37 | when: 38 | - ssh_server_hardening | bool 39 | - not ssh_host_key_files 40 | 41 | - name: Set default for ssh_macs if not supplied 42 | include_tasks: crypto_macs.yml 43 | when: not ssh_macs 44 | 45 | - name: Set default for ssh_ciphers if not supplied 46 | include_tasks: crypto_ciphers.yml 47 | when: not ssh_ciphers 48 | 49 | - name: Set default for ssh_kex if not supplied 50 | include_tasks: crypto_kex.yml 51 | when: not ssh_kex 52 | 53 | - name: Create revoked_keys and set permissions to root/600 54 | template: 55 | src: 'revoked_keys.j2' 56 | dest: '/etc/ssh/revoked_keys' 57 | mode: '0600' 58 | owner: '{{ ssh_owner }}' 59 | group: '{{ ssh_group }}' 60 | notify: Restart sshd 61 | when: ssh_server_hardening | bool 62 | 63 | - name: Create sshd_config and set permissions to root/600 64 | template: 65 | src: 'opensshd.conf.j2' 66 | dest: "{{ ssh_server_config_file }}" 67 | mode: '0600' 68 | owner: '{{ ssh_owner }}' 69 | group: '{{ ssh_group }}' 70 | validate: '{{ sshd_path }} -T -C user=root -C host=localhost -C addr=localhost -C lport=22 -f %s' 71 | notify: Restart sshd 72 | when: ssh_server_hardening | bool 73 | 74 | - name: Disable dynamic MOTD 75 | pamd: 76 | name: sshd 77 | type: session 78 | control: optional 79 | module_path: pam_motd.so 80 | state: absent 81 | backup: true 82 | when: 83 | - ssh_server_hardening | bool 84 | - ssh_pam_support | bool 85 | - not (ssh_print_pam_motd | bool) 86 | 87 | - name: Create ssh_config and set permissions to root/644 88 | template: 89 | src: 'openssh.conf.j2' 90 | dest: "{{ ssh_client_config_file }}" 91 | mode: '0644' 92 | owner: '{{ ssh_owner }}' 93 | group: '{{ ssh_group }}' 94 | when: ssh_client_hardening | bool 95 | 96 | - name: Check if {{ sshd_moduli_file }} contains weak DH parameters 97 | shell: awk '$5 < {{ sshd_moduli_minimum }}' {{ sshd_moduli_file }} 98 | register: sshd_register_moduli 99 | changed_when: false 100 | check_mode: false 101 | when: ssh_server_hardening | bool 102 | 103 | - name: Remove all small primes 104 | shell: awk '$5 >= {{ sshd_moduli_minimum }}' {{ sshd_moduli_file }} > {{ sshd_moduli_file }}.new ; 105 | [ -r {{ sshd_moduli_file }}.new -a -s {{ sshd_moduli_file }}.new ] && mv {{ sshd_moduli_file }}.new {{ sshd_moduli_file }} || true 106 | notify: Restart sshd 107 | when: 108 | - ssh_server_hardening | bool 109 | - sshd_register_moduli.stdout 110 | 111 | - name: Include tasks to setup ca keys and principals 112 | include_tasks: ca_keys_and_principals.yml 113 | when: ssh_trusted_user_ca_keys_file | length > 0 114 | 115 | - name: Include selinux specific tasks 116 | include_tasks: selinux.yml 117 | when: ansible_facts.selinux and ansible_facts.selinux.status == "enabled" 118 | 119 | - name: Gather package facts 120 | package_facts: 121 | check_mode: false 122 | when: 123 | - sshd_disable_crypto_policy | bool 124 | 125 | - name: Disable SSH server CRYPTO_POLICY 126 | copy: 127 | src: sshd 128 | dest: /etc/sysconfig/sshd 129 | owner: 'root' 130 | group: 'root' 131 | mode: '0640' 132 | when: 133 | - sshd_disable_crypto_policy | bool 134 | - ('crypto-policies' in ansible_facts.packages) 135 | -------------------------------------------------------------------------------- /roles/ssh_hardening/templates/openssh.conf.j2: -------------------------------------------------------------------------------- 1 | #jinja2: trim_blocks: "true", lstrip_blocks: "true" 2 | {{ ansible_managed | comment }} 3 | # Generated by Ansible role {{ ansible_role_name }} 4 | 5 | # This is the ssh client system-wide configuration file. 6 | # See ssh_config(5) for more information on any settings used. 7 | 8 | {% if ssh_custom_options %} 9 | # Custom configuration that overwrites default configuration 10 | # ========================================================== 11 | {% for line in ssh_custom_options %} 12 | {{ line }} 13 | {% endfor %} 14 | {% endif %} 15 | 16 | # Basic configuration 17 | # =================== 18 | 19 | # Address family should always be limited to the active network configuration. 20 | AddressFamily {{ 'any' if network_ipv6_enable else 'inet' }} 21 | 22 | {% for host in ssh_remote_hosts %} 23 | {% if loop.first %} 24 | # Host-specific configuration 25 | {% endif %} 26 | Host {{ host.names | join(' ') }} 27 | {{ host.options | join('\n') | indent(2) }} 28 | 29 | {% endfor %} 30 | # Global defaults for all Hosts 31 | Host * 32 | 33 | # The port at the destination should be defined 34 | Port {{ ssh_client_port }} 35 | 36 | # Identity file configuration. You may restrict available identity files. 37 | # Otherwise ssh will search for a pattern and use any that matches. 38 | #IdentityFile ~/.ssh/identity 39 | #IdentityFile ~/.ssh/id_rsa 40 | #IdentityFile ~/.ssh/id_dsa 41 | 42 | 43 | # Security configuration 44 | # ====================== 45 | 46 | {# Support for legacy SSHv1 has been completely removed from OpenSSH in version 7.6 -#} 47 | {% if sshd_version is version('7.6', '<') %} 48 | # Set the protocol version explicitly to 2. Version 1 is obsolete and should not be used. 49 | Protocol 2 50 | 51 | {% endif %} 52 | # Make sure passphrase querying is enabled 53 | BatchMode no 54 | 55 | # Prevent IP spoofing by checking to host IP against the `known_hosts` file. 56 | CheckHostIP yes 57 | 58 | # Always ask before adding keys to the `known_hosts` file. Do not set to `yes`. 59 | StrictHostKeyChecking ask 60 | 61 | # **Ciphers** -- If your clients don't support CTR (eg older versions), cbc will be added 62 | # CBC: is true if you want to connect with OpenSSL-base libraries 63 | # eg ruby Net::SSH::Transport::CipherFactory requires cbc-versions of the given openssh ciphers to work 64 | # -- see: (http://net-ssh.github.com/net-ssh/classes/Net/SSH/Transport/CipherFactory.html) 65 | # 66 | {# This outputs 'Ciphers ' if ssh_ciphers is defined or '#Ciphers' if ssh_ciphers is undefined -#} 67 | {{ 'Ciphers ' ~ ssh_ciphers|join(',') if ssh_ciphers else 'Ciphers'|comment }} 68 | 69 | # **Hash algorithms** -- Make sure not to use SHA1 for hashing, unless it is really necessary. 70 | # Weak HMAC is sometimes required if older package versions are used 71 | # eg Ruby's Net::SSH at around 2.2.* doesn't support sha2 for hmac, so this will have to be set true in this case. 72 | # 73 | {# This outputs 'MACs ' if ssh_macs is defined or '#MACs' if ssh_macs is undefined -#} 74 | {{ 'MACs ' ~ ssh_macs|join(',') if ssh_macs else 'MACs'|comment }} 75 | 76 | # **Key Exchange Algorithms** -- Make sure not to use SHA1 for kex, unless it is really necessary 77 | # Weak kex is sometimes required if older package versions are used 78 | # eg ruby's Net::SSH at around 2.2.* doesn't support sha2 for kex, so this will have to be set true in this case. 79 | # based on: https://bettercrypto.org/static/applied-crypto-hardening.pdf 80 | # 81 | {# This outputs 'KexAlgorithms ' if ssh_kex is defined or '#KexAlgorithms' if ssh_kex is undefined #} 82 | {{ 'KexAlgorithms ' ~ ssh_kex|join(',') if ssh_kex else 'KexAlgorithms'|comment }} 83 | 84 | # Specifies the host key algorithms that the client wants to use in order of preference. 85 | {{ "HostKeyAlgorithms " ~ ssh_client_host_key_algorithms|join(',') if ssh_client_host_key_algorithms else "HostKeyAlgorithms"|comment }} 86 | 87 | {% if sshd_version is version('5.9', '<') %} 88 | # Alternative setting, if OpenSSH version is below v5.9 89 | #MACs hmac-ripemd160 90 | 91 | {% endif %} 92 | # Disable agent forwarding, since local agent could be accessed through forwarded connection. 93 | ForwardAgent no 94 | 95 | # Disable X11 forwarding, since local X11 display could be accessed through forwarded connection. 96 | ForwardX11 no 97 | 98 | # Never use host-based authentication. It can be exploited. 99 | HostbasedAuthentication no 100 | {% if sshd_version is version('7.6', '<') %} 101 | RhostsRSAAuthentication no 102 | # Enable RSA authentication via identity files. 103 | RSAAuthentication yes 104 | {% endif %} 105 | 106 | # Disable password-based authentication, it can allow for potentially easier brute-force attacks. 107 | PasswordAuthentication {{ 'yes' if ssh_client_password_login else 'no' }} 108 | 109 | # Only use GSSAPIAuthentication if implemented on the network. 110 | GSSAPIAuthentication {{ 'yes' if (ssh_gssapi_support|bool) else 'no' }} 111 | GSSAPIDelegateCredentials {{ 'yes' if (ssh_gssapi_delegation|bool) else 'no' }} 112 | 113 | # Disable tunneling 114 | Tunnel no 115 | 116 | # Disable local command execution. 117 | PermitLocalCommand no 118 | 119 | 120 | # Misc. configuration 121 | # =================== 122 | 123 | Compression {{ 'yes' if (ssh_client_compression|bool) else 'no' }} 124 | 125 | #EscapeChar ~ 126 | #VisualHostKey yes 127 | 128 | {% if sshd_version is version('7.1', '<=') %} 129 | # Disable experimental client roaming. 130 | # This is known to cause potential issues with secrets being disclosed to malicious servers. Disabled by default. 131 | UseRoaming {{ 'yes' if ssh_client_roaming else 'no' }} 132 | {% endif %} 133 | -------------------------------------------------------------------------------- /roles/nginx_hardening/README.md: -------------------------------------------------------------------------------- 1 | # devsec.nginx_hardening 2 | 3 | ![devsec.nginx_hardening](https://github.com/dev-sec/ansible-os-hardening/workflows/devsec.nginx_hardening/badge.svg) 4 | 5 | ## Description 6 | 7 | This role provides secure nginx configuration. It is intended to be compliant with the [DevSec Nginx Baseline](https://github.com/dev-sec/nginx-baseline). 8 | 9 | It works with the following nginx-roles, including, but not limited to: 10 | 11 | - [geerlingguy.nginx](https://galaxy.ansible.com/geerlingguy/nginx/) 12 | - [nginxinc.nginx](https://galaxy.ansible.com/nginxinc/nginx) 13 | - [jdauphant.nginx](https://galaxy.ansible.com/jdauphant/nginx/) 14 | - [franklinkim.nginx](https://galaxy.ansible.com/franklinkim/nginx/) 15 | 16 | **NOTE: This role does not work with nginx 1.0.15 or older! Please use the latest version from the official nginx repositories!** 17 | 18 | ## Requirements 19 | 20 | - Ansible >= 2.9 21 | 22 | ## Role Variables 23 | 24 | - [nginx_client_body_buffer_size][] 25 | - Default: `1k` 26 | - Description: Sets buffer size for reading client request body. In case the request body is larger than the buffer, the whole body or only its part is written to a temporary file. 27 | - nginx_remove_default_site 28 | - Default: `true` 29 | - Description: Disables the default site. Set to false to enable the default site in nginx. 30 | - [nginx_client_max_body_size][] 31 | - Default: `1k` 32 | - Description: Sets the maximum allowed size of the client request body, specified in the “Content-Length” request header field. If the size in a request exceeds the configured value, the 41 33 | 3 (Request Entity Too Large) error is returned to the client. 34 | - [nginx_keepalive_timeout][] 35 | - Default: `5 5` 36 | - Description: The first parameter sets a timeout during which a keep-alive client connection will stay open on the server side. The zero value disables keep-alive client connections. The op 37 | tional second parameter sets a value in the “Keep-Alive: timeout=time” response header field. 38 | - [nginx_server_tokens][] 39 | - Default: `off` 40 | - Description: Disables emitting nginx version in error messages and in the "Server" response header field. Set to on to enable the nginx version in error messages and "Server" response head 41 | er. 42 | - [nginx_client_header_buffer_size][] 43 | - Default: `1k` 44 | - Description: Sets buffer size for reading client request header. For most requests, a buffer of 1K bytes is enough. 45 | - [nginx_large_client_header_buffers][] 46 | - Default: `2 1k` 47 | - Description: Sets the maximum number and size of buffers used for reading large client request header. 48 | - [nginx_client_body_timeout][] 49 | - Default: `10` 50 | - Description: Defines a timeout for reading client request body. 51 | - [nginx_client_header_timeout][] 52 | - Default: `10` 53 | - Description: Defines a timeout for reading client request header. 54 | - [nginx_send_timeout][] 55 | - Default: `10` 56 | - Description: Sets a timeout for transmitting a response to the client. 57 | - [nginx_limit_conn_zone][] 58 | - Default: `$binary_remote_addr zone=default:10m` 59 | - Description: Sets parameters for a shared memory zone that will keep states for various keys. 60 | - [nginx_limit_conn][] 61 | - Default: `default 5` 62 | - Description: Sets the shared memory zone and the maximum allowed number of connections for a given key value. 63 | - [nginx_add_header][] 64 | - Default: `[ "X-Frame-Options SAMEORIGIN", "X-Content-Type-Options nosniff", "X-XSS-Protection \"1; mode=block\"" ]` 65 | - Description:Adds the specified field to a response header provided that the response code equals 200, 201, 204, 206, 301, 302, 303, 304, or 307. 66 | - [nginx_ssl_protocols][] 67 | - Default: `TLSv1.2` 68 | - Description: Specifies the SSL protocol which should be used. 69 | - [nginx_ssl_ciphers][] 70 | - Default: _see defaults.yml_ 71 | - Description: Specifies the TLS ciphers which should be used. 72 | - [nginx_ssl_prefer_server_ciphers][] 73 | - Default: `on` 74 | - Description: Specifies that server ciphers should be preferred over client ciphers when using the TLS protocols. Set to false to disable it. 75 | - [nginx_dh_size][] 76 | - Default: `2048` 77 | - Description: Specifies the length of DH parameters for EDH ciphers. 78 | 79 | ## Example Playbook 80 | 81 | ``` 82 | - hosts: localhost 83 | collections: 84 | - devsec.hardening 85 | roles: 86 | - nginx_hardening 87 | ``` 88 | 89 | [nginx_client_body_buffer_size]: http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size 90 | [nginx_client_max_body_size]: http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size 91 | [nginx_keepalive_timeout]: http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_timeout 92 | [nginx_server_tokens]: http://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens 93 | [nginx_more_clear_headers]: http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header 94 | [nginx_client_header_buffer_size]: http://nginx.org/en/docs/http/ngx_http_core_module.html#client_header_buffer_size 95 | [nginx_large_client_header_buffers]: http://nginx.org/en/docs/http/ngx_http_core_module.html#large_client_header_buffers 96 | [nginx_client_body_timeout]: http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_timeout 97 | [nginx_client_header_timeout]: http://nginx.org/en/docs/http/ngx_http_core_module.html#client_header_timeout 98 | [nginx_send_timeout]: http://nginx.org/en/docs/http/ngx_http_core_module.html#send_timeout 99 | [nginx_limit_conn_zone]: http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html#limit_conn_zone 100 | [nginx_limit_conn]: http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html#limit_conn 101 | [nginx_add_header]: http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header 102 | [nginx_ssl_protocols]: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols 103 | [nginx_ssl_ciphers]: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ciphers 104 | [nginx_ssl_prefer_server_ciphers]: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_prefer_server_ciphers 105 | [nginx_dh_size]: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_dhparam 106 | -------------------------------------------------------------------------------- /roles/mysql_hardening/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.2.2](https://github.com/dev-sec/ansible-mysql-hardening/tree/2.2.2) (2020-10-18) 4 | 5 | [Full Changelog](https://github.com/dev-sec/ansible-mysql-hardening/compare/2.2.1...2.2.2) 6 | 7 | **Implemented enhancements:** 8 | 9 | - Use mysql_query module instead of temporary files [\#56](https://github.com/dev-sec/ansible-mysql-hardening/pull/56) ([szEvEz](https://github.com/szEvEz)) 10 | - update readme to new layout for vars [\#53](https://github.com/dev-sec/ansible-mysql-hardening/pull/53) ([rndmh3ro](https://github.com/rndmh3ro)) 11 | - add mode to copy task [\#51](https://github.com/dev-sec/ansible-mysql-hardening/pull/51) ([rndmh3ro](https://github.com/rndmh3ro)) 12 | - Fix linting errors and simplivy vars-handling [\#49](https://github.com/dev-sec/ansible-mysql-hardening/pull/49) ([rndmh3ro](https://github.com/rndmh3ro)) 13 | 14 | **Fixed bugs:** 15 | 16 | - permissions on /etc/mysql/mysql.cnf too restrictive [\#35](https://github.com/dev-sec/ansible-mysql-hardening/issues/35) 17 | - fix missing variables [\#55](https://github.com/dev-sec/ansible-mysql-hardening/pull/55) ([rndmh3ro](https://github.com/rndmh3ro)) 18 | - add modes to template and file tasks [\#50](https://github.com/dev-sec/ansible-mysql-hardening/pull/50) ([rndmh3ro](https://github.com/rndmh3ro)) 19 | 20 | **Closed issues:** 21 | 22 | - Use mysql_query module instead of temporary files [\#52](https://github.com/dev-sec/ansible-mysql-hardening/issues/52) 23 | - Add Centos 8 support for ansible-mysql-hardening [\#36](https://github.com/dev-sec/ansible-mysql-hardening/issues/36) 24 | 25 | **Merged pull requests:** 26 | 27 | - Run ansible-lint via github action [\#58](https://github.com/dev-sec/ansible-mysql-hardening/pull/58) ([szEvEz](https://github.com/szEvEz)) 28 | - Fix kitchen and travisci for major distros [\#57](https://github.com/dev-sec/ansible-mysql-hardening/pull/57) ([szEvEz](https://github.com/szEvEz)) 29 | - update testing [\#48](https://github.com/dev-sec/ansible-mysql-hardening/pull/48) ([rndmh3ro](https://github.com/rndmh3ro)) 30 | 31 | ## [2.2.1](https://github.com/dev-sec/ansible-mysql-hardening/tree/2.2.1) (2020-06-06) 32 | 33 | [Full Changelog](https://github.com/dev-sec/ansible-mysql-hardening/compare/2.2.0...2.2.1) 34 | 35 | **Implemented enhancements:** 36 | 37 | - unify changelog and release actions [\#46](https://github.com/dev-sec/ansible-mysql-hardening/pull/46) ([rndmh3ro](https://github.com/rndmh3ro)) 38 | 39 | ## [2.2.0](https://github.com/dev-sec/ansible-mysql-hardening/tree/2.2.0) (2020-05-09) 40 | 41 | [Full Changelog](https://github.com/dev-sec/ansible-mysql-hardening/compare/2.1.0...2.2.0) 42 | 43 | **Implemented enhancements:** 44 | 45 | - add changelog and release workflow [\#45](https://github.com/dev-sec/ansible-mysql-hardening/pull/45) ([rndmh3ro](https://github.com/rndmh3ro)) 46 | - Use python3-mysqldb for Ubuntu 20.04 [\#44](https://github.com/dev-sec/ansible-mysql-hardening/pull/44) ([shadinaif](https://github.com/shadinaif)) 47 | - add ansible-lint [\#43](https://github.com/dev-sec/ansible-mysql-hardening/pull/43) ([rndmh3ro](https://github.com/rndmh3ro)) 48 | 49 | ## [2.1.0](https://github.com/dev-sec/ansible-mysql-hardening/tree/2.1.0) (2019-10-17) 50 | 51 | [Full Changelog](https://github.com/dev-sec/ansible-mysql-hardening/compare/2.0.0...2.1.0) 52 | 53 | **Implemented enhancements:** 54 | 55 | - Add support for Debian Buster in ansible-mysql-hardening [\#37](https://github.com/dev-sec/ansible-mysql-hardening/issues/37) 56 | - Update readme to include baselines [\#28](https://github.com/dev-sec/ansible-mysql-hardening/issues/28) 57 | - migrate to new inspec test suite [\#25](https://github.com/dev-sec/ansible-mysql-hardening/issues/25) 58 | - use bool filter on bare variable to address Ansible 2.8 deprecation warning [\#40](https://github.com/dev-sec/ansible-mysql-hardening/pull/40) ([deefour](https://github.com/deefour)) 59 | - Add test support for Debian Buster [\#38](https://github.com/dev-sec/ansible-mysql-hardening/pull/38) ([cnkk](https://github.com/cnkk)) 60 | - remove eol'd OS and add new [\#34](https://github.com/dev-sec/ansible-mysql-hardening/pull/34) ([rndmh3ro](https://github.com/rndmh3ro)) 61 | - replace iteritems with items for python3 support [\#33](https://github.com/dev-sec/ansible-mysql-hardening/pull/33) ([rndmh3ro](https://github.com/rndmh3ro)) 62 | - make mysql daemon enabling configurable [\#30](https://github.com/dev-sec/ansible-mysql-hardening/pull/30) ([rndmh3ro](https://github.com/rndmh3ro)) 63 | 64 | **Fixed bugs:** 65 | 66 | - Template fails to render with Python 3 [\#32](https://github.com/dev-sec/ansible-mysql-hardening/issues/32) 67 | - my.cnf symlink turns into None? [\#24](https://github.com/dev-sec/ansible-mysql-hardening/issues/24) 68 | 69 | ## [2.0.0](https://github.com/dev-sec/ansible-mysql-hardening/tree/2.0.0) (2017-05-07) 70 | 71 | [Full Changelog](https://github.com/dev-sec/ansible-mysql-hardening/compare/1.0.0...2.0.0) 72 | 73 | **Implemented enhancements:** 74 | 75 | - Add CentOS7 with MariaDB support [\#23](https://github.com/dev-sec/ansible-mysql-hardening/issues/23) 76 | - Fix ansible.cfg settings [\#29](https://github.com/dev-sec/ansible-mysql-hardening/pull/29) ([fazlearefin](https://github.com/fazlearefin)) 77 | - Add CentOS7 with MariaDB support [\#27](https://github.com/dev-sec/ansible-mysql-hardening/pull/27) ([chrispoupart](https://github.com/chrispoupart)) 78 | - - renamed 'mysql_hardening_mysql_conf' var to 'mysql_hardening_mysql\_… [\#22](https://github.com/dev-sec/ansible-mysql-hardening/pull/22) ([agno01](https://github.com/agno01)) 79 | 80 | **Fixed bugs:** 81 | 82 | - error on task protect my.cnf [\#20](https://github.com/dev-sec/ansible-mysql-hardening/issues/20) 83 | 84 | **Merged pull requests:** 85 | 86 | - use new docker files [\#26](https://github.com/dev-sec/ansible-mysql-hardening/pull/26) ([rndmh3ro](https://github.com/rndmh3ro)) 87 | 88 | ## [1.0.0](https://github.com/dev-sec/ansible-mysql-hardening/tree/1.0.0) (2016-06-28) 89 | 90 | [Full Changelog](https://github.com/dev-sec/ansible-mysql-hardening/compare/dab153eb56e2296ce340e77d95586a55b5eefb80...1.0.0) 91 | 92 | **Implemented enhancements:** 93 | 94 | - add follow=yes to my.cnf protect task, incase its a symlink. fixes \#20 [\#21](https://github.com/dev-sec/ansible-mysql-hardening/pull/21) ([rndmh3ro](https://github.com/rndmh3ro)) 95 | - add changelog generator [\#7](https://github.com/dev-sec/ansible-mysql-hardening/pull/7) ([chris-rock](https://github.com/chris-rock)) 96 | 97 | **Closed issues:** 98 | 99 | - tasks - main [\#14](https://github.com/dev-sec/ansible-mysql-hardening/issues/14) 100 | - Fix directory structure. [\#6](https://github.com/dev-sec/ansible-mysql-hardening/issues/6) 101 | 102 | **Merged pull requests:** 103 | 104 | - Local testing [\#17](https://github.com/dev-sec/ansible-mysql-hardening/pull/17) ([rndmh3ro](https://github.com/rndmh3ro)) 105 | - fix rhel daemon [\#16](https://github.com/dev-sec/ansible-mysql-hardening/pull/16) ([rndmh3ro](https://github.com/rndmh3ro)) 106 | - alt version initial commit [\#15](https://github.com/dev-sec/ansible-mysql-hardening/pull/15) ([fitz123](https://github.com/fitz123)) 107 | - add test support for ansible 2.0 and 1.9 [\#13](https://github.com/dev-sec/ansible-mysql-hardening/pull/13) ([rndmh3ro](https://github.com/rndmh3ro)) 108 | - add webhook for ansible galaxy [\#11](https://github.com/dev-sec/ansible-mysql-hardening/pull/11) ([rndmh3ro](https://github.com/rndmh3ro)) 109 | - update platforms in meta-file [\#10](https://github.com/dev-sec/ansible-mysql-hardening/pull/10) ([rndmh3ro](https://github.com/rndmh3ro)) 110 | - Simplify local testing with custom role [\#9](https://github.com/dev-sec/ansible-mysql-hardening/pull/9) ([rndmh3ro](https://github.com/rndmh3ro)) 111 | - New role layout [\#8](https://github.com/dev-sec/ansible-mysql-hardening/pull/8) ([rndmh3ro](https://github.com/rndmh3ro)) 112 | - fix mysql restart not happening because of missing os specific variable [\#5](https://github.com/dev-sec/ansible-mysql-hardening/pull/5) ([fheinle](https://github.com/fheinle)) 113 | - Update kitchen-ansible, remove separate debian install [\#4](https://github.com/dev-sec/ansible-mysql-hardening/pull/4) ([rndmh3ro](https://github.com/rndmh3ro)) 114 | - update common kitchen.yml platforms \(ansible\), kitchen_debian.yml platforms \(ansible\) [\#3](https://github.com/dev-sec/ansible-mysql-hardening/pull/3) ([chris-rock](https://github.com/chris-rock)) 115 | - Separate system-vars from editable vars. [\#2](https://github.com/dev-sec/ansible-mysql-hardening/pull/2) ([rndmh3ro](https://github.com/rndmh3ro)) 116 | - Add documentation for testing, change value in vars [\#1](https://github.com/dev-sec/ansible-mysql-hardening/pull/1) ([rndmh3ro](https://github.com/rndmh3ro)) 117 | 118 | \* _This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)_ 119 | -------------------------------------------------------------------------------- /roles/ssh_hardening/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # true if IPv6 is needed 3 | network_ipv6_enable: true # sshd + ssh 4 | 5 | # Paths of the config files 6 | ssh_client_config_file: /etc/ssh/ssh_config # ssh 7 | ssh_server_config_file: /etc/ssh/sshd_config # sshd 8 | 9 | # true if sshd should be started and enabled 10 | ssh_server_enabled: true # sshd 11 | 12 | # true if DNS resolutions are needed, look up the remote host name, defaults to false from 6.8, see: http://www.openssh.com/txt/release-6.8 13 | ssh_use_dns: false # sshd 14 | 15 | # true or value if compression is needed 16 | ssh_client_compression: false # ssh 17 | ssh_compression: false # sshd 18 | 19 | # For which components (client and server) to generate the configuration for. Can be useful when running against a client without an SSH server. 20 | ssh_client_hardening: true # ssh 21 | ssh_server_hardening: true # sshd 22 | 23 | # If true, password login is allowed 24 | ssh_client_password_login: false # ssh 25 | ssh_server_password_login: false # sshd 26 | 27 | # ports on which ssh-server should listen 28 | ssh_server_ports: ['22'] # sshd 29 | 30 | # port to which ssh-client should connect 31 | ssh_client_port: '22' # ssh 32 | 33 | # one or more ip addresses, to which ssh-server should listen to. Default is empty, but should be configured for security reasons! 34 | ssh_listen_to: ['0.0.0.0'] # sshd 35 | 36 | # Host keys to look for when starting sshd. 37 | ssh_host_key_files: [] # sshd 38 | 39 | # Host RSA key size in bits 40 | ssh_host_rsa_key_size: 4096 # sshd 41 | 42 | # Host certificates to look for when starting sshd. 43 | ssh_host_certificates: [] # sshd 44 | 45 | # Specifies the host key algorithms that the server offers 46 | ssh_host_key_algorithms: [] # sshd 47 | 48 | # Specifies the host key algorithms order the client will try 49 | ssh_client_host_key_algorithms: [] # ssh 50 | 51 | # specifies the time allowed for successful authentication to the SSH server 52 | ssh_login_grace_time: 30s 53 | 54 | # Specifies the maximum number of authentication attempts permitted per connection. Once the number of failures reaches half this value, additional failures are logged. 55 | ssh_max_auth_retries: 2 56 | 57 | # Specifies the maximum number of open sessions permitted from a given connection 58 | ssh_max_sessions: 10 59 | 60 | ssh_client_alive_interval: 300 # sshd 61 | ssh_client_alive_count: 3 # sshd 62 | 63 | # Allow SSH Tunnels 64 | ssh_permit_tunnel: false 65 | 66 | # Hosts with custom options. # ssh 67 | # Example: 68 | # ssh_remote_hosts: 69 | # - names: ['example.com', 'example2.com'] 70 | # options: ['Port 2222', 'ForwardAgent yes'] 71 | # - names: ['example3.com'] 72 | # options: ['StrictHostKeyChecking no'] 73 | ssh_remote_hosts: [] 74 | 75 | # Set this to "without-password" or "yes" to allow root to login 76 | ssh_permit_root_login: 'no' # sshd 77 | 78 | # false to disable TCP Forwarding. Set to true to allow TCP Forwarding. 79 | ssh_allow_tcp_forwarding: 'no' # sshd 80 | 81 | # false to disable binding forwarded ports to non-loopback addresses. Set to true to force binding on wildcard address. 82 | # Set to 'clientspecified' to allow the client to specify which address to bind to. 83 | ssh_gateway_ports: false # sshd 84 | 85 | # false to disable Agent Forwarding. Set to true to allow Agent Forwarding. 86 | ssh_allow_agent_forwarding: false # sshd 87 | 88 | # false to disable X11 Forwarding. Set to true to allow X11 Forwarding. 89 | ssh_x11_forwarding: false # sshd 90 | 91 | # false to disable pam authentication. 92 | ssh_use_pam: true # sshd 93 | 94 | # specify AuthenticationMethods 95 | sshd_authenticationmethods: 'publickey' 96 | 97 | # Set to true to enable GSSAPI authentication (both client and server) 98 | ssh_gssapi_support: false 99 | 100 | # Set to true to enable GSSAPI credential forwarding 101 | ssh_gssapi_delegation: false 102 | 103 | # if specified, login is disallowed for user names that match one of the patterns. 104 | ssh_deny_users: '' # sshd 105 | 106 | # if specified, login is allowed only for user names that match one of the patterns. 107 | ssh_allow_users: '' # sshd 108 | 109 | # if specified, login is disallowed for users whose primary group or supplementary group list matches one of the patterns. 110 | ssh_deny_groups: '' # sshd 111 | 112 | # if specified, login is allowed only for users whose primary group or supplementary group list matches one of the patterns. 113 | ssh_allow_groups: '' # sshd 114 | 115 | # change default file that contains the public keys that can be used for user authentication. 116 | ssh_authorized_keys_file: '' # sshd 117 | 118 | # specifies the file containing trusted certificate authorities public keys used to sign user certificates. 119 | ssh_trusted_user_ca_keys_file: '' # sshd 120 | 121 | # set the trusted certificate authorities public keys used to sign user certificates. 122 | # Example: 123 | # ssh_trusted_user_ca_keys: 124 | # - 'ssh-rsa ... comment1' 125 | # - 'ssh-rsa ... comment2' 126 | ssh_trusted_user_ca_keys: [] # sshd 127 | 128 | # specifies the file containing principals that are allowed. Only used if ssh_trusted_user_ca_keys_file is set. 129 | # Example: 130 | # ssh_authorized_principals_file: '/etc/ssh/auth_principals/%u' 131 | # 132 | # %h is replaced by the home directory of the user being authenticated, and %u is 133 | # replaced by the username of that user. After expansion, the path is taken to be 134 | # an absolute path or one relative to the user's home directory. 135 | # 136 | ssh_authorized_principals_file: '' # sshd 137 | 138 | # list of hashes containing file paths and authorized principals. Only used if ssh_authorized_principals_file is set. 139 | # Example: 140 | # ssh_authorized_principals: 141 | # - { path: '/etc/ssh/auth_principals/root', principals: [ 'root' ], owner: "{{ ssh_owner }}", group: "{{ ssh_group }}", directoryowner: "{{ ssh_owner }}", directorygroup: "{{ ssh_group}}" } 142 | # - { path: '/etc/ssh/auth_principals/myuser', principals: [ 'masteradmin', 'webserver' ] } 143 | ssh_authorized_principals: [] # sshd 144 | 145 | # false to disable printing of the MOTD 146 | ssh_print_motd: false # sshd 147 | ssh_print_pam_motd: false # sshd 148 | 149 | # false to disable display of last login information 150 | ssh_print_last_log: false # sshd 151 | 152 | # false to disable serving ssh warning banner before authentication is allowed 153 | ssh_banner: false # sshd 154 | 155 | # path to file with ssh warning banner 156 | ssh_banner_path: '/etc/ssh/banner.txt' 157 | 158 | # false to disable distribution version leakage during initial protocol handshake 159 | ssh_print_debian_banner: false # sshd (Debian OS family only) 160 | 161 | # true to enable sftp configuration 162 | sftp_enabled: false 163 | 164 | # false to disable sftp chroot 165 | sftp_chroot: true 166 | 167 | # sftp default umask 168 | sftp_umask: '0027' 169 | 170 | # change default sftp chroot location 171 | sftp_chroot_dir: /home/%u 172 | 173 | # enable experimental client roaming 174 | ssh_client_roaming: false 175 | 176 | # list of hashes (containing user and rules) to generate Match User blocks for 177 | ssh_server_match_user: false # sshd 178 | 179 | # list of hashes (containing group and rules) to generate Match Group blocks for 180 | ssh_server_match_group: false # sshd 181 | 182 | # list of hashes (containing addresses/subnets and rules) to generate Match Address blocks for 183 | ssh_server_match_address: false # sshd 184 | 185 | # list of hashes (containing port and rules) to generate Match LocalPort blocks for 186 | ssh_server_match_local_port: false # sshd 187 | 188 | ssh_server_permit_environment_vars: 'no' 189 | ssh_server_accept_env_vars: '' 190 | 191 | # maximum number of concurrent unauthenticated connections to the SSH daemon 192 | ssh_max_startups: '10:30:60' # sshd 193 | 194 | ssh_ps59: 'sandbox' 195 | 196 | ssh_macs: [] 197 | ssh_ciphers: [] 198 | ssh_kex: [] 199 | 200 | # directory where to store ssh_password policy 201 | ssh_custom_selinux_dir: '/etc/selinux/local-policies' 202 | 203 | sshd_moduli_minimum: 2048 204 | 205 | # disable ChallengeResponseAuthentication 206 | ssh_challengeresponseauthentication: false 207 | 208 | # a list of public keys that are never accepted by the ssh server 209 | ssh_server_revoked_keys: [] 210 | 211 | # Set to false to turn the role into a no-op. Useful when using 212 | # the Ansible role dependency mechanism. 213 | ssh_hardening_enabled: true 214 | 215 | # Custom options for SSH client configuration file 216 | ssh_custom_options: [] 217 | 218 | # Custom options for SSH daemon configuration file 219 | sshd_custom_options: [] 220 | 221 | # Logging 222 | sshd_syslog_facility: 'AUTH' 223 | sshd_log_level: 'VERBOSE' 224 | 225 | sshd_strict_modes: true 226 | -------------------------------------------------------------------------------- /roles/os_hardening/templates/etc/login.defs.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Generated by Ansible role {{ ansible_role_name }} 3 | 4 | # Configuration control definitions for the login package. 5 | # 6 | # Three items must be defined: `MAIL_DIR`, `ENV_SUPATH`, and `ENV_PATH`. If unspecified, some arbitrary (and possibly incorrect) value will be assumed. All other items are optional - if not specified then the described action or option will be inhibited. 7 | # 8 | # Comment lines (lines beginning with `#`) and blank lines are ignored. 9 | # 10 | #-- Modified for Linux. --marekm 11 | 12 | {% if os_useradd_mail_dir is defined %} 13 | # *REQUIRED for useradd/userdel/usermod* 14 | # 15 | # Directory where mailboxes reside, _or_ name of file, relative to the home directory. If you _do_ define `MAIL_DIR` and `MAIL_FILE`, `MAIL_DIR` takes precedence. 16 | # Essentially: 17 | # 18 | # * `MAIL_DIR` defines the location of users mail spool files (for mbox use) by appending the username to `MAIL_DIR` as defined below. 19 | # * `MAIL_FILE` defines the location of the users mail spool files as the fully-qualified filename obtained by prepending the user home directory before `$MAIL_FILE` 20 | # 21 | # *NOTE*: This is no more used for setting up users MAIL environment variable which is, starting from shadow 4.0.12-1 in Debian, entirely the job of the pam_mail PAM modules. 22 | # 23 | # See default PAM configuration files provided for login, su, etc. 24 | # This is a temporary situation: setting these variables will soon move to `/etc/default/useradd` and the variables will then be no more supported 25 | MAIL_DIR {{ os_useradd_mail_dir }} 26 | {% endif %} 27 | 28 | {% if os_useradd_create_home is defined %} 29 | # If useradd should create home directories for users by default 30 | CREATE_HOME {{ 'yes' if os_useradd_create_home else 'no' }} 31 | 32 | {% endif %} 33 | # Enable logging and display of `/var/log/faillog` login failure info. This option conflicts with the `pam_tally` PAM module. 34 | FAILLOG_ENAB yes 35 | 36 | # Enable display of unknown usernames when login failures are recorded. 37 | # 38 | # *WARNING*: Unknown usernames may become world readable. See #290803 and #298773 for details about how this could become a security concern 39 | LOG_UNKFAIL_ENAB no 40 | 41 | # Enable logging of successful logins 42 | LOG_OK_LOGINS yes 43 | 44 | # Enable "syslog" logging of su activity - in addition to sulog file logging. 45 | SYSLOG_SU_ENAB yes 46 | 47 | # Enable "syslog" logging of newgrp and sg. 48 | SYSLOG_SG_ENAB yes 49 | 50 | # If defined, all su activity is logged to this file. 51 | #SULOG_FILE /var/log/sulog 52 | 53 | # If defined, file which maps tty line to `TERM` environment parameter. Each line of the file is in a format something like "vt100 tty01". 54 | #TTYTYPE_FILE /etc/ttytype 55 | 56 | # If defined, login failures will be logged here in a utmp format last, when invoked as lastb, will read `/var/log/btmp`, so... 57 | FTMP_FILE /var/log/btmp 58 | 59 | # If defined, the command name to display when running "su -". For # example, if this is defined as "su" then a "ps" will display the command is "-su". If not defined, then "ps" would display the name of the shell actually being run, e.g. something like "-sh". 60 | SU_NAME su 61 | 62 | # If defined, file which inhibits all the usual chatter during the login sequence. If a full pathname, then hushed mode will be enabled if the user's name or shell are found in the file. If not a full pathname, then hushed mode will be enabled if the file exists in the user's home directory. 63 | #HUSHLOGIN_FILE /etc/hushlogins 64 | HUSHLOGIN_FILE .hushlogin 65 | 66 | # *REQUIRED*: The default PATH settings, for superuser and normal users. (they are minimal, add the rest in the shell startup files) 67 | ENV_SUPATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 68 | ENV_PATH PATH=/usr/local/bin:/usr/bin:/bin:{{ os_env_extra_user_paths | join (':') }} 69 | 70 | # Terminal permissions 71 | # -------------------- 72 | 73 | # Login tty will be assigned this group ownership. 74 | # If you have a "write" program which is "setgid" to a special group which owns the terminals, define `TTYGROUP` to the group number and `TTYPERM` to `0620`. Otherwise leave `TTYGROUP` commented out and assign `TTYPERM` to either `622` or `600`. 75 | TTYGROUP tty 76 | 77 | # Login tty will be set to this permission. 78 | # In Debian `/usr/bin/bsd-write` or similar programs are setgid tty. However, the default and recommended value for `TTYPERM` is still `0600` to not allow anyone to write to anyone else console or terminal 79 | # Users can still allow other people to write them by issuing the `mesg y` command. 80 | TTYPERM 0600 81 | 82 | # Login conf initializations 83 | # -------------------------- 84 | 85 | # Terminal ERASE character ('\010' = backspace). Only used on System V. 86 | ERASECHAR 0177 87 | 88 | # Terminal KILL character ('\025' = CTRL/U). Only used on System V. 89 | KILLCHAR 025 90 | 91 | # The default umask value for `pam_umask` and is used by useradd and newusers to set the mode of the new home directories. 92 | # If `USERGROUPS_ENAB` is set to `yes`, that will modify this `UMASK` default value for private user groups, i. e. the uid is the same as gid, and username is the same as the primary group name: for these, the user permissions will be used as group permissions, e. g. `022` will become `002`. 93 | # Prefix these values with `0` to get octal, `0x` to get hexadecimal. 94 | # `022` is the "historical" value in Debian for UMASK 95 | # `027`, or even `077`, could be considered better for privacy. 96 | UMASK {{ os_env_umask }} 97 | 98 | # Enable setting of the umask group bits to be the same as owner bits (examples: `022` -> `002`, `077` -> `007`) for non-root users, if the uid is the same as gid, and username is the same as the primary group name. 99 | # If set to yes, userdel will remove the user´s group if it contains no more members, and useradd will create by default a group with the name of the user. 100 | USERGROUPS_ENAB yes 101 | 102 | 103 | # Password aging controls 104 | # ----------------------- 105 | 106 | # Maximum number of days a password may be used. 107 | PASS_MAX_DAYS {{ os_auth_pw_max_age }} 108 | 109 | # Minimum number of days allowed between password changes. 110 | PASS_MIN_DAYS {{ os_auth_pw_min_age }} 111 | 112 | # Number of days warning given before a password expires. 113 | PASS_WARN_AGE 7 114 | 115 | # Min/max values for automatic uid selection in useradd 116 | UID_MIN {{ os_auth_uid_min }} 117 | UID_MAX {{ os_auth_uid_max }} 118 | # System accounts 119 | SYS_UID_MIN {{ os_auth_sys_uid_min }} 120 | SYS_UID_MAX {{ os_auth_sys_uid_max }} 121 | 122 | # Min/max values for automatic gid selection in groupadd 123 | GID_MIN {{ os_auth_gid_min }} 124 | GID_MAX {{ os_auth_gid_max }} 125 | # System accounts 126 | SYS_GID_MIN {{ os_auth_sys_gid_min }} 127 | SYS_GID_MAX {{ os_auth_sys_gid_max }} 128 | 129 | # Max number of login retries if password is bad. This will most likely be overriden by PAM, since the default pam_unix module has it's own built in of 3 retries. However, this is a safe fallback in case you are using an authentication module that does not enforce PAM_MAXTRIES. 130 | LOGIN_RETRIES {{ os_auth_retries }} 131 | 132 | # Max time in seconds for login 133 | LOGIN_TIMEOUT {{ os_auth_timeout }} 134 | 135 | # Which fields may be changed by regular users using chfn - use any combination of letters "frwh" (full name, room number, work phone, home phone). If not defined, no changes are allowed. 136 | # For backward compatibility, "yes" = "rwh" and "no" = "frwh". 137 | {% if os_chfn_restrict %} 138 | CHFN_RESTRICT {{ os_chfn_restrict }} 139 | {% endif %} 140 | # Should login be allowed if we can't cd to the home directory? 141 | DEFAULT_HOME {{ 'yes' if os_auth_allow_homeless else 'no' }} 142 | 143 | # If defined, this command is run when removing a user. 144 | # It should remove any at/cron/print jobs etc. owned by 145 | # the user to be removed (passed as the first argument). 146 | #USERDEL_CMD /usr/sbin/userdel_local 147 | 148 | # Instead of the real user shell, the program specified by this parameter will be launched, although its visible name (`argv[0]`) will be the shell's. The program may do whatever it wants (logging, additional authentification, banner, ...) before running the actual shell. 149 | #FAKE_SHELL /bin/fakeshell 150 | 151 | # If defined, either full pathname of a file containing device names or a ":" delimited list of device names. Root logins will be allowed only upon these devices. 152 | # This variable is used by login and su. 153 | #CONSOLE /etc/consoles 154 | #CONSOLE console:tty01:tty02:tty03:tty04 155 | 156 | # List of groups to add to the user's supplementary group set when logging in on the console (as determined by the `CONSOLE` setting). Default is none. 157 | # Use with caution - it is possible for users to gain permanent access to these groups, even when not logged in on the console. How to do it is left as an exercise for the reader... 158 | # This variable is used by login and su. 159 | #CONSOLE_GROUPS floppy:audio:cdrom 160 | 161 | # If set to `MD5`, MD5-based algorithm will be used for encrypting password 162 | # If set to `SHA256`, SHA256-based algorithm will be used for encrypting password 163 | # If set to `SHA512`, SHA512-based algorithm will be used for encrypting password 164 | # If set to `DES`, DES-based algorithm will be used for encrypting password (default) 165 | # Overrides the MD5_CRYPT_ENAB option 166 | # 167 | # Note: It is recommended to use a value consistent with 168 | # the PAM modules configuration. 169 | MD5_CRYPT_ENAB no 170 | ENCRYPT_METHOD SHA512 171 | 172 | # Only used if `ENCRYPT_METHOD` is set to `SHA256` or `SHA512`: Define the number of SHA rounds. 173 | # With a lot of rounds brute forcing the password is more difficult. But note also that it more CPU resources will be needed to authenticate users. 174 | # If not specified, the libc will choose the default number of rounds (5000). The values must be inside the 1000-999999999 range. If only one of the MIN or MAX values is set, then this value will be used. 175 | # If MIN > MAX, the highest value will be used. 176 | SHA_CRYPT_MIN_ROUNDS {{ os_sha_crypt_min_rounds }} 177 | SHA_CRYPT_MAX_ROUNDS {{ os_sha_crypt_max_rounds }} 178 | 179 | 180 | # Obsoleted by PAM 181 | # ================ 182 | # These options are now handled by PAM. Please edit the appropriate file in `/etc/pam.d/` to enable the equivelants of them. 183 | #MOTD_FILE 184 | #DIALUPS_CHECK_ENAB 185 | #LASTLOG_ENAB 186 | #MAIL_CHECK_ENAB 187 | #OBSCURE_CHECKS_ENAB 188 | #PORTTIME_CHECKS_ENAB 189 | #SU_WHEEL_ONLY 190 | #CRACKLIB_DICTPATH 191 | #PASS_CHANGE_TRIES 192 | #PASS_ALWAYS_WARN 193 | #ENVIRON_FILE 194 | #NOLOGINS_FILE 195 | #ISSUE_FILE 196 | #PASS_MIN_LEN 197 | #PASS_MAX_LEN 198 | #ULIMIT 199 | #ENV_HZ 200 | #CHFN_AUTH 201 | #CHSH_AUTH 202 | #FAIL_DELAY 203 | 204 | # Obsoleted 205 | # ========= 206 | # These options are no more handled by shadow. 207 | # Shadow utilities will display a warning if they still appear. 208 | #CLOSE_SESSIONS 209 | #LOGIN_STRING 210 | #NO_PASSWORD_CONSOLE 211 | #QMAIL_DIR 212 | 213 | # If set to `yes`, new passwords will be encrypted using the MD5-based algorithm compatible with the one used by recent releases of FreeBSD. It supports passwords of unlimited length and longer salt strings. 214 | # Set to `no` if you need to copy encrypted passwords to other systems which don't understand the new algorithm. Default is `no`. 215 | # This variable is deprecated. You should use ENCRYPT_METHOD. 216 | # 217 | #MD5_CRYPT_ENAB no 218 | --------------------------------------------------------------------------------