suexec: Honor Run As capability like HTTP REST API

This makes the behavior of the two APIs more consistent.  suexec is
still allowed for the magical "Gerrit Code Review" PeerDaemonUser, but
is now also permitted if the Run As capability has been granted.

Change-Id: I9acd6467d9e02084519b30e0c4a3d08065e74484
This commit is contained in:
Shawn Pearce 2013-06-11 13:47:21 -07:00
parent a931fe1bad
commit 08ae5775ea
5 changed files with 50 additions and 27 deletions

View File

@ -1270,9 +1270,10 @@ command, but also to the web UI results pagination size.
Run As
~~~~~~
Allow users to impersonate any other user with the X-Gerrit-RunAs
HTTP header on REST API calls. Site administrators do not inherit
this capability; it must be granted explicitly.
Allow users to impersonate any other user with the X-Gerrit-RunAs HTTP
header on REST API calls or the link:cmd-suexec.html[suexec] SSH
command. Site administrators do not inherit this capability; it must
be granted explicitly.
[[capability_runGC]]

View File

@ -19,10 +19,14 @@ SYNOPSIS
DESCRIPTION
-----------
The suexec command can only be invoked by the magic user `Gerrit
Code Review` and permits executing any other command as any other
The suexec command permits executing any other command as any other
registered user account.
suexec can only be invoked by the magic user `Gerrit Code Review`,
or any user granted granted the link:access-control.html#capability_runAs[Run As]
capability. The run as capability is permitted to be used only if
link:config-gerrit.html[auth.enableRunAs] is true.
OPTIONS
-------
@ -39,7 +43,8 @@ COMMAND::
ACCESS
------
Caller must be the magic user Gerrit Code Review using the SSH
daemon's host key or a key on this daemon's peer host key ring.
daemon's host key, or a key on this daemon's peer host key ring,
or a user granted the Run As capability.
SCRIPTING
---------

View File

@ -21,14 +21,13 @@ import com.google.gerrit.httpd.restapi.RestApiServlet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.servlet.ServletModule;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -61,10 +60,10 @@ class RunAsFilter implements Filter {
private final AccountResolver accountResolver;
@Inject
RunAsFilter(@GerritServerConfig Config config,
RunAsFilter(AuthConfig config,
Provider<WebSession> session,
AccountResolver accountResolver) {
this.enabled = config.getBoolean("auth", null, "enableRunAs", true);
this.enabled = config.isRunAsEnabled();
this.session = session;
this.accountResolver = accountResolver;
}

View File

@ -37,6 +37,7 @@ public class AuthConfig {
private final AuthType authType;
private final String httpHeader;
private final boolean trustContainerAuth;
private final boolean enableRunAs;
private final boolean userNameToLowerCase;
private final boolean gitBasicAuth;
private final String logoutUrl;
@ -64,6 +65,7 @@ public class AuthConfig {
cookiePath = cfg.getString("auth", null, "cookiepath");
cookieSecure = cfg.getBoolean("auth", "cookiesecure", false);
trustContainerAuth = cfg.getBoolean("auth", "trustContainerAuth", false);
enableRunAs = cfg.getBoolean("auth", null, "enableRunAs", true);
gitBasicAuth = cfg.getBoolean("auth", "gitBasicAuth", false);
userNameToLowerCase = cfg.getBoolean("auth", "userNameToLowerCase", false);
@ -164,6 +166,11 @@ public class AuthConfig {
return trustContainerAuth;
}
/** @return true if users with Run As capability can impersonate others. */
public boolean isRunAsEnabled() {
return enableRunAs;
}
/** Whether user name should be converted to lower-case before validation */
public boolean isUserNameToLowerCase() {
return userNameToLowerCase;

View File

@ -19,6 +19,7 @@ import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PeerDaemonUser;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.sshd.SshScope.Context;
import com.google.inject.Inject;
import com.google.inject.Provider;
@ -45,6 +46,7 @@ public final class SuExec extends BaseCommand {
private final SshScope sshScope;
private final DispatchCommandProvider dispatcher;
private boolean enableRunAs;
private Provider<CurrentUser> caller;
private Provider<SshSession> session;
private IdentifiedUser.GenericFactory userFactory;
@ -66,36 +68,34 @@ public final class SuExec extends BaseCommand {
@CommandName(Commands.ROOT) final DispatchCommandProvider dispatcher,
final Provider<CurrentUser> caller, final Provider<SshSession> session,
final IdentifiedUser.GenericFactory userFactory,
final SshScope.Context callingContext) {
final SshScope.Context callingContext,
AuthConfig config) {
this.sshScope = sshScope;
this.dispatcher = dispatcher;
this.caller = caller;
this.session = session;
this.userFactory = userFactory;
this.callingContext = callingContext;
this.enableRunAs = config.isRunAsEnabled();
atomicCmd = Atomics.newReference();
}
@Override
public void start(Environment env) throws IOException {
try {
if (caller.get() instanceof PeerDaemonUser) {
parseCommandLine();
checkCanRunAs();
parseCommandLine();
final Context ctx = callingContext.subContext(newSession(), join(args));
final Context old = sshScope.set(ctx);
try {
final BaseCommand cmd = dispatcher.get();
cmd.setArguments(args.toArray(new String[args.size()]));
provideStateTo(cmd);
atomicCmd.set(cmd);
cmd.start(env);
} finally {
sshScope.set(old);
}
} else {
throw new UnloggedFailure(1, "fatal: Not a peer daemon");
final Context ctx = callingContext.subContext(newSession(), join(args));
final Context old = sshScope.set(ctx);
try {
final BaseCommand cmd = dispatcher.get();
cmd.setArguments(args.toArray(new String[args.size()]));
provideStateTo(cmd);
atomicCmd.set(cmd);
cmd.start(env);
} finally {
sshScope.set(old);
}
} catch (UnloggedFailure e) {
String msg = e.getMessage();
@ -108,6 +108,17 @@ public final class SuExec extends BaseCommand {
}
}
private void checkCanRunAs() throws UnloggedFailure {
if (caller.get() instanceof PeerDaemonUser) {
// OK.
} else if (!enableRunAs) {
throw new UnloggedFailure(1,
"fatal: suexec disabled by auth.enableRunAs = false");
} else if (!caller.get().getCapabilities().canRunAs()) {
throw new UnloggedFailure(1, "fatal: suexec not permitted");
}
}
private SshSession newSession() {
final SocketAddress peer;
if (peerAddress == null) {