Allow admins to disable remote plugin installation

Some systems might prefer to block remote plugin installation,
requiring the user to instead have access to the server's own
$site_path/plugins directory before code can be injected into
the installation.

Change-Id: I16bd9de83e022a15767fd29ce9e2fb55674f103b
This commit is contained in:
Shawn Pearce 2014-02-14 16:42:35 -08:00 committed by Edwin Kempin
parent a145270984
commit fd03350b9e
9 changed files with 70 additions and 8 deletions

View File

@ -2303,6 +2303,12 @@ may force reloading with link:cmd-plugin.html[gerrit plugin reload].
+ +
Default is 1 minute. Default is 1 minute.
[[plugins.allowRemoteAdmin]]plugins.allowRemoteAdmin::
+
Enable remote installation, enable and disable of plugins over HTTP
and SSH. If set to true Administrators can install new plugins
remotely, or disable existing plugins. Defaults to false.
[[receive]] [[receive]]
=== Section receive === Section receive

View File

@ -17,6 +17,7 @@ package com.google.gerrit.server.plugins;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability; import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.RestModifyView; import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.plugins.DisablePlugin.Input; import com.google.gerrit.server.plugins.DisablePlugin.Input;
import com.google.gerrit.server.plugins.ListPlugins.PluginInfo; import com.google.gerrit.server.plugins.ListPlugins.PluginInfo;
@ -35,7 +36,12 @@ class DisablePlugin implements RestModifyView<PluginResource, Input> {
} }
@Override @Override
public PluginInfo apply(PluginResource resource, Input input) { public PluginInfo apply(PluginResource resource, Input input)
throws MethodNotAllowedException {
if (!loader.isRemoteAdminEnabled()) {
throw new MethodNotAllowedException(
"remote plugin administration is disabled");
}
String name = resource.getName(); String name = resource.getName();
loader.disablePlugins(ImmutableSet.of(name)); loader.disablePlugins(ImmutableSet.of(name));
return new PluginInfo(loader.get(name)); return new PluginInfo(loader.get(name));

View File

@ -17,6 +17,7 @@ package com.google.gerrit.server.plugins;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability; import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestModifyView; import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.plugins.EnablePlugin.Input; import com.google.gerrit.server.plugins.EnablePlugin.Input;
@ -40,7 +41,11 @@ class EnablePlugin implements RestModifyView<PluginResource, Input> {
@Override @Override
public PluginInfo apply(PluginResource resource, Input input) public PluginInfo apply(PluginResource resource, Input input)
throws ResourceConflictException { throws ResourceConflictException, MethodNotAllowedException {
if (!loader.isRemoteAdminEnabled()) {
throw new MethodNotAllowedException(
"remote plugin administration is disabled");
}
String name = resource.getName(); String name = resource.getName();
try { try {
loader.enablePlugins(ImmutableSet.of(name)); loader.enablePlugins(ImmutableSet.of(name));

View File

@ -18,6 +18,7 @@ import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability; import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.DefaultInput; import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.RawInput; import com.google.gerrit.extensions.restapi.RawInput;
import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView; import com.google.gerrit.extensions.restapi.RestModifyView;
@ -53,8 +54,11 @@ class InstallPlugin implements RestModifyView<TopLevelResource, Input> {
} }
@Override @Override
public Response<PluginInfo> apply(TopLevelResource resource, public Response<PluginInfo> apply(TopLevelResource resource, Input input)
Input input) throws BadRequestException, IOException { throws BadRequestException, MethodNotAllowedException, IOException {
if (!loader.isRemoteAdminEnabled()) {
throw new MethodNotAllowedException("remote installation is disabled");
}
try { try {
InputStream in; InputStream in;
if (input.raw != null) { if (input.raw != null) {
@ -102,8 +106,8 @@ class InstallPlugin implements RestModifyView<TopLevelResource, Input> {
} }
@Override @Override
public Response<PluginInfo> apply(PluginResource resource, public Response<PluginInfo> apply(PluginResource resource, Input input)
Input input) throws BadRequestException, IOException { throws BadRequestException, MethodNotAllowedException, IOException {
return new InstallPlugin(loader, resource.getName(), false) return new InstallPlugin(loader, resource.getName(), false)
.apply(TopLevelResource.INSTANCE, input); .apply(TopLevelResource.INSTANCE, input);
} }

View File

@ -90,6 +90,7 @@ public class PluginLoader implements LifecycleListener {
private final Provider<PluginCleanerTask> cleaner; private final Provider<PluginCleanerTask> cleaner;
private final PluginScannerThread scanner; private final PluginScannerThread scanner;
private final Provider<String> urlProvider; private final Provider<String> urlProvider;
private final boolean remoteAdmin;
@Inject @Inject
public PluginLoader(SitePaths sitePaths, public PluginLoader(SitePaths sitePaths,
@ -113,6 +114,9 @@ public class PluginLoader implements LifecycleListener {
cleaner = pct; cleaner = pct;
urlProvider = provider; urlProvider = provider;
remoteAdmin =
cfg.getBoolean("plugins", null, "allowRemoteAdmin", false);
long checkFrequency = ConfigUtil.getTimeUnit(cfg, long checkFrequency = ConfigUtil.getTimeUnit(cfg,
"plugins", null, "checkFrequency", "plugins", null, "checkFrequency",
TimeUnit.MINUTES.toMillis(1), TimeUnit.MILLISECONDS); TimeUnit.MINUTES.toMillis(1), TimeUnit.MILLISECONDS);
@ -123,6 +127,10 @@ public class PluginLoader implements LifecycleListener {
} }
} }
public boolean isRemoteAdminEnabled() {
return remoteAdmin;
}
public Plugin get(String name) { public Plugin get(String name) {
Plugin p = running.get(name); Plugin p = running.get(name);
if (p != null) { if (p != null) {
@ -143,6 +151,8 @@ public class PluginLoader implements LifecycleListener {
public void installPluginFromStream(String originalName, InputStream in) public void installPluginFromStream(String originalName, InputStream in)
throws IOException, PluginInstallException { throws IOException, PluginInstallException {
checkRemoteInstall();
String fileName = originalName; String fileName = originalName;
if (!(fileName.endsWith(".jar") || fileName.endsWith(".js"))) { if (!(fileName.endsWith(".jar") || fileName.endsWith(".js"))) {
fileName += ".jar"; fileName += ".jar";
@ -227,6 +237,12 @@ public class PluginLoader implements LifecycleListener {
} }
public void disablePlugins(Set<String> names) { public void disablePlugins(Set<String> names) {
if (!isRemoteAdminEnabled()) {
log.warn("Remote plugin administration is disabled,"
+ " ignoring disablePlugins(" + names + ")");
return;
}
synchronized (this) { synchronized (this) {
for (String name : names) { for (String name : names) {
Plugin active = running.get(name); Plugin active = running.get(name);
@ -255,6 +271,12 @@ public class PluginLoader implements LifecycleListener {
} }
public void enablePlugins(Set<String> names) throws PluginInstallException { public void enablePlugins(Set<String> names) throws PluginInstallException {
if (!isRemoteAdminEnabled()) {
log.warn("Remote plugin administration is disabled,"
+ " ignoring enablePlugins(" + names + ")");
return;
}
synchronized (this) { synchronized (this) {
for (String name : names) { for (String name : names) {
Plugin off = disabled.get(name); Plugin off = disabled.get(name);
@ -748,4 +770,10 @@ public class PluginLoader implements LifecycleListener {
String fullExt = "." + ext; String fullExt = "." + ext;
return fileName.endsWith(fullExt) || fileName.endsWith(fullExt + ".disabled"); return fileName.endsWith(fullExt) || fileName.endsWith(fullExt + ".disabled");
} }
private void checkRemoteInstall() throws PluginInstallException {
if (!isRemoteAdminEnabled()) {
throw new PluginInstallException("remote installation is disabled");
}
}
} }

View File

@ -17,6 +17,7 @@ package com.google.gerrit.server.plugins;
import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate; import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.IdString; import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestCollection; import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestView; import com.google.gerrit.extensions.restapi.RestView;
@ -58,7 +59,10 @@ public class PluginsCollection implements
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public InstallPlugin create(TopLevelResource parent, IdString id) public InstallPlugin create(TopLevelResource parent, IdString id)
throws ResourceNotFoundException { throws ResourceNotFoundException, MethodNotAllowedException {
if (!loader.isRemoteAdminEnabled()) {
throw new MethodNotAllowedException("remote installation is disabled");
}
return new InstallPlugin(loader, id.get(), true /* created */); return new InstallPlugin(loader, id.get(), true /* created */);
} }

View File

@ -41,6 +41,9 @@ final class PluginEnableCommand extends SshCommand {
@Override @Override
protected void run() throws UnloggedFailure { protected void run() throws UnloggedFailure {
if (!loader.isRemoteAdminEnabled()) {
throw die("remote plugin administration is disabled");
}
if (names != null && !names.isEmpty()) { if (names != null && !names.isEmpty()) {
try { try {
loader.enablePlugins(Sets.newHashSet(names)); loader.enablePlugins(Sets.newHashSet(names));

View File

@ -56,6 +56,9 @@ final class PluginInstallCommand extends SshCommand {
@Override @Override
protected void run() throws UnloggedFailure { protected void run() throws UnloggedFailure {
if (!loader.isRemoteAdminEnabled()) {
throw die("remote installation is disabled");
}
if (Strings.isNullOrEmpty(source)) { if (Strings.isNullOrEmpty(source)) {
throw die("Argument \"-|URL\" is required"); throw die("Argument \"-|URL\" is required");
} }

View File

@ -39,7 +39,10 @@ final class PluginRemoveCommand extends SshCommand {
private PluginLoader loader; private PluginLoader loader;
@Override @Override
protected void run() { protected void run() throws UnloggedFailure {
if (!loader.isRemoteAdminEnabled()) {
throw die("remote plugin administration is disabled");
}
if (names != null && !names.isEmpty()) { if (names != null && !names.isEmpty()) {
loader.disablePlugins(Sets.newHashSet(names)); loader.disablePlugins(Sets.newHashSet(names));
} }