Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F89181
D7419.diff
All Users
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
7 KB
Referenced Files
None
Subscribers
None
D7419.diff
View Options
diff --git a/bin/ssh-auth-key b/bin/ssh-auth-key
new file mode 120000
--- /dev/null
+++ b/bin/ssh-auth-key
@@ -0,0 +1 @@
+../scripts/ssh/ssh-auth-key.php
\ No newline at end of file
diff --git a/resources/sshd/phabricator-ssh-hook.sh b/resources/sshd/phabricator-ssh-hook.sh
new file mode 100755
--- /dev/null
+++ b/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" $@
diff --git a/resources/sshd/sshd_config.example b/resources/sshd/sshd_config.example
new file mode 100644
--- /dev/null
+++ b/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
diff --git a/scripts/ssh/ssh-auth.php b/scripts/ssh/ssh-auth-key.php
copy from scripts/ssh/ssh-auth.php
copy to scripts/ssh/ssh-auth-key.php
--- a/scripts/ssh/ssh-auth.php
+++ b/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.'"',
diff --git a/scripts/ssh/ssh-auth.php b/scripts/ssh/ssh-auth.php
--- a/scripts/ssh/ssh-auth.php
+++ b/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);
diff --git a/scripts/ssh/ssh-exec.php b/scripts/ssh/ssh-exec.php
--- a/scripts/ssh/ssh-exec.php
+++ b/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) {
diff --git a/src/applications/conduit/ssh/ConduitSSHWorkflow.php b/src/applications/conduit/ssh/ConduitSSHWorkflow.php
--- a/src/applications/conduit/ssh/ConduitSSHWorkflow.php
+++ b/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
Details
Attached
Mime Type
text/x-diff
Storage Engine
amazon-s3
Storage Format
Raw Data
Storage Handle
phabricator/bl/ou/kbma2qg7rqxxianm
Default Alt Text
D7419.diff (7 KB)
Attached To
Mode
D7419: Prepare to route VCS connections through SSH
Attached
Detach File
Event Timeline
Log In to Comment