Merge "suexec: Honor Run As capability like HTTP REST API"

This commit is contained in:
Shawn Pearce
2013-06-11 23:31:37 +00:00
committed by Gerrit Code Review
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 Run As
~~~~~~ ~~~~~~
Allow users to impersonate any other user with the X-Gerrit-RunAs Allow users to impersonate any other user with the X-Gerrit-RunAs HTTP
HTTP header on REST API calls. Site administrators do not inherit header on REST API calls or the link:cmd-suexec.html[suexec] SSH
this capability; it must be granted explicitly. command. Site administrators do not inherit this capability; it must
be granted explicitly.
[[capability_runGC]] [[capability_runGC]]

View File

@@ -19,10 +19,14 @@ SYNOPSIS
DESCRIPTION DESCRIPTION
----------- -----------
The suexec command can only be invoked by the magic user `Gerrit The suexec command permits executing any other command as any other
Code Review` and permits executing any other command as any other
registered user account. 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 OPTIONS
------- -------
@@ -39,7 +43,8 @@ COMMAND::
ACCESS ACCESS
------ ------
Caller must be the magic user Gerrit Code Review using the SSH 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 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.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountResolver; 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.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import com.google.inject.servlet.ServletModule; import com.google.inject.servlet.ServletModule;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -61,10 +60,10 @@ class RunAsFilter implements Filter {
private final AccountResolver accountResolver; private final AccountResolver accountResolver;
@Inject @Inject
RunAsFilter(@GerritServerConfig Config config, RunAsFilter(AuthConfig config,
Provider<WebSession> session, Provider<WebSession> session,
AccountResolver accountResolver) { AccountResolver accountResolver) {
this.enabled = config.getBoolean("auth", null, "enableRunAs", true); this.enabled = config.isRunAsEnabled();
this.session = session; this.session = session;
this.accountResolver = accountResolver; this.accountResolver = accountResolver;
} }

View File

@@ -37,6 +37,7 @@ public class AuthConfig {
private final AuthType authType; private final AuthType authType;
private final String httpHeader; private final String httpHeader;
private final boolean trustContainerAuth; private final boolean trustContainerAuth;
private final boolean enableRunAs;
private final boolean userNameToLowerCase; private final boolean userNameToLowerCase;
private final boolean gitBasicAuth; private final boolean gitBasicAuth;
private final String logoutUrl; private final String logoutUrl;
@@ -64,6 +65,7 @@ public class AuthConfig {
cookiePath = cfg.getString("auth", null, "cookiepath"); cookiePath = cfg.getString("auth", null, "cookiepath");
cookieSecure = cfg.getBoolean("auth", "cookiesecure", false); cookieSecure = cfg.getBoolean("auth", "cookiesecure", false);
trustContainerAuth = cfg.getBoolean("auth", "trustContainerAuth", false); trustContainerAuth = cfg.getBoolean("auth", "trustContainerAuth", false);
enableRunAs = cfg.getBoolean("auth", null, "enableRunAs", true);
gitBasicAuth = cfg.getBoolean("auth", "gitBasicAuth", false); gitBasicAuth = cfg.getBoolean("auth", "gitBasicAuth", false);
userNameToLowerCase = cfg.getBoolean("auth", "userNameToLowerCase", false); userNameToLowerCase = cfg.getBoolean("auth", "userNameToLowerCase", false);
@@ -164,6 +166,11 @@ public class AuthConfig {
return trustContainerAuth; 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 */ /** Whether user name should be converted to lower-case before validation */
public boolean isUserNameToLowerCase() { public boolean isUserNameToLowerCase() {
return userNameToLowerCase; 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.CurrentUser;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PeerDaemonUser; import com.google.gerrit.server.PeerDaemonUser;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.sshd.SshScope.Context; import com.google.gerrit.sshd.SshScope.Context;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
@@ -45,6 +46,7 @@ public final class SuExec extends BaseCommand {
private final SshScope sshScope; private final SshScope sshScope;
private final DispatchCommandProvider dispatcher; private final DispatchCommandProvider dispatcher;
private boolean enableRunAs;
private Provider<CurrentUser> caller; private Provider<CurrentUser> caller;
private Provider<SshSession> session; private Provider<SshSession> session;
private IdentifiedUser.GenericFactory userFactory; private IdentifiedUser.GenericFactory userFactory;
@@ -66,36 +68,34 @@ public final class SuExec extends BaseCommand {
@CommandName(Commands.ROOT) final DispatchCommandProvider dispatcher, @CommandName(Commands.ROOT) final DispatchCommandProvider dispatcher,
final Provider<CurrentUser> caller, final Provider<SshSession> session, final Provider<CurrentUser> caller, final Provider<SshSession> session,
final IdentifiedUser.GenericFactory userFactory, final IdentifiedUser.GenericFactory userFactory,
final SshScope.Context callingContext) { final SshScope.Context callingContext,
AuthConfig config) {
this.sshScope = sshScope; this.sshScope = sshScope;
this.dispatcher = dispatcher; this.dispatcher = dispatcher;
this.caller = caller; this.caller = caller;
this.session = session; this.session = session;
this.userFactory = userFactory; this.userFactory = userFactory;
this.callingContext = callingContext; this.callingContext = callingContext;
this.enableRunAs = config.isRunAsEnabled();
atomicCmd = Atomics.newReference(); atomicCmd = Atomics.newReference();
} }
@Override @Override
public void start(Environment env) throws IOException { public void start(Environment env) throws IOException {
try { try {
if (caller.get() instanceof PeerDaemonUser) { checkCanRunAs();
parseCommandLine(); parseCommandLine();
final Context ctx = callingContext.subContext(newSession(), join(args)); final Context ctx = callingContext.subContext(newSession(), join(args));
final Context old = sshScope.set(ctx); final Context old = sshScope.set(ctx);
try { try {
final BaseCommand cmd = dispatcher.get(); final BaseCommand cmd = dispatcher.get();
cmd.setArguments(args.toArray(new String[args.size()])); cmd.setArguments(args.toArray(new String[args.size()]));
provideStateTo(cmd); provideStateTo(cmd);
atomicCmd.set(cmd); atomicCmd.set(cmd);
cmd.start(env); cmd.start(env);
} finally { } finally {
sshScope.set(old); sshScope.set(old);
}
} else {
throw new UnloggedFailure(1, "fatal: Not a peer daemon");
} }
} catch (UnloggedFailure e) { } catch (UnloggedFailure e) {
String msg = e.getMessage(); 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() { private SshSession newSession() {
final SocketAddress peer; final SocketAddress peer;
if (peerAddress == null) { if (peerAddress == null) {