Skip to content

Commit

Permalink
Simplify Kerberos authentication
Browse files Browse the repository at this point in the history
Avoid setting system properties, allowing users to set them instead or
falling back to the krb5.conf file. Also avoid using a temporary file by
creating an in-memory Configuration object.
  • Loading branch information
bluekeyes committed Aug 8, 2015
1 parent fb462f4 commit 13b2a5d
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,16 @@
*/
public class KerberosSshCredential extends SshCredential {

public static KerberosSshCredential of(String username, String realm, String kdcHostname) {
return new KerberosSshCredential(username, realm, kdcHostname);
public static KerberosSshCredential of(String username) {
return new KerberosSshCredential(username);
}

private final String realm;
private final String kdcHostname;

private KerberosSshCredential(String username, String realm, String kdcHostname) {
public KerberosSshCredential(String username) {
super(username);
this.realm = realm;
this.kdcHostname = kdcHostname;
}

@Override
public void authenticate(SshAuthenticator authenticator) throws IOException {
authenticator.authByKerberos(this, realm, kdcHostname);
}

public String getRealm() {
return realm;
authenticator.authByKerberos(this);
}

public String getKdcHostname() {
return kdcHostname;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private PasswordSshCredential(String username, char[] password) {

@Override
public void authenticate(SshAuthenticator authenticator) throws IOException {
authenticator.authByPassword(this, password.clone());
authenticator.authByPassword(this);
}

public char[] getPassword() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private PublicKeySshCredential(String username, byte[] privateKey, Optional<Path

@Override
public void authenticate(SshAuthenticator authenticator) throws IOException {
authenticator.authByPublicKey(this, privateKey.clone());
authenticator.authByPublicKey(this);
}

public byte[] getPrivateKey() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,10 @@
*/
public interface SshAuthenticator extends Authenticator {

void authByPassword(SshCredential credential, char[] password) throws IOException;
void authByPassword(PasswordSshCredential credential) throws IOException;

void authByPublicKey(SshCredential credential, byte[] privateKey) throws IOException;
void authByPublicKey(PublicKeySshCredential credential) throws IOException;

void authByKerberos(SshCredential credential, String realm, String kdcHostname)
throws IOException;
void authByKerberos(KerberosSshCredential credential) throws IOException;

}
12 changes: 9 additions & 3 deletions ssh/src/main/java/com/palantir/giraffe/ssh/SshHostAccessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,16 @@ public static SshHostAccessor forKey(String hostname, String username, String pr
return forCredential(host, PublicKeySshCredential.of(username, privateKey));
}

public static SshHostAccessor forKerberos(String hostname, String username, String realm,
String kdcHostname) {
/**
* Returns a new {@code SshHostAccessor} that authenticates as the given
* user using the system Kerberos configuration.
*
* @param hostname the name of the host to access
* @param username the user to authenticate as
*/
public static SshHostAccessor forKerberos(String hostname, String username) {
Host host = Host.fromHostname(hostname);
return forCredential(host, KerberosSshCredential.of(username, realm, kdcHostname));
return forCredential(host, KerberosSshCredential.of(username));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,21 @@
package com.palantir.giraffe.ssh.internal;

import java.io.IOException;
import java.nio.charset.Charset;

import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

import org.ietf.jgss.GSSException;
import org.ietf.jgss.Oid;

import com.palantir.giraffe.file.MoreFiles;
import com.palantir.giraffe.file.TempPath;
import com.google.common.collect.ImmutableMap;
import com.palantir.giraffe.ssh.KerberosSshCredential;
import com.palantir.giraffe.ssh.PasswordSshCredential;
import com.palantir.giraffe.ssh.PublicKeySshCredential;
import com.palantir.giraffe.ssh.SshAuthenticator;
import com.palantir.giraffe.ssh.SshCredential;
import com.palantir.giraffe.ssh.SshSystemRequest;

import net.schmizz.sshj.Config;
Expand All @@ -39,6 +42,8 @@

final class SshConnectionFactory {

private static final String KRB_ENTRY_NAME = "GiraffeKrb5";

private final Config config;

public SshConnectionFactory(Config sshjConfiguration) {
Expand All @@ -60,79 +65,70 @@ public SSHClient newAuthedConnection(SshSystemRequest request) throws IOExceptio
return sshClient;
}

private final class Authenticator implements SshAuthenticator {
private static final class Authenticator implements SshAuthenticator {

private final SSHClient client;

public Authenticator(SSHClient client) {
this.client = client;
}

@Override
public void authByPassword(SshCredential credential, char[] password)
throws IOException {
client.authPassword(credential.getUsername(), password);
public void authByPassword(PasswordSshCredential credential) throws IOException {
client.authPassword(credential.getUsername(), credential.getPassword());
}

@Override
public void authByPublicKey(SshCredential credential, byte[] privateKey)
throws IOException {
KeyProvider keyProvider = client.loadKeys(new String(privateKey), null, null);
public void authByPublicKey(PublicKeySshCredential credential) throws IOException {
String privateKey = new String(credential.getPrivateKey());
KeyProvider keyProvider = client.loadKeys(privateKey, null, null);
client.authPublickey(credential.getUsername(), keyProvider);
}

private static final String REALM_PROPERTY = "java.security.krb5.realm";
private static final String KDC_PROPERTY = "java.security.krb5.kdc";
private static final String CONFIG_PROPERTY = "java.security.auth.login.config";

@Override
public void authByKerberos(SshCredential credential, String realm, String kdcHostname)
throws IOException {

final String originalRealm = System.getProperty(REALM_PROPERTY);
final String originalKdc = System.getProperty(KDC_PROPERTY);
final String originalConfig = System.getProperty(CONFIG_PROPERTY);

try (TempPath jaasConfFile = TempPath.createFile()) {
MoreFiles.write(jaasConfFile.path(),
"Krb5LoginContext { com.sun.security.auth.module.Krb5LoginModule " +
"required refreshKrb5Config=true useTicketCache=true debug=true ; };",
Charset.defaultCharset()
);

// set properties necessary for Krb5LgoinContext
System.setProperty(REALM_PROPERTY, realm);
System.setProperty(KDC_PROPERTY, kdcHostname);
System.setProperty(CONFIG_PROPERTY,
jaasConfFile.path().toAbsolutePath().toString());

LoginContext lc = null;
try {
lc = new LoginContext("Krb5LoginContext");
lc.login();
} catch (LoginException e) {
throw new UserAuthException(e);
}

Oid krb5Oid;
try {
krb5Oid = new Oid("1.2.840.113554.1.2.2");
} catch (GSSException e) {
throw new UserAuthException("Failed to create Kerberos OID", e);
}

client.authGssApiWithMic(credential.getUsername(), lc, krb5Oid);
} finally {
restoreProperty(REALM_PROPERTY, originalRealm);
restoreProperty(KDC_PROPERTY, originalKdc);
restoreProperty(CONFIG_PROPERTY, originalConfig);
public void authByKerberos(KerberosSshCredential credential) throws IOException {
String user = credential.getUsername();

LoginContext lc = null;
try {
lc = new LoginContext(KRB_ENTRY_NAME, null, null, new KrbAuthConfiguration(user));
lc.login();
} catch (LoginException e) {
throw new UserAuthException(e);
}

Oid krb5Oid;
try {
krb5Oid = new Oid("1.2.840.113554.1.2.2");
} catch (GSSException e) {
// this will never happen, krb5 OID is always valid
throw new AssertionError();
}
client.authGssApiWithMic(user, lc, krb5Oid);
}
}

private static final class KrbAuthConfiguration extends Configuration {
private final AppConfigurationEntry krbEntry;

KrbAuthConfiguration(String principal) {
krbEntry = new AppConfigurationEntry(
"com.sun.security.auth.module.Krb5LoginModule",
LoginModuleControlFlag.REQUIRED,
ImmutableMap.<String, String>builder()
.put("refreshKrb5Config", "true")
.put("useTicketCache", "true")
.put("doNotPrompt", "true")
.put("principal", principal)
.build());
}

private void restoreProperty(String key, String originalValue) {
if (originalValue != null) {
System.setProperty(key, originalValue);
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
if (KRB_ENTRY_NAME.equals(name)) {
return new AppConfigurationEntry[] { krbEntry };
} else {
System.clearProperty(key);
return null;
}
}
}
Expand Down

0 comments on commit 13b2a5d

Please sign in to comment.