SSH key-based authentication with LDAP

SSH key-based authentication with LDAP

I wanted to allow users to log in with their personal SSH keys on our AWS instances so that I wouldn’t have to neither hand over the main key to everyone and neither copy everyone’s key to all machines. The OS for this were Amazon Linux and CentOS.

Install OpenLDAP

[user@host]# yum install openldap-servers

Set root DN and admin

Create password hash with slappasswd:

[user@host]# slappasswd
New password:
Re-enter new password:
{SSHA}Ky1iHfH03xYvjCjkZcm1e0sWtlS7T/KY

Create a file init.ldif with this content:

dn: olcDatabase={2}bdb,cn=config
changetype: modify
replace: olcSuffix
olcSuffix: dc=example,dc=com
-
replace: olcRootDN
olcRootDN: cn=Manager,dc=example,dc=com
-
replace: olcRootPW
olcRootPW: {SSHA}Ky1iHfH03xYvjCjkZcm1e0sWtlS7T/KY

Copy the password hash you created with slappasswd and replace the one in the example above. If you copied the hash, the password is “1” (the number one). You should also pick the correct root DN for your system. This is usually a domain name or an organization name and the country.

Load this file into OpenLDAP:

[user@host]# ldapmodify -Y EXTERNAL -H ldapi:/// -f init.ldif

Now create the base structure. We start with the organization. Copy the text below into a file called org.ldif:

dn: dc=example,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
dc: example.com
o: example.com

And we load it with

[user@host]# ldapadd -Y EXTERNAL -D "cn=Manager,dc=example,dc=com" -W -H ldapi:/// -f org.ldif

Repeat the same for the base entry for user accounts in user-org.ldif:

dn: ou=Users,dc=at
ou: Users
objectClass: top
objectclass: organizationalunit
[user@host]# ldapadd -Y EXTERNAL -D "cn=Manager,dc=example,dc=com" -W -H ldapi:/// -f user-org.ldif

Now we load the schema that will allow us to store the SSH key in LDAP. Create the file  openssh-lpk.ldif with this content:

dn: cn=openssh-lpk,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: openssh-lpk
olcAttributeTypes: ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey'
 DESC 'MANDATORY: OpenSSH Public key'
 EQUALITY octetStringMatch
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
olcObjectClasses: ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY
 DESC 'MANDATORY: OpenSSH LPK objectclass'
 MAY ( sshPublicKey $ uid )
 )

And load it once more with:

[user@host]# ldapadd -Y EXTERNAL -D "cn=Manager,dc=example,dc=com" -W -H ldapi:/// -f openssh-lpk.ldif

Now everything is in place to start creating users. I recommend that you do this with a UI or a script. But for the purpose of this post we’ll once again create an LDIF. This file is called user.ldif:

dn: uid=username,ou=Users,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
objectClass: ldapPublicKey
cn: username
sn: username
loginShell: /bin/bash
uidNumber: 1000
gidNumber: 1000
homeDirectory: /home/username
sshPublicKey: dGhpcyBpcyBqdXN0IGFuIGV4YW1wbGUK

Here you must change all occurrences of “username” to match the username you want to add. You might also note the absence of a password. “userPassword” is marked “optional”, so we can omit it because we’re using a SSH key.

The key itself is the public key of the user, Base64-encoded. You can do this on the command line:

[user@host]# base64 id_rsa.pub
dGhpcyBpcyBqdXN0IGFuIGV4YW1wbGUK

Once everything is in place we can load the user:

[user@host]# ldapadd -Y EXTERNAL -D "cn=Manager,dc=example,dc=com" -W -H ldapi:/// -f user.ldif

Now let’s test if we can retrieve the key!

[user@host]# ldapsearch -x -H ldap://hostname:389 '(&(objectClass=posixAccount)(uid='"username"'))' -b "ou=Users,dc=at" 'sshPublicKey'

Once again, substitute “username” for your username. Also change “hostname” to the address of your OpenLDAP server.

This should return the key we just added:

# extended LDIF
#
# LDAPv3
# base <ou=Users,dc=at> with scope subtree
# filter: (&(objectClass=posixAccount)(uid=kengelke))
# requesting: sshPublicKey
#

# kengelke, Users, at
dn: uid=username,ou=Users,dc=example,dc=com
sshPublicKey:: dGhpcyBpcyBqdXN0IGFuIGV4YW1wbGUK

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

Now it’s time to configure SSHd to use LDAP to authenticate users.

The first part is easy, setting up SSHd itself.

The first step is to make sure we have the LDAP clients, like ldapsearch:

[user@home]# yum install openldap-clients nss-pam-ldapd

Next we create a script that will query LDAP and return the decoded SSH key. Create the file /usr/local/bin/query-ldap.sh:

#!/bin/sh

ldapsearch -x -H ldap://10.0.1.217:389 '(&(objectClass=posixAccount)(uid='"$1"'))' -b "ou=Users,dc=at" 'sshPublicKey' | sed "/^sshPublicKey:: /{:l N;/\n./{s/\n //;bl}}" | egrep "^sshPublicKey" | sed 's/sshPublicKey:: //' | base64 -d

This script takes the LDIF above, extracts the SSH key and decodes the Base64. The result is something SSHd can work with. Now we tell the daemon to use this script. Edit /etc/ssh/sshd_config and add these two lines:

AuthorizedKeysCommand /usr/local/bin/query-ldap.sh
AuthorizedKeysCommandUser nobody

The second lines tells the daemon to run the script as a user with low privileges, which is good practice.

Now we can already log in with our key but only if “username” exists on the local system. We also need to set up PAM to query LDAP for information like uid, gid and home directory.

We start with /etc/nsswitch.conf. Open it and set “ldap” for “passwd”, “shadow” and “group”:

passwd: ldap files
shadow: ldap files
group: ldap files

In this example LDAP is the primary source for authentication information. You can also configure it to check local files first.

Next we edit /etc/pam.d/system-auth. Mine looks like this:

auth sufficient pam_ldap.so use_first_pass
auth required pam_env.so
auth sufficient pam_unix.so nullok try_first_pass
auth requisite pam_succeed_if.so uid >= 1000 quiet_success
auth required pam_deny.so

account sufficient pam_ldap.so
account required pam_unix.so
account sufficient pam_localuser.so
account sufficient pam_succeed_if.so uid < 1000 quiet
account required pam_permit.so

password sufficient pam_ldap.so use_first_pass
password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok
password required pam_deny.so

session optional pam_keyinit.so revoke
session required pam_limits.so
-session optional pam_systemd.so
session optional pam_ldap.so
session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session required pam_unix.so

TODO add pam_mkhomedir

TODO /etc/nslcd.conf

 

base dc=example,dc=com
base passwd ou=Users,dc=example,dc=com
base shadow ou=Users,dc=example,dc=com
uri ldap://hostname/
scope sub

 

Leave a comment