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:
parent
a931fe1bad
commit
08ae5775ea
@ -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]]
|
||||||
|
@ -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
|
||||||
---------
|
---------
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user