Configure signed push globally in Gerrit config

Instead of depending on the standard C git option
receive.certNonceSeed to enable signed push, require a separate option
receive.enableSignedPush. This avoids tying enabling the functionality
to a particular implementation of nonce validation, and in the common
case we can just use a new random seed on startup.

The standard C git keys receive.certNonceSeed and
receive.certNonceSlop are supported, but read them from the global
Gerrit config (preferably secure.config).

Change-Id: Ib38d16052e8896e860e4d9639c62b2023784ef97
This commit is contained in:
Dave Borowitz
2015-06-18 20:28:22 -04:00
parent 90ed9414c2
commit 532342bf0e
3 changed files with 85 additions and 4 deletions

View File

@@ -2771,6 +2771,40 @@ behavior of Gerrit's 'receive-pack' mechanism.
maxObjectSizeLimit = 40 m
----
[[receive.enableSignedPush]]receive.enableSignedPush::
+
If true, server-side signed push validation is enabled.
+
When a client pushes with `git push --signed`, this ensures that the
push certificate is valid and signed with a valid public key stored in
the `refs/gpg-keys` branch of `All-Users`.
+
Defaults to false.
[[receive.certNonceSeed]]receive.certNonceSeed::
+
If set to a non-empty value and server-side signed push validation is
link:#receive.enableSignedPush[enabled], use this value as the seed to
the HMAC SHA-1 nonce generator. If unset, a 64-byte random seed will be
generated at server startup.
+
As this is used as the seed of a cryptographic algorithm, it is
recommended to be placed in link:#secure-config[`secure.config`].
+
Defaults to unset.
[[receive.certNonceSlop]]receive.certNonceSlop::
+
When validating the nonce passed as part of the signed push protocol,
accept valid nonces up to this many seconds old. This allows
certificate verification to work over HTTP where there is a lag between
the HTTP response providing the nonce to sign and the next request
containing the signed nonce. This can be significant on large
repositories, since the lag also includes the time to count objects on
the client.
+
Default is 5 minutes.
[[receive.checkMagicRefs]]receive.checkMagicRefs::
+
If true, Gerrit will verify the destination repository has
@@ -3640,7 +3674,7 @@ notifications if the full name of the user is not set.
By default "Anonymous Coward" is used.
== File `etc/secure.config`
== [[secure.config]]File `etc/secure.config`
The optional file `'$site_path'/etc/secure.config` overrides (or
supplements) the settings supplied by `'$site_path'/etc/gerrit.config`.
The file should be readable only by the daemon process and can be

View File

@@ -14,23 +14,35 @@
package com.google.gerrit.server.git;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.util.BouncyCastleUtil;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.transport.PreReceiveHookChain;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.SignedPushConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;
public class SignedPushModule extends AbstractModule {
private static final Logger log =
LoggerFactory.getLogger(SignedPushModule.class);
public static boolean isEnabled(Config cfg) {
return cfg.getBoolean("receive", null, "enableSignedPush", false);
}
@Override
protected void configure() {
if (BouncyCastleUtil.havePGP()) {
@@ -44,17 +56,50 @@ public class SignedPushModule extends AbstractModule {
@Singleton
private static class Initializer implements ReceivePackInitializer {
private final SignedPushConfig signedPushConfig;
private final SignedPushPreReceiveHook hook;
@Inject
Initializer(SignedPushPreReceiveHook hook) {
Initializer(@GerritServerConfig Config cfg,
SignedPushPreReceiveHook hook) {
this.hook = hook;
if (isEnabled(cfg)) {
String seed = cfg.getString("receive", null, "certNonceSeed");
if (Strings.isNullOrEmpty(seed)) {
seed = randomString(64);
}
signedPushConfig = new SignedPushConfig();
signedPushConfig.setCertNonceSeed(seed);
signedPushConfig.setCertNonceSlopLimit(
cfg.getInt("receive", null, "certNonceSlop", 5 * 60));
} else {
signedPushConfig = null;
}
}
@Override
public void init(Project.NameKey project, ReceivePack rp) {
rp.setSignedPushConfig(signedPushConfig);
if (signedPushConfig != null) {
rp.setPreReceiveHook(PreReceiveHookChain.newChain(Lists.newArrayList(
hook, rp.getPreReceiveHook())));
}
}
}
private static String randomString(int len) {
Random random;
try {
random = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
sb.append((char) random.nextInt());
}
return sb.toString();
}
}

View File

@@ -94,6 +94,8 @@ public class InMemoryModule extends FactoryModule {
cfg.setInt("index", "lucene", "testVersion",
ChangeSchemas.getLatest().getVersion());
cfg.setInt("sendemail", null, "threadPoolSize", 0);
cfg.setBoolean("receive", null, "enableSignedPush", false);
cfg.setString("receive", null, "certNonceSeed", "sekret");
}
private final Config cfg;