Merge "Enable Kerberos authentication for SSH interaction"
This commit is contained in:
@@ -2403,6 +2403,34 @@ Supported MACs: hmac-md5, hmac-md5-96, hmac-sha1, hmac-sha1-96.
|
|||||||
+
|
+
|
||||||
By default, all supported MACs are available.
|
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
|
[[suggest]] Section suggest
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.MILLISECONDS;
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import com.google.gerrit.common.Version;
|
import com.google.gerrit.common.Version;
|
||||||
import com.google.gerrit.extensions.events.LifecycleListener;
|
import com.google.gerrit.extensions.events.LifecycleListener;
|
||||||
import com.google.gerrit.server.config.ConfigUtil;
|
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.SshFile;
|
||||||
import org.apache.sshd.server.UserAuth;
|
import org.apache.sshd.server.UserAuth;
|
||||||
import org.apache.sshd.server.auth.UserAuthPublicKey;
|
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.ChannelDirectTcpip;
|
||||||
import org.apache.sshd.server.channel.ChannelSession;
|
import org.apache.sshd.server.channel.ChannelSession;
|
||||||
import org.apache.sshd.server.kex.DHG1;
|
import org.apache.sshd.server.kex.DHG1;
|
||||||
@@ -85,9 +88,12 @@ import org.eclipse.jgit.lib.Config;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
@@ -129,6 +135,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
|
|||||||
@Inject
|
@Inject
|
||||||
SshDaemon(final CommandFactory commandFactory, final NoShell noShell,
|
SshDaemon(final CommandFactory commandFactory, final NoShell noShell,
|
||||||
final PublickeyAuthenticator userAuth,
|
final PublickeyAuthenticator userAuth,
|
||||||
|
final GerritGSSAuthenticator kerberosAuth,
|
||||||
final KeyPairProvider hostKeyProvider, final IdGenerator idGenerator,
|
final KeyPairProvider hostKeyProvider, final IdGenerator idGenerator,
|
||||||
@GerritServerConfig final Config cfg, final SshLog sshLog,
|
@GerritServerConfig final Config cfg, final SshLog sshLog,
|
||||||
@SshListenAddresses final List<SocketAddress> listen,
|
@SshListenAddresses final List<SocketAddress> listen,
|
||||||
@@ -159,6 +166,11 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
|
|||||||
String.valueOf(maxConnectionsPerUser));
|
String.valueOf(maxConnectionsPerUser));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final String kerberosKeytab = cfg.getString(
|
||||||
|
"sshd", null, "kerberosKeytab");
|
||||||
|
final String kerberosPrincipal = cfg.getString(
|
||||||
|
"sshd", null, "kerberosPrincipal");
|
||||||
|
|
||||||
if (SecurityUtils.isBouncyCastleRegistered()) {
|
if (SecurityUtils.isBouncyCastleRegistered()) {
|
||||||
initProviderBouncyCastle();
|
initProviderBouncyCastle();
|
||||||
} else {
|
} else {
|
||||||
@@ -172,7 +184,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
|
|||||||
initFileSystemFactory();
|
initFileSystemFactory();
|
||||||
initSubsystems();
|
initSubsystems();
|
||||||
initCompression();
|
initCompression();
|
||||||
initUserAuth(userAuth);
|
initUserAuth(userAuth, kerberosAuth, kerberosKeytab, kerberosPrincipal);
|
||||||
setKeyPairProvider(hostKeyProvider);
|
setKeyPairProvider(hostKeyProvider);
|
||||||
setCommandFactory(commandFactory);
|
setCommandFactory(commandFactory);
|
||||||
setShellFactory(noShell);
|
setShellFactory(noShell);
|
||||||
@@ -457,10 +469,36 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
|
|||||||
setSubsystemFactories(Collections.<NamedFactory<Command>> emptyList());
|
setSubsystemFactories(Collections.<NamedFactory<Command>> emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
private void initUserAuth(final PublickeyAuthenticator pubkey,
|
||||||
private void initUserAuth(final PublickeyAuthenticator pubkey) {
|
final GSSAuthenticator kerberosAuthenticator,
|
||||||
setUserAuthFactories(Arrays
|
String kerberosKeytab, String kerberosPrincipal) {
|
||||||
.<NamedFactory<UserAuth>> asList(new UserAuthPublicKey.Factory()));
|
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);
|
setPublickeyAuthenticator(pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import com.google.inject.servlet.RequestScoped;
|
|||||||
import org.apache.sshd.common.KeyPairProvider;
|
import org.apache.sshd.common.KeyPairProvider;
|
||||||
import org.apache.sshd.server.CommandFactory;
|
import org.apache.sshd.server.CommandFactory;
|
||||||
import org.apache.sshd.server.PublickeyAuthenticator;
|
import org.apache.sshd.server.PublickeyAuthenticator;
|
||||||
|
import org.apache.sshd.server.auth.gss.GSSAuthenticator;
|
||||||
import org.eclipse.jgit.lib.Config;
|
import org.eclipse.jgit.lib.Config;
|
||||||
|
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
@@ -84,6 +85,7 @@ public class SshModule extends FactoryModule {
|
|||||||
.toProvider(StreamCommandExecutorProvider.class).in(SINGLETON);
|
.toProvider(StreamCommandExecutorProvider.class).in(SINGLETON);
|
||||||
bind(QueueProvider.class).to(CommandExecutorQueueProvider.class).in(SINGLETON);
|
bind(QueueProvider.class).to(CommandExecutorQueueProvider.class).in(SINGLETON);
|
||||||
|
|
||||||
|
bind(GSSAuthenticator.class).to(GerritGSSAuthenticator.class);
|
||||||
bind(PublickeyAuthenticator.class).to(DatabasePubKeyAuth.class);
|
bind(PublickeyAuthenticator.class).to(DatabasePubKeyAuth.class);
|
||||||
bind(KeyPairProvider.class).toProvider(HostKeyProvider.class).in(SINGLETON);
|
bind(KeyPairProvider.class).toProvider(HostKeyProvider.class).in(SINGLETON);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user