Merge "Enable Kerberos authentication for SSH interaction"

This commit is contained in:
Shawn Pearce
2013-04-04 00:23:12 +00:00
committed by Gerrit Code Review
4 changed files with 141 additions and 5 deletions

View File

@@ -2403,6 +2403,34 @@ Supported MACs: hmac-md5, hmac-md5-96, hmac-sha1, hmac-sha1-96.
+
By default, all supported MACs are available.
[[sshd.kerberosKeytab]]sshd.kerberosKeytab::
+
Enable kerberos authentication for SSH connections. To permit
kerberos authentication, the server must have a host principal
(see `sshd.kerberosPrincipal`) which is acquired from a keytab.
This must be provisioned by the kerberos administrators, and is
typically installed into `/etc/krb5.keytab` on host machines.
+
The keytab must contain at least one `host/` principal, typically
using the host's canonical name. If it does not use the
canonical name, the `sshd.kerberosPrincipal` should be configured
with the correct name.
+
By default, not set and so kerberos authentication is not enabled.
[[sshd.kerberosPrincipal]]sshd.kerberosPrincipal::
+
If kerberos authentication is enabled with `sshd.kerberosKeytab`,
instead use the given principal name instead of the default.
If the principal does not begin with `host/` a warning message is
printed and may prevent successful authentication.
+
This may be useful if the host is behind an IP load balancer or
other SSH forwarding systems, since the principal name is constructed
by the client and must match for kerberos authentication to work.
+
By default, `host/canonical.host.name`
[[suggest]] Section suggest
~~~~~~~~~~~~~~~~~~~~~~~~~~~

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);