diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt index a66e38b104..12a9bb3649 100644 --- a/Documentation/config-gerrit.txt +++ b/Documentation/config-gerrit.txt @@ -181,6 +181,17 @@ is per-user, so 1024 items translates to 1024 unique user accounts. As each individual user account may configure multiple SSH keys, the total number of keys may be larger than the item count. +Section sshd +~~~~~~~~~~~~ + +sshd.reuseAddress:: ++ +If true, permits the daemon to bind to the port even if the port +is already in use. If false, the daemon ensures the port is not +in use before starting. Busy sites may need to set this to true +to permit fast restarts. ++ +By default, true. File `replication.config` ------------------------- diff --git a/src/main/java/com/google/gerrit/server/GerritServer.java b/src/main/java/com/google/gerrit/server/GerritServer.java index 9e2efeb0f5..19729fc9ec 100644 --- a/src/main/java/com/google/gerrit/server/GerritServer.java +++ b/src/main/java/com/google/gerrit/server/GerritServer.java @@ -771,6 +771,11 @@ public class GerritServer { return u; } + /** Get the parsed $site_path/gerrit.config file. */ + public RepositoryConfig getGerritConfig() { + return gerritConfigFile; + } + /** Get the repositories maintained by this server. */ public RepositoryCache getRepositoryCache() { return repositories; diff --git a/src/main/java/com/google/gerrit/server/ssh/GerritSshDaemon.java b/src/main/java/com/google/gerrit/server/ssh/GerritSshDaemon.java index b6201b6483..a02e0629f7 100644 --- a/src/main/java/com/google/gerrit/server/ssh/GerritSshDaemon.java +++ b/src/main/java/com/google/gerrit/server/ssh/GerritSshDaemon.java @@ -19,24 +19,50 @@ import com.google.gerrit.server.GerritServer; import com.google.gwtjsonrpc.server.XsrfException; import com.google.gwtorm.client.OrmException; +import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.core.session.IoSession; +import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import org.apache.sshd.SshServer; +import org.apache.sshd.common.Cipher; import org.apache.sshd.common.Compression; +import org.apache.sshd.common.KeyExchange; import org.apache.sshd.common.KeyPairProvider; +import org.apache.sshd.common.Mac; import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.Signature; +import org.apache.sshd.common.cipher.AES128CBC; +import org.apache.sshd.common.cipher.AES192CBC; +import org.apache.sshd.common.cipher.AES256CBC; +import org.apache.sshd.common.cipher.BlowfishCBC; +import org.apache.sshd.common.cipher.TripleDESCBC; import org.apache.sshd.common.compression.CompressionNone; import org.apache.sshd.common.keyprovider.FileKeyPairProvider; +import org.apache.sshd.common.mac.HMACMD5; +import org.apache.sshd.common.mac.HMACMD596; +import org.apache.sshd.common.mac.HMACSHA1; +import org.apache.sshd.common.mac.HMACSHA196; +import org.apache.sshd.common.random.BouncyCastleRandom; +import org.apache.sshd.common.random.JceRandom; +import org.apache.sshd.common.random.SingletonRandomFactory; import org.apache.sshd.common.session.AbstractSession; +import org.apache.sshd.common.signature.SignatureDSA; +import org.apache.sshd.common.signature.SignatureRSA; import org.apache.sshd.common.util.SecurityUtils; +import org.apache.sshd.server.ServerChannel; import org.apache.sshd.server.SessionFactory; import org.apache.sshd.server.UserAuth; import org.apache.sshd.server.auth.UserAuthPublicKey; +import org.apache.sshd.server.channel.ChannelSession; +import org.apache.sshd.server.kex.DHG1; +import org.apache.sshd.server.kex.DHG14; import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.spearce.jgit.lib.RepositoryConfig; import java.io.File; import java.io.IOException; +import java.net.InetSocketAddress; import java.net.SocketException; import java.security.KeyPair; import java.security.PublicKey; @@ -63,80 +89,33 @@ import java.util.List; * Port 8010 * */ -public class GerritSshDaemon { +public class GerritSshDaemon extends SshServer { private static final Logger log = LoggerFactory.getLogger(GerritSshDaemon.class); - private static SshServer sshd; + private static GerritSshDaemon sshd; private static Collection hostKeys = Collections.emptyList(); public static synchronized void startSshd() throws OrmException, XsrfException, SocketException { final GerritServer srv = GerritServer.getInstance(); - final int myPort = Common.getGerritConfig().getSshdPort(); - final SshServer daemon = SshServer.setUpDefaultServer(); - daemon.setPort(myPort); - daemon.setSessionFactory(new SessionFactory() { - @Override - protected AbstractSession createSession(final IoSession ioSession) - throws Exception { - final AbstractSession s = super.createSession(ioSession); - s.setAttribute(SshUtil.REMOTE_PEER, ioSession.getRemoteAddress()); - return s; - } - }); - - final File sitePath = srv.getSitePath(); - if (SecurityUtils.isBouncyCastleRegistered()) { - daemon.setKeyPairProvider(new FileKeyPairProvider(new String[] { - new File(sitePath, "ssh_host_rsa_key").getAbsolutePath(), - new File(sitePath, "ssh_host_dsa_key").getAbsolutePath()})); - } else { - final SimpleGeneratorHostKeyProvider keyp; - - keyp = new SimpleGeneratorHostKeyProvider(); - keyp.setPath(new File(sitePath, "ssh_host_key").getAbsolutePath()); - daemon.setKeyPairProvider(keyp); - } - - setCompression(daemon); - setUserAuth(daemon); - daemon.setPublickeyAuthenticator(new DatabasePubKeyAuth(srv)); - daemon.setCommandFactory(new GerritCommandFactory()); - daemon.setShellFactory(new NoShell()); - + final GerritSshDaemon daemon = new GerritSshDaemon(srv); try { sshd = daemon; daemon.start(); hostKeys = computeHostKeys(); - log.info("Started Gerrit SSHD on 0.0.0.0:" + myPort); + log.info("Started Gerrit SSHD on 0.0.0.0:" + daemon.getPort()); } catch (IOException e) { - log.error("Cannot start Gerrit SSHD on 0.0.0.0:" + myPort, e); + log.error("Cannot start Gerrit SSHD on 0.0.0.0:" + daemon.getPort(), e); sshd = null; hostKeys = Collections.emptyList(); final SocketException e2; - e2 = new SocketException("Cannot start sshd on " + myPort); + e2 = new SocketException("Cannot start sshd on " + daemon.getPort()); e2.initCause(e); throw e2; } } - @SuppressWarnings("unchecked") - private static void setCompression(final SshServer daemon) { - // Always disable transparent compression. The majority of our data - // transfer is highly compressed Git pack files. We cannot make them - // any smaller than they already are. - // - daemon.setCompressionFactories(Arrays - .> asList(new CompressionNone.Factory())); - } - - @SuppressWarnings("unchecked") - private static void setUserAuth(final SshServer daemon) { - daemon.setUserAuthFactories(Arrays - .> asList(new UserAuthPublicKey.Factory())); - } - private static Collection computeHostKeys() { final KeyPairProvider p = sshd.getKeyPairProvider(); final List keys = new ArrayList(2); @@ -172,4 +151,140 @@ public class GerritSshDaemon { public static synchronized Collection getHostKeys() { return hostKeys; } + + private IoAcceptor acceptor; + private boolean reuseAddress; + + private GerritSshDaemon(final GerritServer srv) { + setPort(Common.getGerritConfig().getSshdPort()); + + final RepositoryConfig cfg = srv.getGerritConfig(); + reuseAddress = cfg.getBoolean("sshd", "reuseaddress", true); + + if (SecurityUtils.isBouncyCastleRegistered()) { + initProviderBouncyCastle(); + } else { + initProviderJce(); + } + initCipers(); + initMac(); + initSignatures(); + initChannels(); + initCompression(); + initUserAuth(srv); + initHostKey(srv); + setCommandFactory(new GerritCommandFactory()); + setShellFactory(new NoShell()); + setSessionFactory(new SessionFactory() { + @Override + protected AbstractSession createSession(final IoSession ioSession) + throws Exception { + final AbstractSession s = super.createSession(ioSession); + s.setAttribute(SshUtil.REMOTE_PEER, ioSession.getRemoteAddress()); + return s; + } + }); + } + + @Override + public void start() throws IOException { + if (acceptor == null) { + checkConfig(); + + final NioSocketAcceptor ain = new NioSocketAcceptor(); + final SessionFactory handler = getSessionFactory(); + handler.setServer(this); + ain.setHandler(handler); + ain.setReuseAddress(reuseAddress); + ain.bind(new InetSocketAddress(getPort())); + acceptor = ain; + } + } + + @Override + public void stop() { + if (acceptor != null) { + try { + acceptor.dispose(); + } finally { + acceptor = null; + } + } + } + + @SuppressWarnings("unchecked") + private void initProviderBouncyCastle() { + setKeyExchangeFactories(Arrays.> asList( + new DHG14.Factory(), new DHG1.Factory())); + setRandomFactory(new SingletonRandomFactory( + new BouncyCastleRandom.Factory())); + } + + @SuppressWarnings("unchecked") + private void initProviderJce() { + setKeyExchangeFactories(Arrays + .> asList(new DHG1.Factory())); + setRandomFactory(new SingletonRandomFactory(new JceRandom.Factory())); + } + + @SuppressWarnings("unchecked") + private void initCipers() { + setCipherFactories(Arrays.> asList( + new AES128CBC.Factory(), new TripleDESCBC.Factory(), + new BlowfishCBC.Factory(), new AES192CBC.Factory(), + new AES256CBC.Factory())); + } + + @SuppressWarnings("unchecked") + private void initMac() { + setMacFactories(Arrays.> asList(new HMACMD5.Factory(), + new HMACSHA1.Factory(), new HMACMD596.Factory(), + new HMACSHA196.Factory())); + } + + @SuppressWarnings("unchecked") + private void initSignatures() { + setSignatureFactories(Arrays.> asList( + new SignatureDSA.Factory(), new SignatureRSA.Factory())); + } + + @SuppressWarnings("unchecked") + private void initCompression() { + // Always disable transparent compression. The majority of our data + // transfer is highly compressed Git pack files. We cannot make them + // any smaller than they already are. + // + setCompressionFactories(Arrays + .> asList(new CompressionNone.Factory())); + } + + @SuppressWarnings("unchecked") + private void initChannels() { + setChannelFactories(Arrays + .> asList(new ChannelSession.Factory())); + } + + @SuppressWarnings("unchecked") + private void initUserAuth(final GerritServer srv) { + setUserAuthFactories(Arrays + .> asList(new UserAuthPublicKey.Factory())); + setPublickeyAuthenticator(new DatabasePubKeyAuth(srv)); + } + + private void initHostKey(final GerritServer srv) { + final File sitePath = srv.getSitePath(); + + if (SecurityUtils.isBouncyCastleRegistered()) { + setKeyPairProvider(new FileKeyPairProvider(new String[] { + new File(sitePath, "ssh_host_rsa_key").getAbsolutePath(), + new File(sitePath, "ssh_host_dsa_key").getAbsolutePath()})); + + } else { + final SimpleGeneratorHostKeyProvider keyp; + + keyp = new SimpleGeneratorHostKeyProvider(); + keyp.setPath(new File(sitePath, "ssh_host_key").getAbsolutePath()); + setKeyPairProvider(keyp); + } + } }