When using Kerberos with SSH across multiple hops (Laptop → Server1 → Server2), the authentication flow leverages ticket-granting tickets (TGTs) and service tickets for seamless authentication. Here's the technical breakdown:
1. [Laptop] User enters: ssh username@server1.example.com
2. [Laptop] SSH client sends username to Server1
3. [Server1] Checks for existing Kerberos credentials:
- If none found, falls back to password auth
4. [Laptop] User enters password (PAM authentication occurs)
5. [Server1] Validates credentials with KDC:
- AS-REQ: Authentication Service Request to KDC
- AS-REP: Receives TGT encrypted with user's secret key
1. [Server1] User executes: ssh server2.example.com
2. [Server1] Presents TGT to KDC for service ticket:
- TGS-REQ: Ticket Granting Service Request for host/server2.example.com
3. [KDC] Returns:
- TGS-REP: Service ticket encrypted with Server2's secret key
4. [Server1] Presents service ticket to Server2
5. [Server2] Validates ticket and establishes session
For this flow to work, ensure proper configuration:
# /etc/krb5.conf on all hosts
[libdefaults]
default_realm = EXAMPLE.COM
forwardable = true
proxiable = true
# /etc/ssh/sshd_config on servers
GSSAPIAuthentication yes
GSSAPICleanupCredentials yes
Use these commands to troubleshoot authentication issues:
# Check ticket cache
klist
# Obtain new ticket
kinit username
# Verify SSH with Kerberos debugging
ssh -vvv -K server2.example.com
# Check KDC logs
tail -f /var/log/krb5kdc.log
Here's how to implement Kerberos-authenticated SSH in Python:
import gssapi
from paramiko import SSHClient, AutoAddPolicy
# Create security context
name = gssapi.Name("host@server2.example.com")
ctx = gssapi.SecurityContext(name=name, usage="initiate")
# Establish SSH connection
client = SSHClient()
client.set_missing_host_key_policy(AutoAddPolicy())
client.connect('server2.example.com', gss_auth=True)
# Execute remote command
stdin, stdout, stderr = client.exec_command('hostname')
print(stdout.read().decode())
client.close()
When using Kerberos with SSH in a multi-hop scenario, the authentication process involves several protocol exchanges between four entities: Client (Laptop), Intermediate Server (Server1), Target Server (Server2), and the Kerberos KDC (Key Distribution Center). Here's the complete breakdown:
1. Laptop → Server1: SSH_MSG_USERAUTH_REQUEST (username) 2. Server1 → Laptop: SSH_MSG_USERAUTH_FAILURE (supported auth methods) 3. Laptop → Kerberos KDC: AS_REQ (Authentication Service Request) 4. Kerberos KDC → Laptop: AS_REP (TGT - Ticket Granting Ticket) 5. Laptop → Kerberos KDC: TGS_REQ (Service Ticket for Server1) 6. Kerberos KDC → Laptop: TGS_REP (Service Ticket + session key) 7. Laptop → Server1: SSH_MSG_USERAUTH_REQUEST (GSSAPI token) 8. Server1 → Kerberos KDC: AP_REQ (Ticket verification) 9. Kerberos KDC → Server1: AP_REP (Verification result) 10. Server1 → Laptop: SSH_MSG_USERAUTH_SUCCESS
The key difference in the second hop is credential forwarding:
1. Server1 → Server2: SSH_MSG_USERAUTH_REQUEST (username) 2. Server2 → Server1: SSH_MSG_USERAUTH_FAILURE (supports GSSAPI-with-mic) 3. Server1 → Server2: SSH_MSG_USERAUTH_REQUEST (GSSAPI token) - Uses delegated credentials from initial authentication 4. Server2 → Kerberos KDC: AP_REQ (Ticket verification) 5. Kerberos KDC → Server2: AP_REP (Verification result) 6. Server2 → Server1: SSH_MSG_USERAUTH_SUCCESS
For this to work, ensure these settings in /etc/ssh/sshd_config
on both servers:
GSSAPIAuthentication yes GSSAPICleanupCredentials yes GSSAPIStrictAcceptorCheck no # Important for credential delegation
And in /etc/krb5.conf
on all systems:
[libdefaults] default_realm = YOURDOMAIN.COM forwardable = true proxiable = true [realms] YOURDOMAIN.COM = { kdc = kerberos.yourdomain.com admin_server = kerberos.yourdomain.com }
Use these commands to verify the Kerberos ticket flow:
# Check ticket cache klist # Enable SSH debugging ssh -vvv user@server1 # Verify GSSAPI support ssh -Q gssapi-keyex ssh -Q gssapi-with-mic
import paramiko from gssapi import Credentials # Acquire delegated credentials krb_creds = Credentials(usage='initiate') ssh = paramiko.SSHClient() ssh.load_system_host_keys() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # Connect with GSSAPI ssh.connect('server2', gss_auth=True, gss_kex=True, gss_deleg_creds=True, gss_host='server2.yourdomain.com') stdin, stdout, stderr = ssh.exec_command('hostname') print(stdout.read().decode())