Add capability.administrateServer to gerrit.config
This provides a fail-safe in case an administrator deletes all administrateServer entries from All-Projects. In a default installation of Gerrit Code Review all administrators are locked out and recovery must happen by directly modifying the refs/meta/config branch in All-Projects "behind Gerrit's back". In some hosted installations this option may not be available to the administrators, as the UNIX account is managed by a different group. By adding a fail-safe group to gerrit.config the administrators are always able to use the REST API and web interface to manage the server, even if all entries were accidentally removed or incorrectly specified in All-Projects. Change-Id: I3083dc2997061ce7be095fb53f187856ab8ed0df
This commit is contained in:
parent
afbc603265
commit
67a3330950
|
@ -892,6 +892,33 @@ threads will die out after the cache is loaded.
|
|||
+
|
||||
Default is the number of CPUs.
|
||||
|
||||
|
||||
[[capability]]
|
||||
=== Section capability
|
||||
|
||||
[[capability.administrateServer]]capability.administrateServer::
|
||||
+
|
||||
Names of groups of users that are allowed to exercise the
|
||||
administrateServer capability, in addition to those listed in
|
||||
All-Projects. Configuring this option can be a useful fail-safe
|
||||
to recover a server in the event an administrator removed all
|
||||
groups from the administrateServer capability, or to ensure that
|
||||
specific groups always have administration capabilities.
|
||||
+
|
||||
----
|
||||
[capability]
|
||||
administrateServer = group Fail Safe Admins
|
||||
----
|
||||
+
|
||||
The configuration file uses group names, not UUIDs. If a group is
|
||||
renamed the gerrit.config file must be updated to reflect the new
|
||||
name. If a group cannot be found for the configured name a warning
|
||||
is logged and the server will continue normal startup.
|
||||
+
|
||||
If not specified (default), only the groups listed by All-Projects
|
||||
may use the administrateServer capability.
|
||||
|
||||
|
||||
[[change]]
|
||||
=== Section change
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@ package com.google.gerrit.pgm.util;
|
|||
import static com.google.inject.Scopes.SINGLETON;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gerrit.common.data.GroupReference;
|
||||
import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
|
||||
import com.google.gerrit.extensions.config.FactoryModule;
|
||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||
|
@ -29,6 +31,7 @@ import com.google.gerrit.server.account.AccountByEmailCacheImpl;
|
|||
import com.google.gerrit.server.account.AccountCacheImpl;
|
||||
import com.google.gerrit.server.account.AccountVisibility;
|
||||
import com.google.gerrit.server.account.AccountVisibilityProvider;
|
||||
import com.google.gerrit.server.account.CapabilityCollection;
|
||||
import com.google.gerrit.server.account.CapabilityControl;
|
||||
import com.google.gerrit.server.account.FakeRealm;
|
||||
import com.google.gerrit.server.account.GroupCacheImpl;
|
||||
|
@ -41,6 +44,7 @@ import com.google.gerrit.server.change.ChangeKindCacheImpl;
|
|||
import com.google.gerrit.server.change.MergeabilityCacheImpl;
|
||||
import com.google.gerrit.server.change.PatchSetInserter;
|
||||
import com.google.gerrit.server.change.RebaseChangeOp;
|
||||
import com.google.gerrit.server.config.AdministrateServerGroups;
|
||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||
import com.google.gerrit.server.config.CanonicalWebUrlProvider;
|
||||
import com.google.gerrit.server.config.DisableReverseDnsLookup;
|
||||
|
@ -130,6 +134,9 @@ public class BatchProgramModule extends FactoryModule {
|
|||
bind(SearchingChangeCacheImpl.class).toProvider(
|
||||
Providers.<SearchingChangeCacheImpl>of(null));
|
||||
|
||||
bind(new TypeLiteral<ImmutableSet<GroupReference>>() {})
|
||||
.annotatedWith(AdministrateServerGroups.class)
|
||||
.toInstance(ImmutableSet.<GroupReference> of());
|
||||
bind(new TypeLiteral<Set<AccountGroup.UUID>>() {})
|
||||
.annotatedWith(GitUploadPackGroups.class)
|
||||
.toInstance(Collections.<AccountGroup.UUID> emptySet());
|
||||
|
@ -153,6 +160,7 @@ public class BatchProgramModule extends FactoryModule {
|
|||
install(ChangeKindCacheImpl.module());
|
||||
install(MergeabilityCacheImpl.module());
|
||||
install(TagCache.module());
|
||||
factory(CapabilityCollection.Factory.class);
|
||||
factory(CapabilityControl.Factory.class);
|
||||
factory(ChangeData.Factory.class);
|
||||
factory(ProjectState.Factory.class);
|
||||
|
|
|
@ -14,32 +14,46 @@
|
|||
|
||||
package com.google.gerrit.server.account;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.common.data.AccessSection;
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.common.data.GroupReference;
|
||||
import com.google.gerrit.common.data.Permission;
|
||||
import com.google.gerrit.common.data.PermissionRange;
|
||||
import com.google.gerrit.common.data.PermissionRule;
|
||||
import com.google.gerrit.server.config.AdministrateServerGroups;
|
||||
import com.google.gerrit.server.group.SystemGroupBackend;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/** Caches active {@link GlobalCapability} set for a site. */
|
||||
public class CapabilityCollection {
|
||||
private final Map<String, List<PermissionRule>> permissions;
|
||||
public interface Factory {
|
||||
CapabilityCollection create(@Nullable AccessSection section);
|
||||
}
|
||||
|
||||
public final List<PermissionRule> administrateServer;
|
||||
public final List<PermissionRule> batchChangesLimit;
|
||||
public final List<PermissionRule> emailReviewers;
|
||||
public final List<PermissionRule> priority;
|
||||
public final List<PermissionRule> queryLimit;
|
||||
private final ImmutableMap<String, ImmutableList<PermissionRule>> permissions;
|
||||
|
||||
public CapabilityCollection(AccessSection section) {
|
||||
public final ImmutableList<PermissionRule> administrateServer;
|
||||
public final ImmutableList<PermissionRule> batchChangesLimit;
|
||||
public final ImmutableList<PermissionRule> emailReviewers;
|
||||
public final ImmutableList<PermissionRule> priority;
|
||||
public final ImmutableList<PermissionRule> queryLimit;
|
||||
|
||||
@Inject
|
||||
CapabilityCollection(
|
||||
@AdministrateServerGroups ImmutableSet<GroupReference> admins,
|
||||
@Assisted @Nullable AccessSection section) {
|
||||
if (section == null) {
|
||||
section = new AccessSection(AccessSection.GLOBAL_CAPABILITIES);
|
||||
}
|
||||
|
@ -61,18 +75,19 @@ public class CapabilityCollection {
|
|||
}
|
||||
}
|
||||
configureDefaults(tmp, section);
|
||||
if (!tmp.containsKey(GlobalCapability.ADMINISTRATE_SERVER) && !admins.isEmpty()) {
|
||||
tmp.put(GlobalCapability.ADMINISTRATE_SERVER, ImmutableList.<PermissionRule>of());
|
||||
}
|
||||
|
||||
Map<String, List<PermissionRule>> res = new HashMap<>();
|
||||
ImmutableMap.Builder<String, ImmutableList<PermissionRule>> m = ImmutableMap.builder();
|
||||
for (Map.Entry<String, List<PermissionRule>> e : tmp.entrySet()) {
|
||||
List<PermissionRule> rules = e.getValue();
|
||||
if (rules.size() == 1) {
|
||||
res.put(e.getKey(), Collections.singletonList(rules.get(0)));
|
||||
} else {
|
||||
res.put(e.getKey(), Collections.unmodifiableList(
|
||||
Arrays.asList(rules.toArray(new PermissionRule[rules.size()]))));
|
||||
if (GlobalCapability.ADMINISTRATE_SERVER.equals(e.getKey())) {
|
||||
rules = mergeAdmin(admins, rules);
|
||||
}
|
||||
m.put(e.getKey(), ImmutableList.copyOf(rules));
|
||||
}
|
||||
permissions = Collections.unmodifiableMap(res);
|
||||
permissions = m.build();
|
||||
|
||||
administrateServer = getPermission(GlobalCapability.ADMINISTRATE_SERVER);
|
||||
batchChangesLimit = getPermission(GlobalCapability.BATCH_CHANGES_LIMIT);
|
||||
|
@ -81,9 +96,27 @@ public class CapabilityCollection {
|
|||
queryLimit = getPermission(GlobalCapability.QUERY_LIMIT);
|
||||
}
|
||||
|
||||
public List<PermissionRule> getPermission(String permissionName) {
|
||||
List<PermissionRule> r = permissions.get(permissionName);
|
||||
return r != null ? r : Collections.<PermissionRule> emptyList();
|
||||
private static List<PermissionRule> mergeAdmin(Set<GroupReference> admins,
|
||||
List<PermissionRule> rules) {
|
||||
if (admins.isEmpty()) {
|
||||
return rules;
|
||||
}
|
||||
|
||||
List<PermissionRule> r = new ArrayList<>(admins.size() + rules.size());
|
||||
for (GroupReference g : admins) {
|
||||
r.add(new PermissionRule(g));
|
||||
}
|
||||
for (PermissionRule rule : rules) {
|
||||
if (!admins.contains(rule.getGroup())) {
|
||||
r.add(rule);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public ImmutableList<PermissionRule> getPermission(String permissionName) {
|
||||
ImmutableList<PermissionRule> r = permissions.get(permissionName);
|
||||
return r != null ? r : ImmutableList.<PermissionRule> of();
|
||||
}
|
||||
|
||||
private static final GroupReference anonymous = SystemGroupBackend
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (C) 2010 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.config;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import com.google.inject.BindingAnnotation;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
/**
|
||||
* Groups that can always exercise {@code administrateServer} capability.
|
||||
*
|
||||
* <pre>
|
||||
* [capability]
|
||||
* administrateServer = group Administrators
|
||||
* </pre>
|
||||
*/
|
||||
@Retention(RUNTIME)
|
||||
@BindingAnnotation
|
||||
public @interface AdministrateServerGroups {
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright (C) 2010 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.config;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gerrit.common.data.GroupReference;
|
||||
import com.google.gerrit.common.data.PermissionRule;
|
||||
import com.google.gerrit.server.account.GroupBackend;
|
||||
import com.google.gerrit.server.account.GroupBackends;
|
||||
import com.google.gerrit.server.util.RequestContext;
|
||||
import com.google.gerrit.server.util.ServerRequestContext;
|
||||
import com.google.gerrit.server.util.ThreadLocalRequestContext;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Loads {@link AdministrateServerGroups} from {@code gerrit.config}. */
|
||||
public class AdministrateServerGroupsProvider implements Provider<ImmutableSet<GroupReference>> {
|
||||
private final ImmutableSet<GroupReference> groups;
|
||||
|
||||
@Inject
|
||||
public AdministrateServerGroupsProvider(GroupBackend groupBackend,
|
||||
@GerritServerConfig Config config,
|
||||
ThreadLocalRequestContext threadContext,
|
||||
ServerRequestContext serverCtx) {
|
||||
RequestContext ctx = threadContext.setContext(serverCtx);
|
||||
try {
|
||||
ImmutableSet.Builder<GroupReference> builder = ImmutableSet.builder();
|
||||
for (String value : config.getStringList("capability", null, "administrateServer")) {
|
||||
PermissionRule rule = PermissionRule.fromString(value, false);
|
||||
String name = rule.getGroup().getName();
|
||||
GroupReference g = GroupBackends.findBestSuggestion(groupBackend, name);
|
||||
if (g != null) {
|
||||
builder.add(g);
|
||||
} else {
|
||||
Logger log = LoggerFactory.getLogger(getClass());
|
||||
log.warn("Group \"{}\" not available, skipping.", name);
|
||||
}
|
||||
}
|
||||
groups = builder.build();
|
||||
} finally {
|
||||
threadContext.setContext(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableSet<GroupReference> get() {
|
||||
return groups;
|
||||
}
|
||||
}
|
|
@ -79,6 +79,7 @@ import com.google.gerrit.server.account.AccountManager;
|
|||
import com.google.gerrit.server.account.AccountResolver;
|
||||
import com.google.gerrit.server.account.AccountVisibility;
|
||||
import com.google.gerrit.server.account.AccountVisibilityProvider;
|
||||
import com.google.gerrit.server.account.CapabilityCollection;
|
||||
import com.google.gerrit.server.account.CapabilityControl;
|
||||
import com.google.gerrit.server.account.ChangeUserName;
|
||||
import com.google.gerrit.server.account.EmailExpander;
|
||||
|
@ -233,6 +234,7 @@ public class GerritGlobalModule extends FactoryModule {
|
|||
factory(DeleteReviewerSender.Factory.class);
|
||||
factory(AddKeySender.Factory.class);
|
||||
factory(BatchUpdate.Factory.class);
|
||||
factory(CapabilityCollection.Factory.class);
|
||||
factory(CapabilityControl.Factory.class);
|
||||
factory(ChangeData.Factory.class);
|
||||
factory(ChangeJson.Factory.class);
|
||||
|
|
|
@ -30,10 +30,9 @@ import org.slf4j.LoggerFactory;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** Parses groups referenced in the {@code gerrit.config} file. */
|
||||
public abstract class GroupSetProvider implements
|
||||
Provider<Set<AccountGroup.UUID>> {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(GroupSetProvider.class);
|
||||
|
||||
protected Set<AccountGroup.UUID> groupIds;
|
||||
|
||||
|
@ -45,10 +44,11 @@ public abstract class GroupSetProvider implements
|
|||
ImmutableSet.Builder<AccountGroup.UUID> builder = ImmutableSet.builder();
|
||||
for (String n : groupNames) {
|
||||
GroupReference g = GroupBackends.findBestSuggestion(groupBackend, n);
|
||||
if (g == null) {
|
||||
log.warn("Group \"{}\" not in database, skipping.", n);
|
||||
} else {
|
||||
if (g != null) {
|
||||
builder.add(g.getUUID());
|
||||
} else {
|
||||
Logger log = LoggerFactory.getLogger(getClass());
|
||||
log.warn("Group \"{}\" not available, skipping.", n);
|
||||
}
|
||||
}
|
||||
groupIds = builder.build();
|
||||
|
|
|
@ -16,8 +16,12 @@ package com.google.gerrit.server.project;
|
|||
|
||||
import static com.google.inject.Scopes.SINGLETON;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gerrit.common.data.GroupReference;
|
||||
import com.google.gerrit.extensions.config.FactoryModule;
|
||||
import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||
import com.google.gerrit.server.config.AdministrateServerGroups;
|
||||
import com.google.gerrit.server.config.AdministrateServerGroupsProvider;
|
||||
import com.google.gerrit.server.config.GitReceivePackGroups;
|
||||
import com.google.gerrit.server.config.GitReceivePackGroupsProvider;
|
||||
import com.google.gerrit.server.config.GitUploadPackGroups;
|
||||
|
@ -29,13 +33,20 @@ import java.util.Set;
|
|||
public class AccessControlModule extends FactoryModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(new TypeLiteral<Set<AccountGroup.UUID>>() {}) //
|
||||
.annotatedWith(GitUploadPackGroups.class) //
|
||||
.toProvider(GitUploadPackGroupsProvider.class).in(SINGLETON);
|
||||
bind(new TypeLiteral<ImmutableSet<GroupReference>>() {})
|
||||
.annotatedWith(AdministrateServerGroups.class)
|
||||
.toProvider(AdministrateServerGroupsProvider.class)
|
||||
.in(SINGLETON);
|
||||
|
||||
bind(new TypeLiteral<Set<AccountGroup.UUID>>() {}) //
|
||||
.annotatedWith(GitReceivePackGroups.class) //
|
||||
.toProvider(GitReceivePackGroupsProvider.class).in(SINGLETON);
|
||||
bind(new TypeLiteral<Set<AccountGroup.UUID>>() {})
|
||||
.annotatedWith(GitUploadPackGroups.class)
|
||||
.toProvider(GitUploadPackGroupsProvider.class)
|
||||
.in(SINGLETON);
|
||||
|
||||
bind(new TypeLiteral<Set<AccountGroup.UUID>>() {})
|
||||
.annotatedWith(GitReceivePackGroups.class)
|
||||
.toProvider(GitReceivePackGroupsProvider.class)
|
||||
.in(SINGLETON);
|
||||
|
||||
bind(ChangeControl.Factory.class);
|
||||
factory(ProjectControl.AssistedFactory.class);
|
||||
|
|
|
@ -123,6 +123,7 @@ public class ProjectState {
|
|||
final GitRepositoryManager gitMgr,
|
||||
final RulesCache rulesCache,
|
||||
final List<CommentLinkInfo> commentLinks,
|
||||
final CapabilityCollection.Factory capabilityFactory,
|
||||
@Assisted final ProjectConfig config) {
|
||||
this.sitePaths = sitePaths;
|
||||
this.projectCache = projectCache;
|
||||
|
@ -137,7 +138,7 @@ public class ProjectState {
|
|||
this.config = config;
|
||||
this.configs = new HashMap<>();
|
||||
this.capabilities = isAllProjects
|
||||
? new CapabilityCollection(config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES))
|
||||
? capabilityFactory.create(config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES))
|
||||
: null;
|
||||
|
||||
if (isAllProjects && !Permission.canBeOnAllProjects(AccessSection.ALL, Permission.OWNER)) {
|
||||
|
|
|
@ -47,6 +47,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
|
|||
import com.google.gerrit.rules.PrologEnvironment;
|
||||
import com.google.gerrit.rules.RulesCache;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.account.CapabilityCollection;
|
||||
import com.google.gerrit.server.account.CapabilityControl;
|
||||
import com.google.gerrit.server.account.GroupMembership;
|
||||
import com.google.gerrit.server.account.ListGroupMembership;
|
||||
|
@ -235,6 +236,7 @@ public class RefControlTest {
|
|||
private ChangeControl.Factory changeControlFactory;
|
||||
private ReviewDb db;
|
||||
|
||||
@Inject private CapabilityCollection.Factory capabilityCollectionFactory;
|
||||
@Inject private CapabilityControl.Factory capabilityControlFactory;
|
||||
@Inject private SchemaCreator schemaCreator;
|
||||
@Inject private InMemoryDatabase schemaFactory;
|
||||
|
@ -243,18 +245,6 @@ public class RefControlTest {
|
|||
@Before
|
||||
public void setUp() throws Exception {
|
||||
repoManager = new InMemoryRepositoryManager();
|
||||
try {
|
||||
Repository repo = repoManager.createRepository(allProjectsName);
|
||||
ProjectConfig allProjects =
|
||||
new ProjectConfig(new Project.NameKey(allProjectsName.get()));
|
||||
allProjects.load(repo);
|
||||
LabelType cr = Util.codeReview();
|
||||
allProjects.getLabelSections().put(cr.getName(), cr);
|
||||
add(allProjects);
|
||||
} catch (IOException | ConfigInvalidException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
projectCache = new ProjectCache() {
|
||||
@Override
|
||||
public ProjectState getAllProjects() {
|
||||
|
@ -312,6 +302,18 @@ public class RefControlTest {
|
|||
Injector injector = Guice.createInjector(new InMemoryModule());
|
||||
injector.injectMembers(this);
|
||||
|
||||
try {
|
||||
Repository repo = repoManager.createRepository(allProjectsName);
|
||||
ProjectConfig allProjects =
|
||||
new ProjectConfig(new Project.NameKey(allProjectsName.get()));
|
||||
allProjects.load(repo);
|
||||
LabelType cr = Util.codeReview();
|
||||
allProjects.getLabelSections().put(cr.getName(), cr);
|
||||
add(allProjects);
|
||||
} catch (IOException | ConfigInvalidException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
db = schemaFactory.open();
|
||||
schemaCreator.create(db);
|
||||
|
||||
|
@ -865,7 +867,7 @@ public class RefControlTest {
|
|||
all.put(pc.getName(),
|
||||
new ProjectState(sitePaths, projectCache, allProjectsName, allUsersName,
|
||||
projectControlFactory, envFactory, repoManager, rulesCache,
|
||||
commentLinks, pc));
|
||||
commentLinks, capabilityCollectionFactory, pc));
|
||||
return repo;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue