From fba07a4d41575d0c17fd2d715963e2413a21fc20 Mon Sep 17 00:00:00 2001 From: Martin Schurz Date: Sun, 22 Dec 2024 18:51:18 +0100 Subject: [PATCH 1/6] MOdify PAM to allow SSH Key logins with locked passwords Signed-off-by: Martin Schurz --- molecule/os_hardening/verify.yml | 1 + .../verify_tasks/ssh_auth_locked.yml | 57 +++++++++++++++++++ roles/os_hardening/tasks/pam_debian.yml | 7 +++ .../templates/etc/pam.d/rhel_auth.j2 | 2 +- 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 molecule/os_hardening/verify_tasks/ssh_auth_locked.yml diff --git a/molecule/os_hardening/verify.yml b/molecule/os_hardening/verify.yml index 5717e6048..287f54233 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 000000000..f15a02549 --- /dev/null +++ b/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml @@ -0,0 +1,57 @@ +--- +- name: Install sshpass + package: + name: + - sshpass + state: present + +- 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/.ssh/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 locked_user@localhost echo "success" + +- name: Check successful login with ssh key + ansible.builtin.shell: + cmd: ssh -i /root/.ssh/locked_user_id 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 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" + +- name: Check successful login with ssh key + ansible.builtin.shell: + cmd: ssh -i /root/.ssh/locked_user_id locked_user@localhost echo "success" diff --git a/roles/os_hardening/tasks/pam_debian.yml b/roles/os_hardening/tasks/pam_debian.yml index f49de474b..3309bc903 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 + lineinfile: + path: /etc/pam.d/common-account + backrefs: yes + 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 3d7cd2243..0bdd17797 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) %} From 4b05df756cb9ffc7d58e9a8f3deaafe7e5b8b4a5 Mon Sep 17 00:00:00 2001 From: Martin Schurz Date: Sun, 22 Dec 2024 18:58:39 +0100 Subject: [PATCH 2/6] fix errors Signed-off-by: Martin Schurz --- molecule/os_hardening/verify_tasks/ssh_auth_locked.yml | 3 ++- roles/os_hardening/tasks/pam_debian.yml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml b/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml index f15a02549..491f69361 100644 --- a/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml +++ b/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml @@ -1,8 +1,9 @@ --- -- name: Install sshpass +- name: Install tools package: name: - sshpass + - python3-passlib state: present - name: Set password for test diff --git a/roles/os_hardening/tasks/pam_debian.yml b/roles/os_hardening/tasks/pam_debian.yml index 3309bc903..3c46553e5 100644 --- a/roles/os_hardening/tasks/pam_debian.yml +++ b/roles/os_hardening/tasks/pam_debian.yml @@ -120,8 +120,8 @@ - not os_auth_pam_passwdqc_enable - name: Allow Login with SSH Keys, when user password is expired - lineinfile: + ansible.builtin.lineinfile: path: /etc/pam.d/common-account - backrefs: yes + backrefs: true regexp: "^(account.*pam_unix.so(?!.*no_pass_expiry).*)$" line: '\1 no_pass_expiry' From 767293e40b613e14f05955fb112920a0a59f282f Mon Sep 17 00:00:00 2001 From: Martin Schurz Date: Sun, 22 Dec 2024 19:26:22 +0100 Subject: [PATCH 3/6] Install passlib on executor Signed-off-by: Martin Schurz --- molecule/os_hardening/verify_tasks/ssh_auth_locked.yml | 1 - requirements.txt | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml b/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml index 491f69361..da21dae08 100644 --- a/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml +++ b/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml @@ -3,7 +3,6 @@ package: name: - sshpass - - python3-passlib state: present - name: Set password for test diff --git a/requirements.txt b/requirements.txt index ade84ad88..b382cd2ec 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 From e8cf61bd3cad95ae71bbb3d30f4730ff19213a40 Mon Sep 17 00:00:00 2001 From: Martin Schurz Date: Sun, 22 Dec 2024 19:38:00 +0100 Subject: [PATCH 4/6] Make changes portable Signed-off-by: Martin Schurz Make changes portable Signed-off-by: Martin Schurz Make changes portable Signed-off-by: Martin Schurz Make changes portable Signed-off-by: Martin Schurz Make changes portable Signed-off-by: Martin Schurz Make changes portable Signed-off-by: Martin Schurz Make changes portable Signed-off-by: Martin Schurz Make changes portable Signed-off-by: Martin Schurz --- .../verify_tasks/ssh_auth_locked.yml | 47 ++++++++++++++++--- roles/os_hardening/tasks/pam.yml | 18 +++++++ 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml b/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml index da21dae08..64e09af55 100644 --- a/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml +++ b/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml @@ -1,9 +1,31 @@ --- - name: Install tools package: - name: - - sshpass + 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: @@ -16,7 +38,7 @@ - name: Create ssh-client-keypair community.crypto.openssh_keypair: - path: /root/.ssh/locked_user_id + path: /root/locked_user_id type: ed25519 state: present register: generated_key @@ -29,11 +51,11 @@ - name: Check successful login with password ansible.builtin.shell: - cmd: sshpass -p {{ test_pw }} ssh locked_user@localhost echo "success" + 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/.ssh/locked_user_id locked_user@localhost echo "success" + 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: @@ -41,7 +63,7 @@ - name: Check unsuccessful login with password ansible.builtin.shell: - cmd: sshpass -p {{ test_pw }} ssh locked_user@localhost echo "success" + cmd: sshpass -p {{ test_pw }} ssh -o StrictHostKeyChecking=no locked_user@localhost echo "success" register: output ignore_errors: true @@ -51,7 +73,18 @@ - output.rc | int == 1 - "'WARNING: Your password has expired.' in output.stderr" - "'success' not in output.stdout" + when: + - ansible_facts.os_family != "OpenSuse" + +- 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 == "OpenSuse" - name: Check successful login with ssh key ansible.builtin.shell: - cmd: ssh -i /root/.ssh/locked_user_id locked_user@localhost echo "success" + cmd: ssh -i /root/locked_user_id -o StrictHostKeyChecking=no locked_user@localhost echo "success" diff --git a/roles/os_hardening/tasks/pam.yml b/roles/os_hardening/tasks/pam.yml index d83a7c21b..f4cbb042d 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 == 'OpenSuse' + - name: NSA 2.3.3.5 Upgrade Password Hashing Algorithm to SHA-512 ansible.builtin.template: src: etc/libuser.conf.j2 From e13bb0fcae3f17e96ab17d5f4d3a0d36ee82a213 Mon Sep 17 00:00:00 2001 From: Martin Schurz Date: Sun, 22 Dec 2024 22:23:01 +0100 Subject: [PATCH 5/6] Use correct os_family for Suse ... Signed-off-by: Martin Schurz --- molecule/os_hardening/verify_tasks/ssh_auth_locked.yml | 4 ++-- roles/os_hardening/tasks/pam.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml b/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml index 64e09af55..6b2bbea2f 100644 --- a/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml +++ b/molecule/os_hardening/verify_tasks/ssh_auth_locked.yml @@ -74,7 +74,7 @@ - "'WARNING: Your password has expired.' in output.stderr" - "'success' not in output.stdout" when: - - ansible_facts.os_family != "OpenSuse" + - ansible_facts.os_family != "Suse" - name: Assert check unsuccessful login ansible.builtin.assert: @@ -83,7 +83,7 @@ - output.stderr | length == 0 - output.stdout | length == 0 when: - - ansible_facts.os_family == "OpenSuse" + - ansible_facts.os_family == "Suse" - name: Check successful login with ssh key ansible.builtin.shell: diff --git a/roles/os_hardening/tasks/pam.yml b/roles/os_hardening/tasks/pam.yml index f4cbb042d..5e8feead0 100644 --- a/roles/os_hardening/tasks/pam.yml +++ b/roles/os_hardening/tasks/pam.yml @@ -43,7 +43,7 @@ regexp: "^(account.*pam_unix.so(?!.*no_pass_expiry).*)$" line: '\1 no_pass_expiry' when: - - ansible_facts.os_family == 'OpenSuse' + - ansible_facts.os_family == 'Suse' - name: NSA 2.3.3.5 Upgrade Password Hashing Algorithm to SHA-512 ansible.builtin.template: From b7f0bdd81f588a805c018f423c203f437cbce41f Mon Sep 17 00:00:00 2001 From: Martin Schurz Date: Mon, 23 Dec 2024 00:34:03 +0100 Subject: [PATCH 6/6] Add docs Signed-off-by: Martin Schurz --- roles/os_hardening/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/roles/os_hardening/README.md b/roles/os_hardening/README.md index f771eb314..508a78dd9 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.