Allow $site_path/etc/peer_keys to authenticate peer daemons
The peer_keys file is the standard OpenSSH authorized_keys file format, one SSH key per line. Blank lines and any lines starting with # are ignored. The file is scanned each time it is modified, allowing hosts to be added or removed from a cluster configuration without needing to restart the current node. I'm choosing to put the peer keys into a local disk file rather than into the database, because we might run into a catch-22 case where the peers need to authenticate to each other before they can read the database. E.g. this could happen if we figure out how to embed Apache Cassandra, tunnel its swarm traffic over our own SSH channels, and require a quorum read to bring the server up. The use of this file is experimental. I'm not documenting it yet because I don't know if we'll be supporting it long-term. Change-Id: I6e9b8ae5cd1bb3643688a3ee657055aab73e6a87 Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
@@ -19,21 +19,33 @@ import com.google.gerrit.server.AccessPath;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.PeerDaemonUser;
|
||||
import com.google.gerrit.server.config.SitePaths;
|
||||
import com.google.gerrit.sshd.SshScope.Context;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.mina.core.future.IoFuture;
|
||||
import org.apache.mina.core.future.IoFutureListener;
|
||||
import org.apache.sshd.common.KeyPairProvider;
|
||||
import org.apache.sshd.common.SshException;
|
||||
import org.apache.sshd.common.util.Buffer;
|
||||
import org.apache.sshd.server.PublickeyAuthenticator;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -42,21 +54,26 @@ import java.util.Set;
|
||||
*/
|
||||
@Singleton
|
||||
class DatabasePubKeyAuth implements PublickeyAuthenticator {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(DatabasePubKeyAuth.class);
|
||||
|
||||
private final SshKeyCacheImpl sshKeyCache;
|
||||
private final SshLog log;
|
||||
private final SshLog sshLog;
|
||||
private final IdentifiedUser.GenericFactory userFactory;
|
||||
private final PeerDaemonUser.Factory peerFactory;
|
||||
private final Set<PublicKey> myHostKeys;
|
||||
private volatile PeerKeyCache peerKeyCache;
|
||||
|
||||
@Inject
|
||||
DatabasePubKeyAuth(final SshKeyCacheImpl skc, final SshLog l,
|
||||
final IdentifiedUser.GenericFactory uf, final PeerDaemonUser.Factory pf,
|
||||
final KeyPairProvider hostKeyProvider) {
|
||||
final SitePaths site, final KeyPairProvider hostKeyProvider) {
|
||||
sshKeyCache = skc;
|
||||
log = l;
|
||||
sshLog = l;
|
||||
userFactory = uf;
|
||||
peerFactory = pf;
|
||||
myHostKeys = myHostKeys(hostKeyProvider);
|
||||
peerKeyCache = new PeerKeyCache(site.peer_keys);
|
||||
}
|
||||
|
||||
private static Set<PublicKey> myHostKeys(KeyPairProvider p) {
|
||||
@@ -79,7 +96,8 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator {
|
||||
final SshSession sd = session.getAttribute(SshSession.KEY);
|
||||
|
||||
if (PeerDaemonUser.USER_NAME.equals(username)) {
|
||||
if (myHostKeys.contains(suppliedKey)) {
|
||||
if (myHostKeys.contains(suppliedKey)
|
||||
|| getPeerKeys().contains(suppliedKey)) {
|
||||
PeerDaemonUser user = peerFactory.create(sd.getRemoteAddress());
|
||||
return success(username, session, sd, user);
|
||||
|
||||
@@ -120,6 +138,15 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator {
|
||||
return success(username, session, sd, createUser(sd, key));
|
||||
}
|
||||
|
||||
private Set<PublicKey> getPeerKeys() {
|
||||
PeerKeyCache p = peerKeyCache;
|
||||
if (!p.isCurrent()) {
|
||||
p = p.reload();
|
||||
peerKeyCache = p;
|
||||
}
|
||||
return p.keys;
|
||||
}
|
||||
|
||||
private boolean success(final String username, final ServerSession session,
|
||||
final SshSession sd, final CurrentUser user) {
|
||||
if (sd.getCurrentUser() == null) {
|
||||
@@ -132,7 +159,7 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator {
|
||||
Context ctx = new Context(sd);
|
||||
Context old = SshScope.set(ctx);
|
||||
try {
|
||||
log.onLogin();
|
||||
sshLog.onLogin();
|
||||
} finally {
|
||||
SshScope.set(old);
|
||||
}
|
||||
@@ -144,7 +171,7 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator {
|
||||
final Context ctx = new Context(sd);
|
||||
final Context old = SshScope.set(ctx);
|
||||
try {
|
||||
log.onLogout();
|
||||
sshLog.onLogout();
|
||||
} finally {
|
||||
SshScope.set(old);
|
||||
}
|
||||
@@ -174,4 +201,62 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class PeerKeyCache {
|
||||
private final File path;
|
||||
private final long modified;
|
||||
final Set<PublicKey> keys;
|
||||
|
||||
PeerKeyCache(final File path) {
|
||||
this.path = path;
|
||||
this.modified = path.lastModified();
|
||||
this.keys = read(path);
|
||||
}
|
||||
|
||||
private static Set<PublicKey> read(File path) {
|
||||
try {
|
||||
final BufferedReader br = new BufferedReader(new FileReader(path));
|
||||
try {
|
||||
final Set<PublicKey> keys = new HashSet<PublicKey>();
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (line.startsWith("#") || line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] bin = Base64.decodeBase64(line.getBytes("ISO-8859-1"));
|
||||
keys.add(new Buffer(bin).getRawPublicKey());
|
||||
} catch (RuntimeException e) {
|
||||
logBadKey(path, line, e);
|
||||
} catch (SshException e) {
|
||||
logBadKey(path, line, e);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableSet(keys);
|
||||
} finally {
|
||||
br.close();
|
||||
}
|
||||
} catch (FileNotFoundException noFile) {
|
||||
return Collections.emptySet();
|
||||
|
||||
} catch (IOException err) {
|
||||
log.error("Cannot read " + path, err);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
private static void logBadKey(File path, String line, Exception e) {
|
||||
log.warn("Invalid key in " + path + ":\n " + line, e);
|
||||
}
|
||||
|
||||
boolean isCurrent() {
|
||||
return path.lastModified() == modified;
|
||||
}
|
||||
|
||||
PeerKeyCache reload() {
|
||||
return new PeerKeyCache(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user