Alex headshot

AlBlue’s Blog

Macs, Modularity and More

SSHD Server in Java with Kerberos Authentication

2011 Howto

Having tried (and failed) to get a working SSHD server that supported Kerberos authentication in the past, I was pleasantly surprised by the recently-released Apache Mina SSHD project.

Setting up a project is relatively straightforward; you need to get hold of Apache Mina SSHD 0.6 Jar along with its dependencies (note; since it uses SLF4J for logging, you'll also need an SLF4J implementation such as SLF4J-Log4J12).

You can then create a simple SSH server with the following:


server = SshServer.setUpDefaultServer();
server.setPort(1234);
server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("/path/to/the/key"));

This sets up a server, running on port 1234, which persists its server key in /path/to/the/key. (It's not mandatory to persist the key; without an argument it regenerates it each time the server starts. But then your clients will complain each time they start that the key has changed if you don't.)

Mind you, as it stands, it's not very useful. It doesn't allow any authentication and doesn't do anything when users are authenticated. First, let's see how we can configure the SSHD server to do something when a connection occurs:


server.setShellFactory(new ProcessShellFactory(new String[] { "ls" });

The ShellFactory is used to instantiate a new process (copying the system input/output) for each new connection that is made. It's possible to write your own as well; in addition, you can take advantage of the SSH channels to do alternative operations.

Kerberos

For the authentication, we're going to look at Kerberos. It's a pain to get right, but the SSH server is capable of supporting it and so it's instructive to know how. This isn't a full guide to Kerberos – one assumes that you have the basics and a working Kerberos environment in the first place. If not, feel free to skip the rest of this section.

Kerberos authenticates users via principals, which are usually of the form me@EXAMPLE.COM for users, and service/canonical.host.name@EXAMPLE.COM for services. For SSH, the normal principal is host/canonical.host.name (the default realm gets added automatically).

Keytabs

To generate a service authenticated by kerberos, the server needs access to a per-host key. This is stored in a Kerberos keytab, which can be exported from the kerberos infrastructure. The details of how to extract a keytab are a part of standard Kerberos administration; in my case, I extracted out a keytab with kadmin.local and the ktadd -k canonical.host.name.keytab -norandkey host/canonical.host.name. Note that this keytab is equivalent to a hashed password, and should be protected as such. Also, the kadmin may result in the key version number being bumped and the key re-randomised; this is to prevent general extraction of the keys. You might find that the server's key is put in /etc/krb5.keytab – although it will be readable by root by default and shouldn't be opened up to the wider world – keytabs are intended only to be permissioned for the individual application(s) that require them.

The default SSH principal will be host/canonical.host.name, which is very often shared with the real SSH server running on the box. Unfortunately there is no easy way of changing this, which means that the Java SSHD server needs access to the same key.

Configuring SSHD for Kerberos authentication

Assuming you have the keytab problem solved, the next step is to hook it up to the server. You can do this with the following:


List<NamedFactory<UserAuth>> userAuthFactories = new ArrayList<NamedFactory<UserAuth>>(1);
userAuthFactories.add(new UserAuthGSS.Factory());
server.setUserAuthFactories(userAuthFactories);

GSSAuthenticator authenticator = new GSSAuthenticator();
authenticator.setKeytabFile(keytab);
// authenticator.setServicePrincipalName(principalName);
server.setGSSAuthenticator(authenticator);

The first part hooks up the server with the GSS authenticator. This is the General Security Service, which is a generic wrapper around Kerberos and other authentication mechanisms. Note that the Apache Mina implementation is specifically crafted towards the Sun implementation, so if you're running in a classloader constrained environment or on a non-Sun/Oracle JVM then there may be problems with this approach. (The CredentialManager can be overridden to be customised to solve this problem.)

The second part configures a GSSAuthenticator to allow a specific keytab to be used. Without this, authentication will fail as the server won't be able to get a kerberos credential to offer to users. (Both the server and client need to have credentials in the Kerberos world; it will often fail because the server has no Kerberos credential sourced from its own keytab.)

Finally, you can optionally specify what the principal is going to be. This is only really of use if you have a custom Java SSH client that can specify its principal. However, it can be useful sometimes if the server is calculating the default principal incorrectly; for example, many hosts have multiple interfaces and therefore the potential for multiple canonical names. The default is to calculate host/ + InetAddress.getLocalHost().getCanonicalName(); so if this is wrong, then you may choose to override it here.

Conclusion

With this code, and a working Kerberos environment, you can now start an SSH server in Java, and connect to it via SSH with Kerberos authentication.

I have put up an example project at GitHub which you can clone via http://github.com/alblue/Examples.git. You can generate a -jar-with-dependencies by running mvn, and specify a port, path to keytab and (optionally) the principal to use for authentication. Note that it includes a log4j.properties file with debug enabled; when you run it you will see a lot of output indicating the stage of the results.


$ java -jar KerberizedSshServer-1.0-jar-with-dependencies.jar 1234 /tmp/canonical.host.name.keytab host/canonical.host.name
2011-11-02 22:25:04,886 [main] INFO  org.apache.sshd.common.util.SecurityUtils - BouncyCastle not registered, using the default JCE provider
2011-11-02 22:25:13,935 [NioProcessor-2] INFO  org.apache.sshd.server.session.ServerSession - Session created...
2011-11-02 22:25:13,963 [NioProcessor-2] INFO  org.apache.sshd.server.session.ServerSession - Client version string: SSH-2.0-OpenSSH_5.6
2011-11-02 22:25:13,963 [NioProcessor-2] DEBUG org.apache.sshd.server.session.ServerSession - Received packet SSH_MSG_KEXINIT
⋮
2011-11-02 22:25:14,060 [NioProcessor-2] INFO  org.apache.sshd.server.session.ServerSession - Authenticating user 'me' with method 'gssapi-with-mic'
2011-11-02 22:25:14,062 [NioProcessor-2] DEBUG org.apache.sshd.server.auth.gss.UserAuthGSS - UserAuthGSS: found Kerberos 5
2011-11-02 22:25:14,083 [NioProcessor-2] INFO  org.apache.sshd.server.session.ServerSession - Authentication not finished
2011-11-02 22:25:14,091 [NioProcessor-2] DEBUG org.apache.sshd.server.session.ServerSession - Received packet SSH_MSG_USERAUTH_INFO_RESPONSE
2011-11-02 22:25:14,091 [NioProcessor-2] INFO  org.apache.sshd.server.session.ServerSession - Received SSH_MSG_USERAUTH_INFO_RESPONSE
2011-11-02 22:25:14,091 [NioProcessor-2] DEBUG org.apache.sshd.server.auth.gss.UserAuthGSS - In krb5.next: msg = SSH_MSG_USERAUTH_INFO_RESPONSE
2011-11-02 22:25:14,118 [NioProcessor-2] INFO  org.apache.sshd.server.auth.gss.UserAuthGSS - GSS identity is me@EXAMPLE.COM
2011-11-02 22:25:14,118 [NioProcessor-2] INFO  org.apache.sshd.server.session.ServerSession - Authentication still not finished
2011-11-02 22:25:14,120 [NioProcessor-2] DEBUG org.apache.sshd.server.session.ServerSession - Received packet SSH_MSG_USERAUTH_GSSAPI_MIC
2011-11-02 22:25:14,120 [NioProcessor-2] INFO  org.apache.sshd.server.session.ServerSession - Received SSH_MSG_USERAUTH_GSSAPI_MIC
2011-11-02 22:25:14,120 [NioProcessor-2] DEBUG org.apache.sshd.server.auth.gss.UserAuthGSS - In krb5.next: msg = SSH_MSG_USERAUTH_GSSAPI_MIC
2011-11-02 22:25:14,121 [NioProcessor-2] DEBUG org.apache.sshd.server.auth.gss.UserAuthGSS - MIC verified
⋮
2011-11-02 22:25:14,142 [NioProcessor-2] DEBUG org.apache.sshd.server.session.ServerSession - Received packet SSH_MSG_CHANNEL_REQUEST
2011-11-02 22:25:14,142 [NioProcessor-2] INFO  org.apache.sshd.server.channel.ChannelSession - Received SSH_MSG_CHANNEL_REQUEST on channel 0
2011-11-02 22:25:14,142 [NioProcessor-2] INFO  org.apache.sshd.server.channel.ChannelSession - Received channel request: shell

To connect from the same host, you likely can do ssh -p 1234 canonical.host.name, and because the user will have access to the per-user credential cache (if you're running the server as the same userid) then it will Just Work™

However, if you're connecting from a remote host, then you may need to invoke it with ssh -K -p 1234 canonical.host.name instead. The -K says to forward the ticket to the server (which is why it says GSS identity is me@EXAMPLE.COM in the debug trace). Your SSH client can supply per-server or global configurations to always forward Kerberos tickets (GSSAPIAuthentication yes in .ssh/config). Without the forwarded Kerberos ticket, the server can't authenticate you against the kerberos mechanism:


2011-11-02 22:30:28,632 [NioProcessor-2] INFO  org.apache.sshd.server.session.ServerSession - Authorized authentication methods: gssapi-with-mic
2011-11-02 22:30:28,634 [NioProcessor-2] DEBUG org.apache.sshd.server.session.ServerSession - Received packet SSH_MSG_USERAUTH_REQUEST
2011-11-02 22:30:28,634 [NioProcessor-2] INFO  org.apache.sshd.server.session.ServerSession - Received SSH_MSG_USERAUTH_REQUEST
2011-11-02 22:30:28,634 [NioProcessor-2] INFO  org.apache.sshd.server.session.ServerSession - Authenticating user 'me' with method 'none'
2011-11-02 22:30:28,634 [NioProcessor-2] INFO  org.apache.sshd.server.session.ServerSession - Unsupported authentication method 'none'

If you're connecting from a Windows host then you'll need to have a client which is capable of making SSH connections and forwarding the Kerberos tickets. Some versions of Putty have GSSAPI support but check whether a Unix-Unix connection works before trying to debug Windows related problems. Note that some clients (e.g. Eclipse EGit) do not support Kerberos-only authentication, but instead use password-based authentication.

Note that it is possible to configure SSHD to use passwords as well (see the UserAuthPassword.Factory()) but this is outside the scope of this post.