diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt index bda424bb71..37ea0b75af 100644 --- a/Documentation/access-control.txt +++ b/Documentation/access-control.txt @@ -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]] diff --git a/Documentation/cmd-suexec.txt b/Documentation/cmd-suexec.txt index baffd536e8..78fc361593 100644 --- a/Documentation/cmd-suexec.txt +++ b/Documentation/cmd-suexec.txt @@ -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 --------- diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java index 37617e1737..ff65674511 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java @@ -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 session, AccountResolver accountResolver) { - this.enabled = config.getBoolean("auth", null, "enableRunAs", true); + this.enabled = config.isRunAsEnabled(); this.session = session; this.accountResolver = accountResolver; } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java index 9a804c10cb..06d2a7193c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java @@ -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; diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java index ccf23e1921..54bb84c83c 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java @@ -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 caller; private Provider session; private IdentifiedUser.GenericFactory userFactory; @@ -66,36 +68,34 @@ public final class SuExec extends BaseCommand { @CommandName(Commands.ROOT) final DispatchCommandProvider dispatcher, final Provider caller, final Provider 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) {