Page MenuHomePhabricator

D7419.diff
No OneTemporary

D7419.diff

Index: bin/ssh-auth-key
===================================================================
--- /dev/null
+++ bin/ssh-auth-key
@@ -0,0 +1 @@
+../scripts/ssh/ssh-auth-key.php
\ No newline at end of file
Index: resources/sshd/phabricator-ssh-hook.sh
===================================================================
--- /dev/null
+++ resources/sshd/phabricator-ssh-hook.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+###
+### WARNING: This feature is new and experimental. Use it at your own risk!
+###
+
+ROOT=/INSECURE/devtools/phabricator
+exec "$ROOT/bin/ssh-auth" $@
Index: resources/sshd/sshd_config.example
===================================================================
--- /dev/null
+++ resources/sshd/sshd_config.example
@@ -0,0 +1,24 @@
+###
+### WARNING: This feature is new and experimental. Use it at your own risk!
+###
+
+# You must have OpenSSHD 6.2 or newer; support for AuthorizedKeysCommand was
+# added in this version.
+
+Port 2222
+AuthorizedKeysCommand /etc/phabricator-ssh-hook.sh
+AuthorizedKeysCommandUser some-unprivileged-user
+
+# You may need to tweak these options, but mostly they just turn off everything
+# dangerous.
+
+Protocol 2
+PermitRootLogin no
+AllowAgentForwarding no
+AllowTcpForwarding no
+PrintMotd no
+PrintLastLog no
+PasswordAuthentication no
+AuthorizedKeysFile none
+
+PidFile /var/run/sshd-phabricator.pid
Index: scripts/ssh/ssh-auth-key.php
===================================================================
--- scripts/ssh/ssh-auth-key.php
+++ scripts/ssh/ssh-auth-key.php
@@ -47,7 +47,7 @@
$bin = $root.'/bin/ssh-exec';
$cmd = csprintf('%s --phabricator-ssh-user %s', $bin, $user);
// This is additional escaping for the SSH 'command="..."' string.
-$cmd = str_replace('"', '\\"', $cmd);
+$cmd = addcslashes($cmd, '"\\');
$options = array(
'command="'.$cmd.'"',
Index: scripts/ssh/ssh-auth.php
===================================================================
--- scripts/ssh/ssh-auth.php
+++ scripts/ssh/ssh-auth.php
@@ -4,58 +4,45 @@
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
-$cert = file_get_contents('php://stdin');
-
-if (!$cert) {
- exit(1);
-}
-
-$parts = preg_split('/\s+/', $cert);
-if (count($parts) < 2) {
- exit(1);
-}
-
-list($type, $body) = $parts;
-
$user_dao = new PhabricatorUser();
$ssh_dao = new PhabricatorUserSSHKey();
$conn_r = $user_dao->establishConnection('r');
-$row = queryfx_one(
+$rows = queryfx_all(
$conn_r,
- 'SELECT userName FROM %T u JOIN %T ssh ON u.phid = ssh.userPHID
- WHERE ssh.keyType = %s AND ssh.keyBody = %s',
+ 'SELECT userName, keyBody, keyType FROM %T u JOIN %T ssh
+ ON u.phid = ssh.userPHID',
$user_dao->getTableName(),
- $ssh_dao->getTableName(),
- $type,
- $body);
+ $ssh_dao->getTableName());
-if (!$row) {
- exit(1);
-}
+$bin = $root.'/bin/ssh-exec';
+foreach ($rows as $row) {
+ $user = $row['userName'];
-$user = idx($row, 'userName');
+ $cmd = csprintf('%s --phabricator-ssh-user %s', $bin, $user);
+ // This is additional escaping for the SSH 'command="..."' string.
+ $cmd = addcslashes($cmd, '"\\');
-if (!$user) {
- exit(1);
-}
+ // Strip out newlines and other nonsense from the key type and key body.
+
+ $type = $row['keyType'];
+ $type = preg_replace('@[\x00-\x20]+@', '', $type);
+
+ $key = $row['keyBody'];
+ $key = preg_replace('@[\x00-\x20]+@', '', $key);
-if (!PhabricatorUser::validateUsername($user)) {
- exit(1);
+
+ $options = array(
+ 'command="'.$cmd.'"',
+ 'no-port-forwarding',
+ 'no-X11-forwarding',
+ 'no-agent-forwarding',
+ 'no-pty',
+ );
+ $options = implode(',', $options);
+
+ $lines[] = $options.' '.$type.' '.$key."\n";
}
-$bin = $root.'/bin/ssh-exec';
-$cmd = csprintf('%s --phabricator-ssh-user %s', $bin, $user);
-// This is additional escaping for the SSH 'command="..."' string.
-$cmd = str_replace('"', '\\"', $cmd);
-
-$options = array(
- 'command="'.$cmd.'"',
- 'no-port-forwarding',
- 'no-X11-forwarding',
- 'no-agent-forwarding',
- 'no-pty',
-);
-
-echo implode(',', $options);
+echo implode('', $lines);
exit(0);
Index: scripts/ssh/ssh-exec.php
===================================================================
--- scripts/ssh/ssh-exec.php
+++ scripts/ssh/ssh-exec.php
@@ -4,29 +4,25 @@
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
-$original_command = getenv('SSH_ORIGINAL_COMMAND');
-$original_argv = id(new PhutilShellLexer())->splitArguments($original_command);
-$argv = array_merge($argv, $original_argv);
-
+// First, figure out the authenticated user.
$args = new PhutilArgumentParser($argv);
$args->setTagline('receive SSH requests');
$args->setSynopsis(<<<EOSYNOPSIS
-**ssh-exec** --phabricator-ssh-user __user__ __commmand__ [__options__]
+**ssh-exec** --phabricator-ssh-user __user__ [--ssh-command __commmand__]
Receive SSH requests.
-
EOSYNOPSIS
);
-// NOTE: Do NOT parse standard arguments. Arguments are coming from a remote
-// client over SSH, and they should not be able to execute "--xprofile",
-// "--recon", etc.
-
-$args->parsePartial(
+$args->parse(
array(
array(
'name' => 'phabricator-ssh-user',
'param' => 'username',
),
+ array(
+ 'name' => 'ssh-command',
+ 'param' => 'command',
+ ),
));
try {
@@ -46,24 +42,33 @@
throw new Exception("You have been exiled.");
}
+ if ($args->getArg('ssh-command')) {
+ $original_command = $args->getArg('ssh-command');
+ } else {
+ $original_command = getenv('SSH_ORIGINAL_COMMAND');
+ }
+
+ // Now, rebuild the original command.
+ $original_argv = id(new PhutilShellLexer())
+ ->splitArguments($original_command);
+ if (!$original_argv) {
+ throw new Exception("No interactive logins.");
+ }
+ $command = head($original_argv);
+ array_unshift($original_argv, 'phabricator-ssh-exec');
+
+ $original_args = new PhutilArgumentParser($original_argv);
+
$workflows = array(
new ConduitSSHWorkflow(),
);
- // This duplicates logic in parseWorkflows(), but allows us to raise more
- // concise/relevant exceptions when the client is a remote SSH.
- $remain = $args->getUnconsumedArgumentVector();
- if (empty($remain)) {
- throw new Exception("No interactive logins.");
- } else {
- $command = head($remain);
- $workflow_names = mpull($workflows, 'getName', 'getName');
- if (empty($workflow_names[$command])) {
- throw new Exception("Invalid command.");
- }
+ $workflow_names = mpull($workflows, 'getName', 'getName');
+ if (empty($workflow_names[$command])) {
+ throw new Exception("Invalid command.");
}
- $workflow = $args->parseWorkflows($workflows);
+ $workflow = $original_args->parseWorkflows($workflows);
$workflow->setUser($user);
$sock_stdin = fopen('php://stdin', 'r');
@@ -82,7 +87,7 @@
$metrics_channel = new PhutilMetricsChannel($socket_channel);
$workflow->setIOChannel($metrics_channel);
- $err = $workflow->execute($args);
+ $err = $workflow->execute($original_args);
$metrics_channel->flush();
} catch (Exception $ex) {
Index: src/applications/conduit/ssh/ConduitSSHWorkflow.php
===================================================================
--- src/applications/conduit/ssh/ConduitSSHWorkflow.php
+++ src/applications/conduit/ssh/ConduitSSHWorkflow.php
@@ -31,7 +31,7 @@
throw new Exception("Invalid JSON input.");
}
- $params = idx($raw_params, 'params', array());
+ $params = idx($raw_params, 'params', '[]');
$params = json_decode($params, true);
$metadata = idx($params, '__conduit__', array());
unset($params['__conduit__']);

File Metadata

Mime Type
text/plain
Expires
Sat, Jul 6, 1:23 PM (3 d, 15 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6283251
Default Alt Text
D7419.diff (7 KB)

Event Timeline