Two-Factor SSH Authentication with LinOTP

1 Introduction

Adding OneTimePasswords as additional authentication layer for SSH clients does significantly improve security for SSH based login procedures. This article describes how to implement LinOTP in your SSH environment by integrating the usage of OTPs into the PAM stack of a SSH server.

For that two methods are available:

  • authentication through the LinOTP web API done by:

    • PAM C modul [recommended]: pam_linotp (provided by LinOTP)
    • PAM python modul: pam_linotp.py (provided by LinOTP)
  • authentication by a RADIUS server connected to LinOTP done by:

    • libpam-radius-auth (natively available in Debian jessie/stretch)

What this guide does not cover:

2 Prerequisites

You will need:

  • SSH server
  • SSH client
  • LinOTP (reachable either from the SSH server or the RADIUS server depending of you choice of authentication)
  • optional: RADIUS server (if this is the authentication method of your choice)

This guide is written for Debian jessie, albeit the how-to should be applicable to most distributions.

Package versions

This how-to was tested with the following versions of packages (retrieved from Debian and LinOTP repositories):

  • linotp: 2.10.0.4-1
  • libpam-linotp: 2.9-1
  • libpam-radius-auth: 1.3.16-4.4
  • libpam-python: 1.0.4-1
  • openssh-server: 1:6.7p1-5+deb8u4
  • openssh-client: 7.4p1-10+deb9u3
  • pam_linotp.py: 2.7

Don't worry - you can use these packages in other version (older as well as newer, but of course newer is recommended) and have a successfully running setup. But if you encounter obscure problems, please check the changelogs, whether they contain any related modifications.

3 Direct authentication via LinOTP web interface (no RADIUS server needed)

As it comes in the world of Open source, there are two implementations of the necessary PAM module:

  • pam_linotp (written in C, developed and updated by netgo GmbH)
  • libpam-python together with pam_py_linotp (a script-based version, developed and updated by netgo GmbH)

Both PAM modules connect directly to the https-interface of your LinOTP instance. So no trouble in setting up a RADIUS server :)

Which one you like more is up to you, we will show both setups for your SSH server.

3.1 pam_linotp

This is the recommended method. Alternatively see next chapter for pam_linotp.py.

  • If not yet done, add the linotp-Repository to your SSH server.
echo 'deb http://dist.linotp.org/debian/linotp2 jessie linotp' > /etc/apt/sources.list.d/linotp.list
Add the gpg-key of linotp to your apt-keyring:
apt-key adv --keyserver eu.pool.sks-keyservers.net --recv-keys 913DFF12F86258E5
  • And install pam_linotp (written in C):
apt-get update
apt-get install libpam-linotp
  • You have to create a symbolic link to the libpam-linotp module, otherwise PAM will be unable to locate it (here the example for a 64bit system)
ln -s /usr/lib/x86_64-linux-gnu/security/pam_linotp.so /lib/x86_64-linux-gnu/security/
  • Add a PAM configuration file for LinOTP, which can be used for SSH or any other loginprocedure of your choice:

/etc/pam.d/common-linotp:

auth [success=1 default=ignore]  pam_linotp.so nosslhostnameverify \
nosslcertverify url=https://192.168.8.203/validate/simplecheck
auth    requisite                       pam_deny.so
#The next line is required if common-auth is commented out in /etc/pam.d/sshd
#in order to have validation via pub-key + OTP without asking for the users password
auth    required                        pam_permit.so

Substitute the IP with the one of your LinOTP server.

WARNING: If you have self-signed https-certificates you must set the 'nosslhostnameverify' and 'nosslcertverify' plugin options unless you configure the certificates to be trusted [1].

The pam_linotp plugin knows a number of parameters:

url
the IP of your LinOTP machine
realm
sets the realm which should be used to get the authentication (i.e. 'realm=management'), if not set it defaults to the standardrealm of LinOTP
debug
if you have trouble, try to set this - you will get a lot more messages in the logfiles (i.e. in '/var/log/auth') - be careful: the PIN+OTP will be shown
nosslhostnameverify
ignore the mismatch of real hostname and the hostname in the certificate
nosslcertverify
necessary for self-signed certificates unless configured as trusted [1]
  • Include the new PAM file in the PAM login configuration for SSH; with pam_linotp you can put it before @include common-auth or after, depending on whether the OTP should be asked first or the Password of the user

/etc/pam.d/sshd

# PAM configuration for the Secure Shell service

# Read environment variables from /etc/environment and
# /etc/security/pam_env.conf.
auth       required     pam_env.so # [1]
# In Debian 4.0 (etch), locale-related environment variables were moved to
# /etc/default/locale, so read that as well.
auth       required     pam_env.so envfile=/etc/default/locale

# Include LinOTP authentication
@include common-linotp

