mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-12 18:02:40 +01:00
Proxy VCS SSH requests
Summary: Fixes T7034. Like HTTP, proxy requests to the correct host if a repository has an Almanac service host. Test Plan: Ran VCS requests through the proxy. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T7034 Differential Revision: https://secure.phabricator.com/D11543
This commit is contained in:
parent
fe0ca0abf2
commit
8798083ad9
9 changed files with 289 additions and 48 deletions
|
@ -8,15 +8,6 @@ $keys = id(new PhabricatorAuthSSHKeyQuery())
|
||||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
foreach ($keys as $key => $ssh_key) {
|
|
||||||
// For now, filter out any keys which don't belong to users. Eventually we
|
|
||||||
// may allow devices to use this channel.
|
|
||||||
if (!($ssh_key->getObject() instanceof PhabricatorUser)) {
|
|
||||||
unset($keys[$key]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$keys) {
|
if (!$keys) {
|
||||||
echo pht('No keys found.')."\n";
|
echo pht('No keys found.')."\n";
|
||||||
exit(1);
|
exit(1);
|
||||||
|
@ -24,11 +15,26 @@ if (!$keys) {
|
||||||
|
|
||||||
$bin = $root.'/bin/ssh-exec';
|
$bin = $root.'/bin/ssh-exec';
|
||||||
foreach ($keys as $ssh_key) {
|
foreach ($keys as $ssh_key) {
|
||||||
$user = $ssh_key->getObject()->getUsername();
|
|
||||||
|
|
||||||
$key_argv = array();
|
$key_argv = array();
|
||||||
|
$object = $ssh_key->getObject();
|
||||||
|
if ($object instanceof PhabricatorUser) {
|
||||||
$key_argv[] = '--phabricator-ssh-user';
|
$key_argv[] = '--phabricator-ssh-user';
|
||||||
$key_argv[] = $user;
|
$key_argv[] = $object->getUsername();
|
||||||
|
} else if ($object instanceof AlmanacDevice) {
|
||||||
|
if (!$ssh_key->getIsTrusted()) {
|
||||||
|
// If this key is not a trusted device key, don't allow SSH
|
||||||
|
// authentication.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$key_argv[] = '--phabricator-ssh-device';
|
||||||
|
$key_argv[] = $object->getName();
|
||||||
|
} else {
|
||||||
|
// We don't know what sort of key this is; don't permit SSH auth.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key_argv[] = '--phabricator-ssh-key';
|
||||||
|
$key_argv[] = $ssh_key->getID();
|
||||||
|
|
||||||
$cmd = csprintf('%s %Ls', $bin, $key_argv);
|
$cmd = csprintf('%s %Ls', $bin, $key_argv);
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,14 @@ require_once $root.'/scripts/__init_script__.php';
|
||||||
|
|
||||||
$ssh_log = PhabricatorSSHLog::getLog();
|
$ssh_log = PhabricatorSSHLog::getLog();
|
||||||
|
|
||||||
// First, figure out the authenticated user.
|
|
||||||
$args = new PhutilArgumentParser($argv);
|
$args = new PhutilArgumentParser($argv);
|
||||||
$args->setTagline('receive SSH requests');
|
$args->setTagline('execute SSH requests');
|
||||||
$args->setSynopsis(<<<EOSYNOPSIS
|
$args->setSynopsis(<<<EOSYNOPSIS
|
||||||
**ssh-exec** --phabricator-ssh-user __user__ [--ssh-command __commmand__]
|
**ssh-exec** --phabricator-ssh-user __user__ [--ssh-command __commmand__]
|
||||||
Receive SSH requests.
|
**ssh-exec** --phabricator-ssh-device __device__ [--ssh-command __commmand__]
|
||||||
|
Execute authenticated SSH requests. This script is normally invoked
|
||||||
|
via SSHD, but can be invoked manually for testing.
|
||||||
|
|
||||||
EOSYNOPSIS
|
EOSYNOPSIS
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -22,24 +24,150 @@ $args->parse(
|
||||||
array(
|
array(
|
||||||
'name' => 'phabricator-ssh-user',
|
'name' => 'phabricator-ssh-user',
|
||||||
'param' => 'username',
|
'param' => 'username',
|
||||||
|
'help' => pht(
|
||||||
|
'If the request authenticated with a user key, the name of the '.
|
||||||
|
'user.'),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'name' => 'phabricator-ssh-device',
|
||||||
|
'param' => 'name',
|
||||||
|
'help' => pht(
|
||||||
|
'If the request authenticated with a device key, the name of the '.
|
||||||
|
'device.'),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'name' => 'phabricator-ssh-key',
|
||||||
|
'param' => 'id',
|
||||||
|
'help' => pht(
|
||||||
|
'The ID of the SSH key which authenticated this request. This is '.
|
||||||
|
'used to allow logs to report when specific keys were used, to make '.
|
||||||
|
'it easier to manage credentials.'),
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'name' => 'ssh-command',
|
'name' => 'ssh-command',
|
||||||
'param' => 'command',
|
'param' => 'command',
|
||||||
|
'help' => pht(
|
||||||
|
'Provide a command to execute. This makes testing this script '.
|
||||||
|
'easier. When running normally, the command is read from the '.
|
||||||
|
'environment (SSH_ORIGINAL_COMMAND), which is populated by sshd.'),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$user_name = $args->getArg('phabricator-ssh-user');
|
$remote_address = null;
|
||||||
if (!strlen($user_name)) {
|
$ssh_client = getenv('SSH_CLIENT');
|
||||||
throw new Exception('No username.');
|
if ($ssh_client) {
|
||||||
|
// This has the format "<ip> <remote-port> <local-port>". Grab the IP.
|
||||||
|
$remote_address = head(explode(' ', $ssh_client));
|
||||||
|
$ssh_log->setData(
|
||||||
|
array(
|
||||||
|
'r' => $remote_address,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = id(new PhabricatorUser())->loadOneWhere(
|
$key_id = $args->getArg('phabricator-ssh-key');
|
||||||
'userName = %s',
|
if ($key_id) {
|
||||||
$user_name);
|
$ssh_log->setData(
|
||||||
|
array(
|
||||||
|
'k' => $key_id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_name = $args->getArg('phabricator-ssh-user');
|
||||||
|
$device_name = $args->getArg('phabricator-ssh-device');
|
||||||
|
|
||||||
|
$user = null;
|
||||||
|
$device = null;
|
||||||
|
$is_cluster_request = false;
|
||||||
|
|
||||||
|
if ($user_name && $device_name) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'The --phabricator-ssh-user and --phabricator-ssh-device flags are '.
|
||||||
|
'mutually exclusive. You can not authenticate as both a user ("%s") '.
|
||||||
|
'and a device ("%s"). Specify one or the other, but not both.',
|
||||||
|
$user_name,
|
||||||
|
$device_name));
|
||||||
|
} else if (strlen($user_name)) {
|
||||||
|
$user = id(new PhabricatorPeopleQuery())
|
||||||
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
|
->withUsernames(array($user_name))
|
||||||
|
->executeOne();
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
throw new Exception('Invalid username.');
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Invalid username ("%s"). There is no user with this username.',
|
||||||
|
$user_name));
|
||||||
|
}
|
||||||
|
} else if (strlen($device_name)) {
|
||||||
|
if (!$remote_address) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Unable to identify remote address from the SSH_CLIENT environment '.
|
||||||
|
'variable. Device authentication is accepted only from trusted '.
|
||||||
|
'sources.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PhabricatorEnv::isClusterAddress($remote_address)) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'This request originates from outside of the Phabricator cluster '.
|
||||||
|
'address range. Requests signed with a trusted device key must '.
|
||||||
|
'originate from trusted hosts.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$device = id(new AlmanacDeviceQuery())
|
||||||
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
|
->withNames(array($device_name))
|
||||||
|
->executeOne();
|
||||||
|
if (!$device) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Invalid device name ("%s"). There is no device with this name.',
|
||||||
|
$device->getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're authenticated as a device, but we're going to read the user out of
|
||||||
|
// the command below.
|
||||||
|
$is_cluster_request = true;
|
||||||
|
} else {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'This script must be invoked with either the --phabricator-ssh-user '.
|
||||||
|
'or --phabricator-ssh-device flag.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($args->getArg('ssh-command')) {
|
||||||
|
$original_command = $args->getArg('ssh-command');
|
||||||
|
} else {
|
||||||
|
$original_command = getenv('SSH_ORIGINAL_COMMAND');
|
||||||
|
}
|
||||||
|
|
||||||
|
$original_argv = id(new PhutilShellLexer())
|
||||||
|
->splitArguments($original_command);
|
||||||
|
|
||||||
|
if ($device) {
|
||||||
|
$act_as_name = array_shift($original_argv);
|
||||||
|
if (!preg_match('/^@/', $act_as_name)) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Commands executed by devices must identify an acting user in the '.
|
||||||
|
'first command argument. This request was not constructed '.
|
||||||
|
'properly.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$act_as_name = substr($act_as_name, 1);
|
||||||
|
$user = id(new PhabricatorPeopleQuery())
|
||||||
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
|
->withUsernames(array($act_as_name))
|
||||||
|
->executeOne();
|
||||||
|
if (!$user) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Device request identifies an acting user with an invalid '.
|
||||||
|
'username ("%s"). There is no user with this username.',
|
||||||
|
$act_as_name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$ssh_log->setData(
|
$ssh_log->setData(
|
||||||
|
@ -49,13 +177,11 @@ try {
|
||||||
));
|
));
|
||||||
|
|
||||||
if (!$user->isUserActivated()) {
|
if (!$user->isUserActivated()) {
|
||||||
throw new Exception(pht('Your account is not activated.'));
|
throw new Exception(
|
||||||
}
|
pht(
|
||||||
|
'Your account ("%s") is not activated. Visit the web interface '.
|
||||||
if ($args->getArg('ssh-command')) {
|
'for more information.',
|
||||||
$original_command = $args->getArg('ssh-command');
|
$user->getUsername()));
|
||||||
} else {
|
|
||||||
$original_command = getenv('SSH_ORIGINAL_COMMAND');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$workflows = id(new PhutilSymbolLoader())
|
$workflows = id(new PhutilSymbolLoader())
|
||||||
|
@ -64,9 +190,6 @@ try {
|
||||||
|
|
||||||
$workflow_names = mpull($workflows, 'getName', 'getName');
|
$workflow_names = mpull($workflows, 'getName', 'getName');
|
||||||
|
|
||||||
// Now, rebuild the original command.
|
|
||||||
$original_argv = id(new PhutilShellLexer())
|
|
||||||
->splitArguments($original_command);
|
|
||||||
if (!$original_argv) {
|
if (!$original_argv) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
|
@ -82,7 +205,7 @@ try {
|
||||||
implode(', ', $workflow_names)));
|
implode(', ', $workflow_names)));
|
||||||
}
|
}
|
||||||
|
|
||||||
$log_argv = implode(' ', array_slice($original_argv, 1));
|
$log_argv = implode(' ', $original_argv);
|
||||||
$log_argv = id(new PhutilUTF8StringTruncator())
|
$log_argv = id(new PhutilUTF8StringTruncator())
|
||||||
->setMaximumCodepoints(128)
|
->setMaximumCodepoints(128)
|
||||||
->truncateString($log_argv);
|
->truncateString($log_argv);
|
||||||
|
@ -94,16 +217,20 @@ try {
|
||||||
));
|
));
|
||||||
|
|
||||||
$command = head($original_argv);
|
$command = head($original_argv);
|
||||||
array_unshift($original_argv, 'phabricator-ssh-exec');
|
|
||||||
|
|
||||||
$original_args = new PhutilArgumentParser($original_argv);
|
$parseable_argv = $original_argv;
|
||||||
|
array_unshift($parseable_argv, 'phabricator-ssh-exec');
|
||||||
|
|
||||||
|
$parsed_args = new PhutilArgumentParser($parseable_argv);
|
||||||
|
|
||||||
if (empty($workflow_names[$command])) {
|
if (empty($workflow_names[$command])) {
|
||||||
throw new Exception('Invalid command.');
|
throw new Exception('Invalid command.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$workflow = $original_args->parseWorkflows($workflows);
|
$workflow = $parsed_args->parseWorkflows($workflows);
|
||||||
$workflow->setUser($user);
|
$workflow->setUser($user);
|
||||||
|
$workflow->setOriginalArguments($original_argv);
|
||||||
|
$workflow->setIsClusterRequest($is_cluster_request);
|
||||||
|
|
||||||
$sock_stdin = fopen('php://stdin', 'r');
|
$sock_stdin = fopen('php://stdin', 'r');
|
||||||
if (!$sock_stdin) {
|
if (!$sock_stdin) {
|
||||||
|
@ -130,7 +257,7 @@ try {
|
||||||
|
|
||||||
$rethrow = null;
|
$rethrow = null;
|
||||||
try {
|
try {
|
||||||
$err = $workflow->execute($original_args);
|
$err = $workflow->execute($parsed_args);
|
||||||
|
|
||||||
$metrics_channel->flush();
|
$metrics_channel->flush();
|
||||||
$error_channel->flush();
|
$error_channel->flush();
|
||||||
|
|
|
@ -37,6 +37,7 @@ final class PhabricatorAccessLogConfigOptions
|
||||||
$ssh_map = $common_map + array(
|
$ssh_map = $common_map + array(
|
||||||
's' => pht('The system user.'),
|
's' => pht('The system user.'),
|
||||||
'S' => pht('The system sudo user.'),
|
'S' => pht('The system sudo user.'),
|
||||||
|
'k' => pht('ID of the SSH key used to authenticate the request.'),
|
||||||
);
|
);
|
||||||
|
|
||||||
$http_desc = pht(
|
$http_desc = pht(
|
||||||
|
|
|
@ -19,7 +19,11 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
||||||
// This is a write, and must have write access.
|
// This is a write, and must have write access.
|
||||||
$this->requireWriteAccess();
|
$this->requireWriteAccess();
|
||||||
|
|
||||||
|
if ($this->shouldProxy()) {
|
||||||
|
$command = $this->getProxyCommand();
|
||||||
|
} else {
|
||||||
$command = csprintf('git-receive-pack %s', $repository->getLocalPath());
|
$command = csprintf('git-receive-pack %s', $repository->getLocalPath());
|
||||||
|
}
|
||||||
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
||||||
|
|
||||||
$future = id(new ExecFuture('%C', $command))
|
$future = id(new ExecFuture('%C', $command))
|
||||||
|
|
|
@ -16,7 +16,11 @@ final class DiffusionGitUploadPackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
||||||
protected function executeRepositoryOperations() {
|
protected function executeRepositoryOperations() {
|
||||||
$repository = $this->getRepository();
|
$repository = $this->getRepository();
|
||||||
|
|
||||||
|
if ($this->shouldProxy()) {
|
||||||
|
$command = $this->getProxyCommand();
|
||||||
|
} else {
|
||||||
$command = csprintf('git-upload-pack -- %s', $repository->getLocalPath());
|
$command = csprintf('git-upload-pack -- %s', $repository->getLocalPath());
|
||||||
|
}
|
||||||
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
||||||
|
|
||||||
$future = id(new ExecFuture('%C', $command))
|
$future = id(new ExecFuture('%C', $command))
|
||||||
|
|
|
@ -42,7 +42,13 @@ final class DiffusionMercurialServeSSHWorkflow
|
||||||
throw new Exception('Expected `hg ... serve`!');
|
throw new Exception('Expected `hg ... serve`!');
|
||||||
}
|
}
|
||||||
|
|
||||||
$command = csprintf('hg -R %s serve --stdio', $repository->getLocalPath());
|
if ($this->shouldProxy()) {
|
||||||
|
$command = $this->getProxyCommand();
|
||||||
|
} else {
|
||||||
|
$command = csprintf(
|
||||||
|
'hg -R %s serve --stdio',
|
||||||
|
$repository->getLocalPath());
|
||||||
|
}
|
||||||
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
||||||
|
|
||||||
$future = id(new ExecFuture('%C', $command))
|
$future = id(new ExecFuture('%C', $command))
|
||||||
|
|
|
@ -5,6 +5,7 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
|
||||||
private $args;
|
private $args;
|
||||||
private $repository;
|
private $repository;
|
||||||
private $hasWriteAccess;
|
private $hasWriteAccess;
|
||||||
|
private $proxyURI;
|
||||||
|
|
||||||
public function getRepository() {
|
public function getRepository() {
|
||||||
if (!$this->repository) {
|
if (!$this->repository) {
|
||||||
|
@ -49,14 +50,82 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function shouldProxy() {
|
||||||
|
return (bool)$this->proxyURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getProxyCommand() {
|
||||||
|
$uri = new PhutilURI($this->proxyURI);
|
||||||
|
|
||||||
|
$username = PhabricatorEnv::getEnvConfig('cluster.instance');
|
||||||
|
if (!strlen($username)) {
|
||||||
|
$username = PhabricatorEnv::getEnvConfig('diffusion.ssh-user');
|
||||||
|
if (!strlen($username)) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Unable to determine the username to connect with when trying '.
|
||||||
|
'to proxy an SSH request within the Phabricator cluster.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$port = $uri->getPort();
|
||||||
|
$host = $uri->getDomain();
|
||||||
|
$key_path = AlmanacKeys::getKeyPath('device.key');
|
||||||
|
if (!Filesystem::pathExists($key_path)) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Unable to proxy this SSH request within the cluster: this device '.
|
||||||
|
'is not registered and has a missing device key (expected to '.
|
||||||
|
'find key at "%s").',
|
||||||
|
$key_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
$options = array();
|
||||||
|
$options[] = '-o';
|
||||||
|
$options[] = 'StrictHostKeyChecking=no';
|
||||||
|
$options[] = '-o';
|
||||||
|
$options[] = 'UserKnownHostsFile=/dev/null';
|
||||||
|
|
||||||
|
// This is suppressing "added <address> to the list of known hosts"
|
||||||
|
// messages, which are confusing and irrelevant when they arise from
|
||||||
|
// proxied requests. It might also be suppressing lots of useful errors,
|
||||||
|
// of course. Ideally, we would enforce host keys eventually.
|
||||||
|
$options[] = '-o';
|
||||||
|
$options[] = 'LogLevel=quiet';
|
||||||
|
|
||||||
|
// NOTE: We prefix the command with "@username", which the far end of the
|
||||||
|
// connection will parse in order to act as the specified user. This
|
||||||
|
// behavior is only available to cluster requests signed by a trusted
|
||||||
|
// device key.
|
||||||
|
|
||||||
|
return csprintf(
|
||||||
|
'ssh %Ls -l %s -i %s -p %s %s -- %s %Ls',
|
||||||
|
$options,
|
||||||
|
$username,
|
||||||
|
$key_path,
|
||||||
|
$port,
|
||||||
|
$host,
|
||||||
|
'@'.$this->getUser()->getUsername(),
|
||||||
|
$this->getOriginalArguments());
|
||||||
|
}
|
||||||
|
|
||||||
final public function execute(PhutilArgumentParser $args) {
|
final public function execute(PhutilArgumentParser $args) {
|
||||||
$this->args = $args;
|
$this->args = $args;
|
||||||
|
|
||||||
$repository = $this->identifyRepository();
|
$repository = $this->identifyRepository();
|
||||||
$this->setRepository($repository);
|
$this->setRepository($repository);
|
||||||
|
|
||||||
// TODO: Here, we would make a proxying decision, had I implemented
|
$is_cluster_request = $this->getIsClusterRequest();
|
||||||
// proxying yet.
|
$uri = $repository->getAlmanacServiceURI(
|
||||||
|
$this->getUser(),
|
||||||
|
$is_cluster_request,
|
||||||
|
array(
|
||||||
|
'ssh',
|
||||||
|
));
|
||||||
|
|
||||||
|
if ($uri) {
|
||||||
|
$this->proxyURI = $uri;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return $this->executeRepositoryOperations();
|
return $this->executeRepositoryOperations();
|
||||||
|
|
|
@ -144,11 +144,15 @@ final class DiffusionSubversionServeSSHWorkflow
|
||||||
throw new Exception('Expected `svnserve -t`!');
|
throw new Exception('Expected `svnserve -t`!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->shouldProxy()) {
|
||||||
|
$command = $this->getProxyCommand();
|
||||||
|
} else {
|
||||||
$command = csprintf(
|
$command = csprintf(
|
||||||
'svnserve -t --tunnel-user=%s',
|
'svnserve -t --tunnel-user=%s',
|
||||||
$this->getUser()->getUsername());
|
$this->getUser()->getUsername());
|
||||||
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
}
|
||||||
|
|
||||||
|
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
||||||
$future = new ExecFuture('%C', $command);
|
$future = new ExecFuture('%C', $command);
|
||||||
|
|
||||||
$this->inProtocol = new DiffusionSubversionWireProtocol();
|
$this->inProtocol = new DiffusionSubversionWireProtocol();
|
||||||
|
|
|
@ -5,6 +5,8 @@ abstract class PhabricatorSSHWorkflow extends PhabricatorManagementWorkflow {
|
||||||
private $user;
|
private $user;
|
||||||
private $iochannel;
|
private $iochannel;
|
||||||
private $errorChannel;
|
private $errorChannel;
|
||||||
|
private $isClusterRequest;
|
||||||
|
private $originalArguments;
|
||||||
|
|
||||||
public function isExecutable() {
|
public function isExecutable() {
|
||||||
return false;
|
return false;
|
||||||
|
@ -63,4 +65,22 @@ abstract class PhabricatorSSHWorkflow extends PhabricatorManagementWorkflow {
|
||||||
->setErrorChannel($this->getErrorChannel());
|
->setErrorChannel($this->getErrorChannel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setIsClusterRequest($is_cluster_request) {
|
||||||
|
$this->isClusterRequest = $is_cluster_request;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIsClusterRequest() {
|
||||||
|
return $this->isClusterRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOriginalArguments(array $original_arguments) {
|
||||||
|
$this->originalArguments = $original_arguments;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOriginalArguments() {
|
||||||
|
return $this->originalArguments;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue