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:
- Run the fetcher as unprivileged user (
AuthorizedKeysCommandUser
) - Use LDAPS (LDAP over TLS)
- Restrict LDAP bind account to read-only access
- 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