Merge branch 'stable-2.15' into stable-2.16

* stable-2.15:
  Add a new extension point SshExecuteCommandInterceptor

Change-Id: I96b30bf681a1a96d8f6cf5ca53f70f8bb13dd740
This commit is contained in:
David Pursehouse
2019-04-22 20:11:20 +09:00
4 changed files with 93 additions and 7 deletions

View File

@@ -2786,8 +2786,8 @@ public class MyPlugin implements MailFilter {
}
----
[[ssh-command-interception]]
== SSH Command Interception
[[ssh-command-creation-interception]]
== SSH Command Creation Interception
Gerrit provides an extension point that allows a plugin to intercept
creation of SSH commands and override the functionality with its own
@@ -2910,6 +2910,40 @@ public class MyPluginModule extends AbstractModule {
}
----
[[ssh-command-execution-interception]]
== SSH Command Execution Interception
Gerrit provides an extension point that enables plugins to check and
prevent an SSH command from being run.
[source, java]
----
import com.google.gerrit.sshd.SshExecuteCommandInterceptor;
@Singleton
public class SshExecuteCommandInterceptorImpl implements SshExecuteCommandInterceptor {
private final Provider<SshSession> sessionProvider;
@Inject
SshExecuteCommandInterceptorImpl(Provider<SshSession> sessionProvider) {
this.sessionProvider = sessionProvider;
}
@Override
public boolean accept(String command, List<String> arguments) {
if (command.startsWith("gerrit") && !"10.1.2.3".equals(sessionProvider.get().getRemoteAddressAsString())) {
return false;
}
return true;
}
}
----
And then declare it in your SSH module:
[source, java]
----
DynamicSet.bind(binder(), SshExecuteCommandInterceptor.class).to(SshExecuteCommandInterceptorImpl.class);
----
Plugin authors should also consider binding their SubmitRule using a `Gerrit-BatchModule`.
See link:dev-plugins.html[Batch runtime] for more informations.

View File

@@ -18,6 +18,7 @@ import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Atomics;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.args4j.SubcommandHandler;
import com.google.gerrit.server.permissions.GlobalPermission;
@@ -44,6 +45,7 @@ final class DispatchCommand extends BaseCommand {
private final PermissionBackend permissionBackend;
private final Map<String, CommandProvider> commands;
private final AtomicReference<Command> atomicCmd;
private final DynamicSet<SshExecuteCommandInterceptor> commandInterceptors;
@Argument(index = 0, required = false, metaVar = "COMMAND", handler = SubcommandHandler.class)
private String commandName;
@@ -52,10 +54,14 @@ final class DispatchCommand extends BaseCommand {
private List<String> args = new ArrayList<>();
@Inject
DispatchCommand(PermissionBackend permissionBackend, @Assisted Map<String, CommandProvider> all) {
DispatchCommand(
PermissionBackend permissionBackend,
DynamicSet<SshExecuteCommandInterceptor> commandInterceptors,
@Assisted Map<String, CommandProvider> all) {
this.permissionBackend = permissionBackend;
commands = all;
atomicCmd = Atomics.newReference();
this.commandInterceptors = commandInterceptors;
}
Map<String, CommandProvider> getMap() {
@@ -84,19 +90,29 @@ final class DispatchCommand extends BaseCommand {
final Command cmd = p.getProvider().get();
checkRequiresCapability(cmd);
String actualCommandName = commandName;
if (cmd instanceof BaseCommand) {
final BaseCommand bc = (BaseCommand) cmd;
if (getName().isEmpty()) {
bc.setName(commandName);
} else {
bc.setName(getName() + " " + commandName);
if (!getName().isEmpty()) {
actualCommandName = getName() + " " + commandName;
}
bc.setName(actualCommandName);
bc.setArguments(args.toArray(new String[args.size()]));
} else if (!args.isEmpty()) {
throw die(commandName + " does not take arguments");
}
for (SshExecuteCommandInterceptor commandInterceptor : commandInterceptors) {
if (!commandInterceptor.accept(actualCommandName, args)) {
throw new UnloggedFailure(
126,
String.format(
"blocked by %s, contact gerrit administrators for more details",
commandInterceptor.name()));
}
}
provideStateTo(cmd);
atomicCmd.set(cmd);
cmd.start(env);

View File

@@ -0,0 +1,35 @@
// Copyright (C) 2019 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.sshd;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import java.util.List;
@ExtensionPoint
public interface SshExecuteCommandInterceptor {
/**
* Check the command and return false if this command must not be run.
*
* @param command the command
* @param arguments the list of arguments
* @return whether or not this command with these arguments can be executed
*/
boolean accept(String command, List<String> arguments);
default String name() {
return this.getClass().getSimpleName();
}
}

View File

@@ -106,6 +106,7 @@ public class SshModule extends LifecycleModule {
DynamicMap.mapOf(binder(), DynamicOptions.DynamicBean.class);
DynamicItem.itemOf(binder(), SshCreateCommandInterceptor.class);
DynamicSet.setOf(binder(), SshExecuteCommandInterceptor.class);
listener().toInstance(registerInParentInjectors());
listener().to(SshLog.class);