Replication Security

Replication now makes use of the standard security system in Gerrit. This is
done by tying to Gerrit groups via replication.config.
This commit is contained in:
Ulrik Sjolin
2009-11-10 22:29:18 +01:00
parent ea25ffe8c9
commit 2464ac82b7
5 changed files with 151 additions and 5 deletions

View File

@@ -45,6 +45,8 @@ different hosts:
push = +refs/heads/*
push = +refs/tags/*
threads = 3
authGroup = Public Mirror Group
authGroup = Second Public Mirror Group
====
To manually trigger replication at runtime, see
@@ -151,6 +153,18 @@ parallel pushing.
+
By default, 1 thread.
[[remote.name.authGroup]]remote.<name>.authGroup:
+
Specifies the name of a group that the remote should use to access
the repositories. Multiple authGroups may be specified within a
single remote block to signify a wider access right. In the project
administration web interface the read access can be specified for
this group to control if a project should be replicated or not to the
remote.
+
By default, replicates without group control, i.e replicates
everything to all remotes.
[[ssh_config]]File `~/.ssh/config`
----------------------------------

View File

@@ -14,8 +14,16 @@
package com.google.gerrit.git;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.ReplicationUser;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
@@ -47,9 +55,11 @@ import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/** Manages automatic replication to remote repositories. */
@@ -72,12 +82,18 @@ public class PushReplication implements ReplicationQueue {
private final Injector injector;
private final WorkQueue workQueue;
private final List<ReplicationConfig> configs;
private final SchemaFactory<ReviewDb> database;
private final ReplicationUser.Factory replicationUserFactory;
@Inject
PushReplication(final Injector i, final WorkQueue wq,
@SitePath final File sitePath) throws ConfigInvalidException, IOException {
@SitePath final File sitePath, final ReplicationUser.Factory ruf,
final SchemaFactory<ReviewDb> db) throws ConfigInvalidException,
IOException {
injector = i;
workQueue = wq;
database = db;
replicationUserFactory = ruf;
configs = allConfigs(sitePath);
}
@@ -152,7 +168,8 @@ public class PushReplication implements ReplicationQueue {
c.addPushRefSpec(spec);
}
r.add(new ReplicationConfig(injector, workQueue, c, cfg));
r.add(new ReplicationConfig(injector, workQueue, c, cfg, database,
replicationUserFactory));
}
return Collections.unmodifiableList(r);
}
@@ -285,9 +302,13 @@ public class PushReplication implements ReplicationQueue {
private final WorkQueue.Executor pool;
private final Map<URIish, PushOp> pending = new HashMap<URIish, PushOp>();
private final PushOp.Factory opFactory;
private final ProjectControl.Factory projectControlFactory;
private final boolean authEnabled;
ReplicationConfig(final Injector injector, final WorkQueue workQueue,
final RemoteConfig rc, final Config cfg) {
final RemoteConfig rc, final Config cfg, SchemaFactory<ReviewDb> db,
final ReplicationUser.Factory replicationUserFactory) {
remote = rc;
delay = Math.max(0, getInt(rc, cfg, "replicationdelay", 15));
@@ -295,6 +316,22 @@ public class PushReplication implements ReplicationQueue {
final String poolName = "ReplicateTo-" + rc.getName();
pool = workQueue.createQueue(poolSize, poolName);
String[] authGroupNames =
cfg.getStringList("remote", rc.getName(), "authGroup");
authEnabled = authGroupNames.length > 0;
Set<AccountGroup.Id> authGroups = groupsFor(db, authGroupNames);
final ReplicationUser remoteUser =
replicationUserFactory.create(authGroups);
projectControlFactory =
injector.createChildInjector(new AbstractModule() {
@Override
protected void configure() {
bind(CurrentUser.class).toInstance(remoteUser);
}
}).getInstance(ProjectControl.Factory.class);
opFactory = injector.createChildInjector(new AbstractModule() {
@Override
protected void configure() {
@@ -307,6 +344,31 @@ public class PushReplication implements ReplicationQueue {
}).getInstance(PushOp.Factory.class);
}
private static Set<AccountGroup.Id> groupsFor(
SchemaFactory<ReviewDb> dbfactory, String[] groupNames) {
final Set<AccountGroup.Id> result = new HashSet<AccountGroup.Id>();
try {
final ReviewDb db = dbfactory.open();
try {
for (String name : groupNames) {
AccountGroup group =
db.accountGroups().get(new AccountGroup.NameKey(name));
if (group == null) {
log.warn("Group \"" + name + "\" not in database,"
+ " removing from authGroup");
} else {
result.add(group.getId());
}
}
} finally {
db.close();
}
} catch (OrmException e) {
log.error("Database error: " + e);
}
return result;
}
private int getInt(final RemoteConfig rc, final Config cfg,
final String name, final int defValue) {
return cfg.getInt("remote", rc.getName(), name, defValue);
@@ -314,6 +376,16 @@ public class PushReplication implements ReplicationQueue {
void schedule(final Project.NameKey project, final String ref,
final URIish uri) {
try {
if (authEnabled
&& !projectControlFactory.controlFor(project).isVisible()) {
return;
}
} catch (NoSuchProjectException e1) {
log.error("Internal error: project " + project
+ " not found during replication");
return;
}
synchronized (pending) {
PushOp e = pending.get(uri);
if (e == null) {

View File

@@ -23,5 +23,8 @@ public enum AccessPath {
WEB,
/** Access through an SSH command, e.g. git fetch or push. */
SSH;
SSH,
/** Access through replication */
REPLICATION;
}

View File

@@ -0,0 +1,56 @@
// Copyright (C) 2009 The Android Open Source Project
//
// 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.server;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.Change;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class ReplicationUser extends CurrentUser {
public interface Factory {
ReplicationUser create(@Assisted Set<AccountGroup.Id> authGroups);
}
private Set<AccountGroup.Id> effectiveGroups;
@Inject
protected ReplicationUser(AuthConfig authConfig,
@Assisted Set<AccountGroup.Id> authGroups) {
super(AccessPath.REPLICATION, authConfig);
effectiveGroups = new HashSet<AccountGroup.Id>(authGroups);
if (effectiveGroups.isEmpty()) {
effectiveGroups.addAll(authConfig.getRegisteredGroups());
}
effectiveGroups = Collections.unmodifiableSet(effectiveGroups);
}
@Override
public Set<AccountGroup.Id> getEffectiveGroups() {
return Collections.unmodifiableSet(effectiveGroups);
}
@Override
public Set<Change.Id> getStarredChanges() {
return Collections.emptySet();
}
}

View File

@@ -35,6 +35,7 @@ import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.MimeUtilFileTypeRegistry;
import com.google.gerrit.server.ReplicationUser;
import com.google.gerrit.server.account.AccountByEmailCacheImpl;
import com.google.gerrit.server.account.AccountCacheImpl;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
@@ -45,7 +46,6 @@ import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.cache.CachePool;
import com.google.gerrit.server.ldap.LdapModule;
import com.google.gerrit.server.mail.AbandonedSender;
import com.google.gerrit.server.mail.AddReviewerSender;
import com.google.gerrit.server.mail.CommentSender;
import com.google.gerrit.server.mail.EmailSender;
import com.google.gerrit.server.mail.FromAddressGenerator;
@@ -160,5 +160,6 @@ public class GerritGlobalModule extends FactoryModule {
factory(MergedSender.Factory.class);
factory(MergeFailSender.Factory.class);
factory(RegisterNewEmailSender.Factory.class);
factory(ReplicationUser.Factory.class);
}
}