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:
@@ -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