Enable Kerberos authentication for SSH interaction

Kerberos authentication is configured by setting the server-side
kerberos keytab, which identifies the kerberos principal used for
SSH connections (typically host/canonical.host.name).

For servers that have been appropriately configured, this
file typically exists at /etc/krb5.keytab.

Kerberos authentication can be enabled by adding a line to the
gerrit.config under the ssh section as follows:

[ssh]
        kerberosKeytab = /etc/krb5.keytab

If the file is readable and contains a keytab, kerberos authentication
is enabled. Accounts must already exist in Gerrit and be active for
authentication to succeed.

If the canonical host is not the name used by the service, the name of
the principal can be defined appropriately:

[ssh]
        kerberosKeytab = /etc/krb5.keytab
        kerberosPrincipal = host/other.host.name

Change-Id: I03744b6391962bdabf647689ec3a2b8d1ab37078
This commit is contained in:
Alex Blewitt
2013-04-01 12:46:48 -04:00
parent e035784cd2
commit 7efb06ff40
4 changed files with 141 additions and 5 deletions

View File

@@ -0,0 +1,68 @@
// Copyright (C) 2013 Goldman Sachs
//
// 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.google.gerrit.sshd;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.IdentifiedUser.GenericFactory;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.sshd.server.auth.gss.GSSAuthenticator;
import org.apache.sshd.server.session.ServerSession;
/**
* Authenticates users with kerberos (gssapi-with-mic).
*/
@Singleton
class GerritGSSAuthenticator extends GSSAuthenticator {
private final AccountCache accounts;
private final SshScope sshScope;
private final SshLog sshLog;
private final GenericFactory userFactory;
@Inject
GerritGSSAuthenticator(final AccountCache accounts, final SshScope sshScope,
final SshLog sshLog, final IdentifiedUser.GenericFactory userFactory) {
this.accounts = accounts;
this.sshScope = sshScope;
this.sshLog = sshLog;
this.userFactory = userFactory;
}
@Override
public boolean validateIdentity(final ServerSession session,
final String identity) {
final SshSession sd = session.getAttribute(SshSession.KEY);
int at = identity.indexOf('@');
String username;
if (at == -1) {
username = identity;
} else {
username = identity.substring(0, at);
}
AccountState state = accounts.getByUsername(username);
Account account = state == null ? null : state.getAccount();
boolean active = account != null && account.isActive();
if (active) {
return SshUtil.success(username, session, sshScope, sshLog, sd,
SshUtil.createUser(sd, userFactory, account.getId()));
} else {
return false;
}
}
}

View File

@@ -19,6 +19,7 @@ import static com.google.gerrit.server.ssh.SshAddressesModule.IANA_SSH_PORT;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.collect.Lists;
import com.google.gerrit.common.Version;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.server.config.ConfigUtil;
@@ -75,6 +76,8 @@ import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.SshFile;
import org.apache.sshd.server.UserAuth;
import org.apache.sshd.server.auth.UserAuthPublicKey;
import org.apache.sshd.server.auth.gss.GSSAuthenticator;
import org.apache.sshd.server.auth.gss.UserAuthGSS;
import org.apache.sshd.server.channel.ChannelDirectTcpip;
import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.kex.DHG1;
@@ -85,9 +88,12 @@ import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.PublicKey;
@@ -129,6 +135,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
@Inject
SshDaemon(final CommandFactory commandFactory, final NoShell noShell,
final PublickeyAuthenticator userAuth,
final GerritGSSAuthenticator kerberosAuth,
final KeyPairProvider hostKeyProvider, final IdGenerator idGenerator,
@GerritServerConfig final Config cfg, final SshLog sshLog,
@SshListenAddresses final List<SocketAddress> listen,
@@ -159,6 +166,11 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
String.valueOf(maxConnectionsPerUser));
}
final String kerberosKeytab = cfg.getString(
"sshd", null, "kerberosKeytab");
final String kerberosPrincipal = cfg.getString(
"sshd", null, "kerberosPrincipal");
if (SecurityUtils.isBouncyCastleRegistered()) {
initProviderBouncyCastle();
} else {
@@ -172,7 +184,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
initFileSystemFactory();
initSubsystems();
initCompression();
initUserAuth(userAuth);
initUserAuth(userAuth, kerberosAuth, kerberosKeytab, kerberosPrincipal);
setKeyPairProvider(hostKeyProvider);
setCommandFactory(commandFactory);
setShellFactory(noShell);
@@ -457,10 +469,36 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
setSubsystemFactories(Collections.<NamedFactory<Command>> emptyList());
}
@SuppressWarnings("unchecked")
private void initUserAuth(final PublickeyAuthenticator pubkey) {
setUserAuthFactories(Arrays
.<NamedFactory<UserAuth>> asList(new UserAuthPublicKey.Factory()));
private void initUserAuth(final PublickeyAuthenticator pubkey,
final GSSAuthenticator kerberosAuthenticator,
String kerberosKeytab, String kerberosPrincipal) {
List<NamedFactory<UserAuth>> authFactories = Lists.newArrayList();
if (kerberosKeytab != null) {
authFactories.add(new UserAuthGSS.Factory());
log.info("Enabling kerberos with keytab " + kerberosKeytab);
if (!new File(kerberosKeytab).canRead()) {
log.error("Keytab " + kerberosKeytab +
" does not exist or is not readable; further errors are possible");
}
kerberosAuthenticator.setKeytabFile(kerberosKeytab);
if (kerberosPrincipal == null) {
try {
kerberosPrincipal = "host/" +
InetAddress.getLocalHost().getCanonicalHostName();
} catch(UnknownHostException e) {
kerberosPrincipal = "host/localhost";
}
}
log.info("Using kerberos principal " + kerberosPrincipal);
if (!kerberosPrincipal.startsWith("host/")) {
log.warn("Host principal does not start with host/ " +
"which most SSH clients will supply automatically");
}
kerberosAuthenticator.setServicePrincipalName(kerberosPrincipal);
setGSSAuthenticator(kerberosAuthenticator);
}
authFactories.add(new UserAuthPublicKey.Factory());
setUserAuthFactories(authFactories);
setPublickeyAuthenticator(pubkey);
}

View File

@@ -42,6 +42,7 @@ import com.google.inject.servlet.RequestScoped;
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.server.CommandFactory;
import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.auth.gss.GSSAuthenticator;
import org.eclipse.jgit.lib.Config;
import java.net.SocketAddress;
@@ -84,6 +85,7 @@ public class SshModule extends FactoryModule {
.toProvider(StreamCommandExecutorProvider.class).in(SINGLETON);
bind(QueueProvider.class).to(CommandExecutorQueueProvider.class).in(SINGLETON);
bind(GSSAuthenticator.class).to(GerritGSSAuthenticator.class);
bind(PublickeyAuthenticator.class).to(DatabasePubKeyAuth.class);
bind(KeyPairProvider.class).toProvider(HostKeyProvider.class).in(SINGLETON);