# Standard Un*x authentication.
# Deactivate if public key + OTP only login should be allowed
# Mind to have the pam_permit.so line in common-linotp
#@include common-auth

# Disallow non-root logins when /etc/nologin exists.
account    required     pam_nologin.so

# Uncomment and edit /etc/security/access.conf if you need to set complex
# access limits that are hard to express in sshd_config.
# account  required     pam_access.so

# Standard Un*x authorization.
@include common-account

# Standard Un*x session setup and teardown.
@include common-session

# Print the message of the day upon successful login.
# This includes a dynamically generated part from /run/motd.dynamic
# and a static (admin-editable) part from /etc/motd.
session    optional     pam_motd.so  motd=/run/motd.dynamic noupdate
session    optional     pam_motd.so # [1]

# Print the status of the user's mailbox upon successful login.
session    optional     pam_mail.so standard noenv # [1]

# Set up user limits from /etc/security/limits.conf.
session    required     pam_limits.so

# Set up SELinux capabilities (need modified pam)
# session  required     pam_selinux.so multiple

# Standard Un*x password updating.
@include common-password

3.2 pam_py_linotp

Disclaimer: At the current state, pam_py_linotp does not interact correctly with the Debian SSH-PAM-stack. You can try your luck, but you have been warned...

  • The python-based PAM plugin requires the package libpam-python:
apt-get install libpam-python python

Then we need the PAM plugins itself. Here you have two options:

  1. Download by hand from 'https://pypi.python.org/pypi/pam_py_linotp/':
  • download:
wget --no-check-certificate -P /tmp https://pypi.python.org/packages/source/p/pam_py_linotp/pam_py_linotp-2.7.tar.gz
  • extract:
tar -C /tmp -xzf /tmp/pam_py_linotp-2.7.tar.gz
  • install:
cp /tmp/pam_py_linotp-2.7/src/pam_linotp.py /lib/security/
  1. or: install via pip
  • provide requirements:
apt-get install python-pip
  • install:
pip install pam_py_linotp

Whichever way was used for installing the plugin - now it needs to get activated.

  • Add a PAM configuration file for LinOTP, which can be used for SSH or any other loginprocedure of your choice:

/etc/pam.d/common-linotp:

auth    [success=1 default=ignore]      pam_python.so\
/lib/security/pam_linotp.py nosslhostnameverify nosslcertverify\
url=https://192.168.8.203/validate/simplecheck
auth    requisite                       pam_deny.so

Substitute the IP with the one of your LinOTP server.

WARNING: If you have self-signed https-certificates you must set the 'nosslhostnameverify' and 'nosslcertverify' plugin options unless you configure the certificates to be trusted [1].

The pam_linotp.py plugin knows a number of parameters:

url
the IP of your LinOTP machine
realm
sets the realm which should be used to get the authentication (i.e. 'realm=management'), if not set it defaults to the standardrealm of LinOTP
debug
if you have trouble, try to set this - you will get a lot more messages in the logfiles (i.e. in '/var/log/auth') - be careful: the PIN+OTP will be shown
nosslhostnameverify
ignore the mismatch of real hostname and the hostname in the certificate
nosslcertverify
necessary for self-signed certificates unless configured as trusted [1]
  • Include the new PAM file in the PAM login configuration for SSH:

/etc/pam.d/sshd

# PAM configuration for the Secure Shell service

# Read environment variables from /etc/environment and
# /etc/security/pam_env.conf.
auth       required     pam_env.so # [1]
# In Debian 4.0 (etch), locale-related environment variables were moved to
# /etc/default/locale, so read that as well.
auth       required     pam_env.so envfile=/etc/default/locale

# Include LinOTP authentication
@include common-linotp

# Standard Un*x authentication.
# Deactivate if public key + OTP only login should be allowed
# Mind to have the pam_permit.so line in common-linotp
#@include common-auth

# Disallow non-root logins when /etc/nologin exists.
account    required     pam_nologin.so

# Uncomment and edit /etc/security/access.conf if you need to set complex
# access limits that are hard to express in sshd_config.
# account  required     pam_access.so

# Standard Un*x authorization.
@include common-account

# Standard Un*x session setup and teardown.
@include common-session

# Print the message of the day upon successful login.
# This includes a dynamically generated part from /run/motd.dynamic
# and a static (admin-editable) part from /etc/motd.
session    optional     pam_motd.so  motd=/run/motd.dynamic noupdate
session    optional     pam_motd.so # [1]

# Print the status of the user's mailbox upon successful login.
session    optional     pam_mail.so standard noenv # [1]

# Set up user limits from /etc/security/limits.conf.
session    required     pam_limits.so

# Set up SELinux capabilities (need modified pam)
# session  required     pam_selinux.so multiple

# Standard Un*x password updating.
@include common-password

4 Authentication via RADIUS

