Allow suexec to run any command as any user
The suexec command can only be run by a peer daemon, and permits the daemon to execute another command as a specific user identity. This is the foundation of allowing writes to be proxied from a slave server into the master, the slave just needs to SSH into the master and use suexec in front of the user supplied command line to perform an action on their behalf on the master. Unfortunately this means we have to trust the slave process, as it can become anyone, including an administrator. A better approach would be to use agent authentication and authenticate back through the slave to the user's agent process, but not every user connection may be using an agent. In particular batch jobs might be using an unencrypted key and no agent to authenticate. Change-Id: Icb8ddb16959f01189a6c0bdfc8fec45cdd99659b Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
145
gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
Normal file
145
gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
Normal file
@@ -0,0 +1,145 @@
|
||||
// Copyright (C) 2010 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.reviewdb.Account;
|
||||
import com.google.gerrit.server.AccessPath;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.PeerDaemonUser;
|
||||
import com.google.gerrit.sshd.SshScope.Context;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import org.apache.sshd.server.Command;
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Executes any other command as a different user identity.
|
||||
* <p>
|
||||
* The calling user must be authenticated as a {@link PeerDaemonUser}, which
|
||||
* usually requires public key authentication using this daemon's private host
|
||||
* key, or a key on this daemon's peer host key ring.
|
||||
*/
|
||||
public final class SuExec extends BaseCommand {
|
||||
private final DispatchCommandProvider dispatcher;
|
||||
|
||||
private Provider<CurrentUser> caller;
|
||||
private Provider<SshSession> session;
|
||||
private IdentifiedUser.GenericFactory userFactory;
|
||||
|
||||
@Option(name = "--as", required = true)
|
||||
private Account.Id accountId;
|
||||
|
||||
@Option(name = "--from")
|
||||
private SocketAddress peerAddress;
|
||||
|
||||
@Argument(index = 0, multiValued = true, metaVar = "COMMAND")
|
||||
private List<String> args = new ArrayList<String>();
|
||||
|
||||
private Command cmd;
|
||||
|
||||
@Inject
|
||||
SuExec(@CommandName(Commands.ROOT) final DispatchCommandProvider dispatcher,
|
||||
final Provider<CurrentUser> caller, final Provider<SshSession> session,
|
||||
final IdentifiedUser.GenericFactory userFactory) {
|
||||
this.dispatcher = dispatcher;
|
||||
this.caller = caller;
|
||||
this.session = session;
|
||||
this.userFactory = userFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Environment env) throws IOException {
|
||||
try {
|
||||
if (caller.get() instanceof PeerDaemonUser) {
|
||||
final PeerDaemonUser peer = (PeerDaemonUser) caller.get();
|
||||
|
||||
parseCommandLine();
|
||||
|
||||
final Context ctx = new Context(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);
|
||||
|
||||
synchronized (this) {
|
||||
this.cmd = cmd;
|
||||
}
|
||||
cmd.start(env);
|
||||
} finally {
|
||||
SshScope.set(old);
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new UnloggedFailure(1, "fatal: Not a peer daemon");
|
||||
}
|
||||
} catch (UnloggedFailure e) {
|
||||
String msg = e.getMessage();
|
||||
if (!msg.endsWith("\n")) {
|
||||
msg += "\n";
|
||||
}
|
||||
err.write(msg.getBytes("UTF-8"));
|
||||
err.flush();
|
||||
onExit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private SshSession newSession() {
|
||||
final SocketAddress peer;
|
||||
if (peerAddress == null) {
|
||||
peer = session.get().getRemoteAddress();
|
||||
} else {
|
||||
peer = peerAddress;
|
||||
}
|
||||
|
||||
return new SshSession(session.get(), peer, userFactory.create(
|
||||
AccessPath.SSH, new Provider<SocketAddress>() {
|
||||
@Override
|
||||
public SocketAddress get() {
|
||||
return peer;
|
||||
}
|
||||
}, accountId));
|
||||
}
|
||||
|
||||
private static String join(List<String> args) {
|
||||
StringBuilder r = new StringBuilder();
|
||||
for (String a : args) {
|
||||
if (r.length() > 0) {
|
||||
r.append(" ");
|
||||
}
|
||||
r.append(a);
|
||||
}
|
||||
return r.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
synchronized (this) {
|
||||
if (cmd != null) {
|
||||
cmd.destroy();
|
||||
cmd = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user