diff --git a/molecule/os_hardening/verify.yml b/molecule/os_hardening/verify.yml index 5717e604..287f5423 100644 --- a/molecule/os_hardening/verify.yml +++ b/molecule/os_hardening/verify.yml @@ -20,6 +20,7 @@ - verify_tasks/pw_ageing.yml - verify_tasks/netrc.yml - verify_tasks/ignore_home_folders.yml + - verify_tasks/ssh_auth_locked.yml # temp. disabled - https://github.com/dev-sec/ansible-collection-hardening/issues/690 # - name: Include PAM tests diff --git a/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml b/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml new file mode 100644 index 00000000..6b2bbea2 --- /dev/null +++ b/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml @@ -0,0 +1,90 @@ +--- +- name: Install tools + package: + name: "{{ item }}" + state: present + ignore_errors: true + loop: + - sshpass + - openssh + - openssh-clients + - openssh-server + +- name: Allow password Login for sshd + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + search_string: PasswordAuthentication no + line: PasswordAuthentication yes + when: + - ansible_facts.distribution == "Amazon" + +- name: Start sshd service + ansible.builtin.service: + name: "{{ item }}" + state: started + ignore_errors: true + loop: + - sshd + - ssh + +- name: Set password for test + ansible.builtin.set_fact: + test_pw: myTest!pw + +- name: Create locked_user + user: + name: locked_user + password: "{{ test_pw | password_hash('sha512') }}" + +- name: Create ssh-client-keypair + community.crypto.openssh_keypair: + path: /root/locked_user_id + type: ed25519 + state: present + register: generated_key + +- name: Add ssh-public-key to locked_user + ansible.posix.authorized_key: + user: locked_user + key: "{{ generated_key.public_key }}" + state: present + +- name: Check successful login with password + ansible.builtin.shell: + cmd: sshpass -p {{ test_pw }} ssh -o StrictHostKeyChecking=no locked_user@localhost echo "success" + +- name: Check successful login with ssh key + ansible.builtin.shell: + cmd: ssh -i /root/locked_user_id -o StrictHostKeyChecking=no locked_user@localhost echo "success" + +- name: Set password change date for locked_user + ansible.builtin.shell: + cmd: chage -d 2020-01-01 locked_user + +- name: Check unsuccessful login with password + ansible.builtin.shell: + cmd: sshpass -p {{ test_pw }} ssh -o StrictHostKeyChecking=no locked_user@localhost echo "success" + register: output + ignore_errors: true + +- name: Assert check unsuccessful login + ansible.builtin.assert: + that: + - output.rc | int == 1 + - "'WARNING: Your password has expired.' in output.stderr" + - "'success' not in output.stdout" + when: + - ansible_facts.os_family != "Suse" + +- name: Assert check unsuccessful login + ansible.builtin.assert: + that: + - output.rc | int == 5 + - output.stderr | length == 0 + - output.stdout | length == 0 + when: + - ansible_facts.os_family == "Suse" + +- name: Check successful login with ssh key + ansible.builtin.shell: + cmd: ssh -i /root/locked_user_id -o StrictHostKeyChecking=no locked_user@localhost echo "success" diff --git a/requirements.txt b/requirements.txt index 728cb5ce..38422ab7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ ansible-core==2.18.1 docker==7.1.0 jmespath==1.0.1 aar-doc==2.0.1 +passlib==1.7.4 diff --git a/roles/os_hardening/README.md b/roles/os_hardening/README.md index f771eb31..508a78dd 100644 --- a/roles/os_hardening/README.md +++ b/roles/os_hardening/README.md @@ -94,6 +94,18 @@ We are setting this sysctl to a default of `32`, some systems only support small vm.mmap_rnd_bits: 16 ``` +### password expiry and SSH key based logins + +With default PAM configuration setting a password expiry for users will also block SSH key logins after the password has expired. +We have added a flag for PAM to ignore the expiry if SSH keys or other login mechanisms are used. +If you choose to use your own PAM configuration please adjust it accordingly to contain `no_pass_expiry` in the `account` stage for the `pam_unix.so` module. + +A valid example would look like this: + +```text +account required pam_unix.so no_pass_expiry +``` + ## Testing with inspec If you're using inspec to test your machines after applying this role, please make sure to add the connecting user to the `os_ignore_users`-variable. diff --git a/roles/os_hardening/tasks/pam.yml b/roles/os_hardening/tasks/pam.yml index d83a7c21..5e8feead 100644 --- a/roles/os_hardening/tasks/pam.yml +++ b/roles/os_hardening/tasks/pam.yml @@ -27,6 +27,24 @@ when: - ansible_facts.os_family == 'RedHat' +- name: Allow Login with SSH Keys, when user password is expired + ansible.builtin.lineinfile: + path: /etc/pam.d/system-auth + backrefs: true + regexp: "^(account.*pam_unix.so(?!.*no_pass_expiry).*)$" + line: '\1 no_pass_expiry' + when: + - ansible_facts.os_family == 'Archlinux' + +- name: Allow Login with SSH Keys, when user password is expired + ansible.builtin.lineinfile: + path: /etc/pam.d/common-account + backrefs: true + regexp: "^(account.*pam_unix.so(?!.*no_pass_expiry).*)$" + line: '\1 no_pass_expiry' + when: + - ansible_facts.os_family == 'Suse' + - name: NSA 2.3.3.5 Upgrade Password Hashing Algorithm to SHA-512 ansible.builtin.template: src: etc/libuser.conf.j2 diff --git a/roles/os_hardening/tasks/pam_debian.yml b/roles/os_hardening/tasks/pam_debian.yml index f49de474..3c46553e 100644 --- a/roles/os_hardening/tasks/pam_debian.yml +++ b/roles/os_hardening/tasks/pam_debian.yml @@ -118,3 +118,10 @@ state: absent when: - not os_auth_pam_passwdqc_enable + +- name: Allow Login with SSH Keys, when user password is expired + ansible.builtin.lineinfile: + path: /etc/pam.d/common-account + backrefs: true + regexp: "^(account.*pam_unix.so(?!.*no_pass_expiry).*)$" + line: '\1 no_pass_expiry' diff --git a/roles/os_hardening/templates/etc/pam.d/rhel_auth.j2 b/roles/os_hardening/templates/etc/pam.d/rhel_auth.j2 index 3d7cd224..0bdd1779 100644 --- a/roles/os_hardening/templates/etc/pam.d/rhel_auth.j2 +++ b/roles/os_hardening/templates/etc/pam.d/rhel_auth.j2 @@ -24,7 +24,7 @@ auth required pam_deny.so {% if os_auth_retries|int > 0 %} account required pam_faillock.so {% endif %} -account required pam_unix.so +account required pam_unix.so no_pass_expiry account sufficient pam_localuser.so account sufficient pam_succeed_if.so uid < 1000 quiet {% if (os_auth_pam_sssd_enable | bool) %}