How to Implement LDAP-Based SSH Key Authentication in OpenSSH >=6.2


3 views

Managing SSH keys across multiple servers becomes exponentially complex as infrastructure scales. Traditional approaches involving manual authorized_keys file management or configuration management tools introduce latency - particularly problematic when dealing with auto-scaling environments where new instances need immediate access.

LDAP directories already contain user identities and attributes. Storing SSH public keys in LDAP as the single source of truth provides several advantages:

  • Instant propagation of key updates across all servers
  • Elimination of key synchronization delays
  • Audit trail through LDAP modification logs
  • Integration with existing user onboarding/offboarding workflows

Modern OpenSSH (>=6.2) supports external key sources through these critical sshd_config directives:

AuthorizedKeysCommand /usr/local/bin/ldap-ssh-key-fetcher
AuthorizedKeysCommandUser nobody

The command should output authorized_keys format when passed the username as first argument.

Here's a Python implementation using python-ldap:

#!/usr/bin/env python3
import ldap
import sys

LDAP_URI = 'ldap://your.ldap.server'
BASE_DN = 'ou=People,dc=example,dc=com'
SSH_KEY_ATTR = 'sshPublicKey'

def fetch_keys(username):
    conn = ldap.initialize(LDAP_URI)
    conn.simple_bind_s()
    
    filter = f'(uid={username})'
    results = conn.search_s(BASE_DN, ldap.SCOPE_SUBTREE, filter, [SSH_KEY_ATTR])
    
    if not results:
        return ''
    
    _, entry = results[0]
    return '\n'.join(key.decode() for key in entry.get(SSH_KEY_ATTR, []))

if __name__ == '__main__':
    print(fetch_keys(sys.argv[1]))

You'll need to extend your LDAP schema to store SSH keys. For OpenLDAP, add this to your schema:

attributetype ( 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 )

objectclass ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey'
    SUP top AUXILIARY
    DESC 'SSH Public Key Information'
    MAY ( sshPublicKey $ sshPublicKeyLegacy ) )

To minimize LDAP queries:

  • Implement connection pooling in your fetcher script
  • Consider caching results with TTL (e.g., 5 minutes)
  • Use LDAP persistent searches if supported

When implementing this solution:

  1. Run the fetcher as unprivileged user (AuthorizedKeysCommandUser)
  2. Use LDAPS (LDAP over TLS)
  3. Restrict LDAP bind account to read-only access
  4. Implement rate limiting in sshd_config

Common issues and their solutions:

Symptom Diagnosis
Authentication fails silently Check fetcher script executes properly when run manually
LDAP connection timeouts Verify network connectivity and DNS resolution
Permission denied errors Ensure AuthorizedKeysCommandUser has execute permissions

Managing SSH keys across multiple servers becomes increasingly complex as infrastructure scales. Traditional approaches involving manual key distribution or configuration management tools introduce delays - particularly problematic when using custom AMIs that need immediate access.

Since OpenSSH 6.2, we can leverage AuthorizedKeysCommand to fetch keys dynamically. Here's the core configuration for /etc/ssh/sshd_config:

AuthorizedKeysCommand /usr/local/bin/ldap-ssh-key-fetcher
AuthorizedKeysCommandUser nobody
PubkeyAuthentication yes
PasswordAuthentication no

We need to store SSH public keys in LDAP. The recommended approach is using the ldapPublicKey attribute in the posixAccount objectClass. Example LDIF:

dn: uid=jdoe,ou=people,dc=example,dc=com
objectClass: posixAccount
objectClass: ldapPublicKey
uid: jdoe
sshPublicKey: ssh-rsa AAAAB3NzaC1yc2E... jdoe@workstation

Create /usr/local/bin/ldap-ssh-key-fetcher (Python example):

#!/usr/bin/env python3
import sys
import ldap

def main():
    username = sys.argv[1]
    ldap_uri = "ldap://ldap.example.com"
    base_dn = "ou=people,dc=example,dc=com"
    search_filter = f"(uid={username})"
    
    try:
        conn = ldap.initialize(ldap_uri)
        conn.simple_bind_s()
        results = conn.search_s(
            base_dn,
            ldap.SCOPE_SUBTREE,
            search_filter,
            ['sshPublicKey']
        )
        for dn, entry in results:
            if 'sshPublicKey' in entry:
                for key in entry['sshPublicKey']:
                    print(key.decode('utf-8'))
    except Exception as e:
        sys.stderr.write(f"Error: {str(e)}\n")
        sys.exit(1)

if __name__ == "__main__":
    main()

To prevent LDAP lookup latency from affecting SSH login times:

  • Implement LDAP connection pooling
  • Cache results with TTL (e.g., using redis)
  • Consider using LDAP's memberOf attribute for group-based access control

Key security measures to implement:

# Set strict permissions
chmod 755 /usr/local/bin/ldap-ssh-key-fetcher
chown root:root /usr/local/bin/ldap-ssh-key-fetcher

# Configure LDAP TLS
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)

For environments not using OpenSSH 6.2+:

  • SSH certificate authority
  • PAM modules like pam_ldap with SSH key support
  • Commercial solutions like Teleport or HashiCorp Boundary