diff --git a/ssh/src/main/java/com/palantir/giraffe/ssh/KerberosSshCredential.java b/ssh/src/main/java/com/palantir/giraffe/ssh/KerberosSshCredential.java new file mode 100644 index 00000000..75c8f01a --- /dev/null +++ b/ssh/src/main/java/com/palantir/giraffe/ssh/KerberosSshCredential.java @@ -0,0 +1,53 @@ +/** + * Copyright 2015 Palantir Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.palantir.giraffe.ssh; + +import java.io.IOException; + +/** + * Authenticates SSH connections via GSS-API in a Kerberos environment. + * + * @author benh + */ +public class KerberosSshCredential extends SshCredential { + + public static KerberosSshCredential of(String username, String realm, String kdcHostname) { + return new KerberosSshCredential(username, realm, kdcHostname); + } + + private final String realm; + private final String kdcHostname; + + private KerberosSshCredential(String username, String realm, String kdcHostname) { + 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; + } + + public String getKdcHostname() { + return kdcHostname; + } + +} diff --git a/ssh/src/main/java/com/palantir/giraffe/ssh/SshAuthenticator.java b/ssh/src/main/java/com/palantir/giraffe/ssh/SshAuthenticator.java index edee3b04..664ba787 100644 --- a/ssh/src/main/java/com/palantir/giraffe/ssh/SshAuthenticator.java +++ b/ssh/src/main/java/com/palantir/giraffe/ssh/SshAuthenticator.java @@ -33,4 +33,7 @@ public interface SshAuthenticator extends Authenticator { void authByPublicKey(SshCredential credential, byte[] privateKey) throws IOException; + void authByKerberos(SshCredential credential, String realm, String kdcHostname) + throws IOException; + } diff --git a/ssh/src/main/java/com/palantir/giraffe/ssh/SshHostAccessor.java b/ssh/src/main/java/com/palantir/giraffe/ssh/SshHostAccessor.java index 330d5e14..f0dd46e3 100644 --- a/ssh/src/main/java/com/palantir/giraffe/ssh/SshHostAccessor.java +++ b/ssh/src/main/java/com/palantir/giraffe/ssh/SshHostAccessor.java @@ -87,6 +87,12 @@ 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) { + Host host = Host.fromHostname(hostname); + return forCredential(host, KerberosSshCredential.of(username, realm, kdcHostname)); + } + /** * Returns a new {@code SshHostAccessor} that authenticates using the given * {@link SshCredential}. diff --git a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshConnectionFactory.java b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshConnectionFactory.java index 576318b0..38bc1d76 100644 --- a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshConnectionFactory.java +++ b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshConnectionFactory.java @@ -16,7 +16,16 @@ package com.palantir.giraffe.ssh.internal; import java.io.IOException; +import java.nio.charset.Charset; +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.palantir.giraffe.ssh.SshAuthenticator; import com.palantir.giraffe.ssh.SshCredential; import com.palantir.giraffe.ssh.SshSystemRequest; @@ -25,6 +34,7 @@ import net.schmizz.sshj.SSHClient; import net.schmizz.sshj.common.IOUtils; import net.schmizz.sshj.transport.verification.PromiscuousVerifier; +import net.schmizz.sshj.userauth.UserAuthException; import net.schmizz.sshj.userauth.keyprovider.KeyProvider; final class SshConnectionFactory { @@ -69,5 +79,61 @@ public void authByPublicKey(SshCredential credential, byte[] privateKey) KeyProvider keyProvider = client.loadKeys(new String(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); + } + } + + private void restoreProperty(String key, String originalValue) { + if (originalValue != null) { + System.setProperty(key, originalValue); + } else { + System.clearProperty(key); + } + } } }