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:
- setup of LinOTP
- setup of RADIUS and connecting it to LinOTP please refer to: http://linotp.org/howtos/howto-radius.html
- basic setup of SSH, neither client nor server
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:
- 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/
- 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:
- a SSH server
- a SSH client
- LinOTP
- a RADIUS server connected to your LinOTP instance: http://linotp.org/howtos/howto-radius.html
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