4

I'm trying to enable 2FA with ssh using libpam-google-authenticator. Not all users need authenticator enabled. Everybody uses ssh public keys, and nobody has a password. I'm running Debian buster, and I've also tried libpam-google-authenticator from bullseye.

My problem is that no matter what I put in the PAM config, users without authenticator enabled are never logged straight in, but always asked for a password.

I've install libpam-google-authenticator and configured /etc/ssh/sshd_config with:

PasswordAuthentication no
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
PasswordAuthentication no
PermitEmptyPasswords no

I haven't been able to work out the correct PAM config so that users without a .google_authenticator file are still logged in. Depending on what I use, users are either prompted for a password (they don't have one), or not allowed in at all.

In /etc/pam.d/sshd I've tried (like this Trying to get SSH with public key (no password) + google authenticator working on Ubuntu 14.04.1):

#@include common-auth
auth       required     pam_google_authenticator.so debug nullok

In this case, users without an authenticator setup get rejected with the following debug;

Aug 05 15:11:18 <host> sshd(pam_google_authenticator)[746624]: debug: start of google_authenticator for "<user>"
Aug 05 15:11:18 <host> sshd(pam_google_authenticator)[746624]: debug: end of google_authenticator for "<user>" Result: The return value should be ignored by PAM dispatch
Aug 05 15:11:18 <host> sshd[746620]: error: PAM: Permission denied for <user> from <IP>

Is pam_permit is needed to set up the fallback case?

I've also tried various combinations of auth required and auth sufficient before and after @include common-auth but they all result in users without authenticator being asked for a password and sometimes users WITH authenticator also being asked for a password.

Does anyone have a recipe to make this work?

  • Ok I think I am finally better understanding your problem. I did some reading, and now I have a question. Are all the users that need pubkey+totp in a specific group, or could the be in a group? Or perhaps the opposite. Are all the users that are pubkey only in a group? You can set different `AuthenticationMethod` directives in a `match` section in your sshd_config. – Zoredache Aug 06 '21 at 17:40
  • I could make that work. To complicate things though, once I have this working I want to try to make the TOTP requirement dependent on where I'm connecting from using `pam_access` - if I'm connecting from a VPN IP I want to bypass TOTP. Although it looks like I could also the IP check using `match` rather than `pam_access` so maybe that would work afterall. – Hamish Moffatt Aug 07 '21 at 06:36

2 Answers2

2

Here is my working configuration. Some users have authenticator enabled and some don't, and only SSH logins with public keys are permitted, never passwords.

In /etc/ssh/sshd_config,

UsePAM yes
PasswordAuthentication no
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
PermitEmptyPasswords no

In /etc/pam.d/sshd,

# Standard Un*x authentication.
#@include common-auth

# Require authenticator, if not configured then allow
auth    required    pam_google_authenticator.so debug nullok
auth    required    pam_permit.so

@include comon-auth must be disabled because it includes pam_unix, which I don't want to use. Then you need pam_permit to make authentication successful for users without authenticator (for which pam_google_authenticator returns ignore rather than pass).

This still doesn't let root login with an ssh key; sshd logs

sshd[1244501]: fatal: Internal error: PAM auth succeeded when it should have failed

This is discussed at Google Authenticator PAM on SSH blocks root login without 2FA .

Having gotten this working as above, I think it's actually nicer to enforce 2FA for certain groups using the SSH config as @zoredache suggested. This easily allows you to whitelist certain IPs as not requiring 2FA also. In this case, sshd_config says for example

UsePAM yes
PasswordAuthentication no
ChallengeResponseAuthentication yes
#AuthenticationMethods any # default
PermitEmptyPasswords no

Match Group adm Address *,!172.16.1.0/24
    AuthenticationMethods publickey,keyboard-interactive

and /etc/pam.d/ssh says

 Standard Un*x authentication.
#@include common-auth

# Require authenticator; SSH should not allow any user in who doesn't have it
auth       sufficient   pam_google_authenticator.so debug nullok
auth       requisite    pam_deny.so
0

I don't think you need or want to comment out the @include common-auth. Or at least I did not and it seemed to work correctly. But I am still mostly been testing this.

Does anyone have a recipe to make this work?

Don't have time to translate it to a shell script for you, but this is a excerpt of an ansible playbook that seems to work for me. I suspect you should be able follow along with what this is doing even if you aren't using ansible.

- hosts: linux_systems
  tasks:

  - name: Add group 'totp'
    group:
      name: totp
      state: present
      system: yes

  - name: Create directory for totp secrets
    file:
      state: directory
      path: /var/lib/google-authenticator
      owner: "0"
      group: "0"
      mode: "0700"

  - name: install libpam-google-authenticator
    apt:
      update_cache: yes
      cache_valid_time: '{{ apt_cache_valid_time | default(7200) }}'
      state: present
      name:
      - libpam-google-authenticator

  - name: Create secret for 'example-user'
    args:
      creates: /var/lib/google-authenticator/example-user
    shell: |
      TOTP_USER=example-user; \
      google-authenticator \
        --force --quiet \
        --emergency-codes=10 \
        --time-based \
        --qr-mode=none \
        --allow-reuse \
        --window-size=3 \
        --rate-limit=4 --rate-time=30 \
        --secret=/var/lib/google-authenticator/${TOTP_USER}

  - name: update pam
    lineinfile:
      insertafter: '^@include common-password'
      path: /etc/pam.d/login
      line: 'auth required pam_google_authenticator.so nullok user=root secret=/var/lib/google-authenticator/${USER}'

  - name: update pam
    lineinfile:
      insertafter: '^@include common-password'
      path: /etc/pam.d/sshd
      line: 'auth required pam_google_authenticator.so nullok user=root secret=/var/lib/google-authenticator/${USER}'

  - name: update sshd ChallengeResponseAuthentication
    notify: Restart sshd
    lineinfile:
      path: /etc/ssh/sshd_config
      regexp: '^ChallengeResponseAuthentication .*'
      line: 'ChallengeResponseAuthentication yes'

  handlers:

  - name: Restart sshd
    service:
      name: sshd
      state: restarted
Zoredache
  • 128,755
  • 40
  • 271
  • 413
  • Thanks. That looks like what I have, except that you have added the module later in the file - effectively at the end. Do you also have the AuthenticationMethods in sshd_config changed to add keyboard-interactive? I never get any prompts for the verification code without it. But with it, and the authenticator module at the end, I get asked for a password. Is your common-auth unmodified? – Hamish Moffatt Aug 05 '21 at 10:12
  • As far as recall, those changes are the everything I needed from a completely stock current Debian buster install. – Zoredache Aug 05 '21 at 16:41
  • I don't get any attempt at TOTP verification without modifying the AuthenticationMethods in sshd_config. Other documentation I've seen eg https://blog.geoghegan.me/linux/setting-up-google-auth-2fa-on-debian also includes this step. – Hamish Moffatt Aug 06 '21 at 09:00
  • 1
    You are right the docs do say that for ssh. Most of my testing for this was testing local logins. Now I am not sure if I had ever tested SSH. My SSH is basically key-based only, and I wasn't worried about ssh for my test account since it didn't get any keys. – Zoredache Aug 06 '21 at 17:33
  • OK thanks for the replies. – Hamish Moffatt Aug 07 '21 at 06:32