RADIUS is widely used authentication protocol. If you have a RADIUS server running you can connect it to LinOTP and use its One Time Passwords as (additional) authentication layer for your SSH-clients (see our LinOTP+RADIUS documentation how such a setup is done: http://linotp.org/howtos/howto-radius.html).

Here we describe how to establish an authentication request from the SSH server to a RADIUS server in order to validate SSH clients.

We assume you have the following:

4.1 Connect to RADIUS via PAM (libpam-radius-auth)

The following must be done at the SSH server.

  • install the necessary PAM-RADIUS plugin:
apt-get install libpam-radius-auth
  • Adopt plugin configuration to your needs:

You will find the documentation and examples of the PAM module in /usr/share/doc/libpam-radius-auth. Have a look... And adopt the configuration of the PAM plugin in /etc/pam_radius_auth.conf according to your needs:

# RADIUSserver[:port] shared_secret_of_RADIUS_server      timeout (s)
# you can provide more than one server line
192.168.8.203          SECRET                  3
  • Add a PAM configuration file for RADIUS, which can be used for SSH or any other loginprocedure of your choice:

/etc/pam.d/common-linotp:

auth    [success=1 default=ignore]      pam_radius_auth.so
auth    requisite                       pam_deny.so
#The next line is required if common-auth is commented out in /etc/pam.d/sshd
#in order to have validation via pub-key + OTP without asking for the users password
auth    required                        pam_permit.so

You can set the parameter debug for the plugin to make it verbose in case of trouble.

  • Include the new PAM file in the PAM login configuration for SSH - it is important to put it before @include common-auth, because the other way around (ask first password of the user and then the OTP) does unfortunately not work correctly:

/etc/pam.d/sshd

# PAM configuration for the Secure Shell service

# Read environment variables from /etc/environment and
# /etc/security/pam_env.conf.
auth       required     pam_env.so # [1]
# In Debian 4.0 (etch), locale-related environment variables were moved to
# /etc/default/locale, so read that as well.
auth       required     pam_env.so envfile=/etc/default/locale

# Include LinOTP-RADIUS authentication
@include common-linotp

# Standard Un*x authentication.
# Deactivate if public key + OTP only login should be allowed
# Mind to have the pam_permit.so line in common-linotp
#@include common-auth

# Disallow non-root logins when /etc/nologin exists.
account    required     pam_nologin.so

# Uncomment and edit /etc/security/access.conf if you need to set complex
# access limits that are hard to express in sshd_config.
# account  required     pam_access.so

# Standard Un*x authorization.
@include common-account

# Standard Un*x session setup and teardown.
@include common-session

# Print the message of the day upon successful login.
# This includes a dynamically generated part from /run/motd.dynamic
# and a static (admin-editable) part from /etc/motd.
session    optional     pam_motd.so  motd=/run/motd.dynamic noupdate
session    optional     pam_motd.so # [1]

# Print the status of the user's mailbox upon successful login.
session    optional     pam_mail.so standard noenv # [1]

# Set up user limits from /etc/security/limits.conf.
session    required     pam_limits.so

# Set up SELinux capabilities (need modified pam)
# session  required     pam_selinux.so multiple

# Standard Un*x password updating.
@include common-password

5 Final Configuration of the SSH server

Whichever PAM-Module you have chosen - you must as last step activate challenge response functionality in you SSH server configuration. So open /etc/ssh/sshd_config and add:

ChallengeResponseAuthentication yes

# Optional if needed
# By default, users with publickey connect directly and users without
# publickey need OTP and password via PAM.
# The following directive enforces public key together with OTP for everyone.
AuthenticationMethods  publickey,keyboard-interactive:pam

Reload the SSH server and start testing...

6 Test

Connect with a SSH client and if PAM is working properly you should see something like this:

root@vpnclient:~# ssh paul@192.168.42.227
Your OTP:
Password:

If you use libpam-radius-auth there is no distinction between password and OTP and both questions are the same (so your user must know the correct sequence...). pam_echo could be used to inject messages before the authentication modules in order to identify the factors.

root@vpnclient:~# ssh paul@192.168.42.227
Password:
Password:

If you encounter any problems:

  • activate debug feature of the PAM module
  • read auth-logfile of the SSH server: /var/log/auth.log
  • have a look at the LinOTP Audit Trail and/or the LinOTP Logfile /var/log/linotp/linotp.log
[1](1, 2, 3, 4) In production environment you should not use nosslcertverify for the LinOTP Pam modules. Here is an example how to declare your LinOTP server certificate trustworth for Debian jessie:
  • Copy the certificate to the SSH server:
scp /etc/ssl/certs/lseappliance.pem IP_SSH_SERVER:/usr/share/ca-certificates/lseappliance.crt
  • Activate the new certificate on the SSH server:
dpkg-reconfigure ca-certificates