Skip to content

Commit bb49045

Browse files
author
epriestley
committed
Route some VCS connections over SSH
Summary: - Add web UI for configuring SSH hosting. - Route git reads (`git-upload-pack` over SSH). Test Plan: >>> orbital ~ $ git clone ssh://127.0.0.1/ Cloning into '127.0.0.1'... Exception: Unrecognized repository path "/". Expected a path like "/diffusion/X/". fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. >>> orbital ~ $ git clone ssh://127.0.0.1/diffusion/X/ Cloning into 'X'... Exception: No repository "X" exists! fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. >>> orbital ~ $ git clone ssh://127.0.0.1/diffusion/MT/ Cloning into 'MT'... Exception: This repository is not available over SSH. fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. >>> orbital ~ $ git clone ssh://127.0.0.1/diffusion/P/ Cloning into 'P'... Exception: TODO: Implement serve over SSH. fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. Reviewers: btrahan Reviewed By: btrahan CC: hach-que, aran Maniphest Tasks: T2230 Differential Revision: https://secure.phabricator.com/D7421
1 parent 888b383 commit bb49045

7 files changed

+183
-2
lines changed

scripts/ssh/ssh-exec.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@
6161

6262
$workflows = array(
6363
new ConduitSSHWorkflow(),
64+
65+
new DiffusionSSHGitUploadPackWorkflow(),
6466
);
6567

6668
$workflow_names = mpull($workflows, 'getName', 'getName');
@@ -81,16 +83,24 @@
8183
throw new Exception("Unable to open stdout.");
8284
}
8385

86+
$sock_stderr = fopen('php://stderr', 'w');
87+
if (!$sock_stderr) {
88+
throw new Exception("Unable to open stderr.");
89+
}
90+
8491
$socket_channel = new PhutilSocketChannel(
8592
$sock_stdin,
8693
$sock_stdout);
94+
$error_channel = new PhutilSocketChannel(null, $sock_stderr);
8795
$metrics_channel = new PhutilMetricsChannel($socket_channel);
8896
$workflow->setIOChannel($metrics_channel);
97+
$workflow->setErrorChannel($error_channel);
8998

9099
$err = $workflow->execute($original_args);
91100

92101
$metrics_channel->flush();
102+
$error_channel->flush();
93103
} catch (Exception $ex) {
94-
echo "phabricator-ssh-exec: ".$ex->getMessage()."\n";
104+
fwrite(STDERR, "phabricator-ssh-exec: ".$ex->getMessage()."\n");
95105
exit(1);
96106
}

src/__phutil_library_map__.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,9 @@
526526
'DiffusionRepositoryPath' => 'applications/diffusion/data/DiffusionRepositoryPath.php',
527527
'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php',
528528
'DiffusionRequest' => 'applications/diffusion/request/DiffusionRequest.php',
529+
'DiffusionSSHGitUploadPackWorkflow' => 'applications/diffusion/ssh/DiffusionSSHGitUploadPackWorkflow.php',
530+
'DiffusionSSHGitWorkflow' => 'applications/diffusion/ssh/DiffusionSSHGitWorkflow.php',
531+
'DiffusionSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSSHWorkflow.php',
529532
'DiffusionSetupException' => 'applications/diffusion/exception/DiffusionSetupException.php',
530533
'DiffusionStableCommitNameQuery' => 'applications/diffusion/query/stablecommitname/DiffusionStableCommitNameQuery.php',
531534
'DiffusionSvnCommitParentsQuery' => 'applications/diffusion/query/parents/DiffusionSvnCommitParentsQuery.php',
@@ -2711,6 +2714,9 @@
27112714
0 => 'DiffusionController',
27122715
1 => 'PhabricatorApplicationSearchResultsControllerInterface',
27132716
),
2717+
'DiffusionSSHGitUploadPackWorkflow' => 'DiffusionSSHGitWorkflow',
2718+
'DiffusionSSHGitWorkflow' => 'DiffusionSSHWorkflow',
2719+
'DiffusionSSHWorkflow' => 'PhabricatorSSHWorkflow',
27142720
'DiffusionSetupException' => 'AphrontUsageException',
27152721
'DiffusionStableCommitNameQuery' => 'DiffusionQuery',
27162722
'DiffusionSvnCommitParentsQuery' => 'DiffusionCommitParentsQuery',

src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ public function handleProtocols(PhabricatorRepository $repository) {
134134

135135
if ($request->isFormPost()) {
136136
$v_http_mode = $request->getStr('http');
137-
$v_ssh_mode = PhabricatorRepository::SERVE_OFF;
137+
$v_ssh_mode = $request->getStr('ssh');
138138

139139
$xactions = array();
140140
$template = id(new PhabricatorRepositoryTransaction());
@@ -176,6 +176,29 @@ public function handleProtocols(PhabricatorRepository $repository) {
176176
'writes.');
177177
}
178178

179+
$ssh_control =
180+
id(new AphrontFormRadioButtonControl())
181+
->setName('ssh')
182+
->setLabel(pht('SSH'))
183+
->setValue($v_ssh_mode)
184+
->addButton(
185+
PhabricatorRepository::SERVE_OFF,
186+
PhabricatorRepository::getProtocolAvailabilityName(
187+
PhabricatorRepository::SERVE_OFF),
188+
pht('Phabricator will not serve this repository.'))
189+
->addButton(
190+
PhabricatorRepository::SERVE_READONLY,
191+
PhabricatorRepository::getProtocolAvailabilityName(
192+
PhabricatorRepository::SERVE_READONLY),
193+
pht('Phabricator will serve a read-only copy of this repository.'))
194+
->addButton(
195+
PhabricatorRepository::SERVE_READWRITE,
196+
PhabricatorRepository::getProtocolAvailabilityName(
197+
PhabricatorRepository::SERVE_READWRITE),
198+
$rw_message,
199+
$repository->isHosted() ? null : 'disabled',
200+
$repository->isHosted() ? null : true);
201+
179202
$http_control =
180203
id(new AphrontFormRadioButtonControl())
181204
->setName('http')
@@ -205,6 +228,7 @@ public function handleProtocols(PhabricatorRepository $repository) {
205228
pht(
206229
'Phabricator can serve repositories over various protocols. You can '.
207230
'configure server protocols here.'))
231+
->appendChild($ssh_control)
208232
->appendChild($http_control)
209233
->appendChild(
210234
id(new AphrontFormSubmitControl())
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
final class DiffusionSSHGitUploadPackWorkflow
4+
extends DiffusionSSHGitWorkflow {
5+
6+
public function didConstruct() {
7+
$this->setName('git-upload-pack');
8+
$this->setArguments(
9+
array(
10+
array(
11+
'name' => 'dir',
12+
'wildcard' => true,
13+
),
14+
));
15+
}
16+
17+
public function isReadOnly() {
18+
return true;
19+
}
20+
21+
public function getRequestPath() {
22+
$args = $this->getArgs();
23+
return head($args->getArg('dir'));
24+
}
25+
26+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
abstract class DiffusionSSHGitWorkflow extends DiffusionSSHWorkflow {
4+
5+
protected function writeError($message) {
6+
// Git assumes we'll add our own newlines.
7+
return parent::writeError($message."\n");
8+
}
9+
10+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
4+
5+
private $args;
6+
7+
public function getArgs() {
8+
return $this->args;
9+
}
10+
11+
abstract protected function isReadOnly();
12+
abstract protected function getRequestPath();
13+
protected function writeError($message) {
14+
$this->getErrorChannel()->write($message);
15+
return $this;
16+
}
17+
18+
final public function execute(PhutilArgumentParser $args) {
19+
$this->args = $args;
20+
21+
try {
22+
$repository = $this->loadRepository();
23+
24+
throw new Exception("TODO: Implement serve over SSH.");
25+
26+
} catch (Exception $ex) {
27+
$this->writeError(get_class($ex).': '.$ex->getMessage());
28+
return 1;
29+
}
30+
31+
return 0;
32+
}
33+
34+
private function loadRepository() {
35+
$viewer = $this->getUser();
36+
$path = $this->getRequestPath();
37+
38+
$regex = '@^/?diffusion/(?P<callsign>[A-Z]+)(?:/|$)@';
39+
$matches = null;
40+
if (!preg_match($regex, $path, $matches)) {
41+
throw new Exception(
42+
pht(
43+
'Unrecognized repository path "%s". Expected a path like '.
44+
'"%s".',
45+
$path,
46+
"/diffusion/X/"));
47+
}
48+
49+
$callsign = $matches[1];
50+
$repository = id(new PhabricatorRepositoryQuery())
51+
->setViewer($viewer)
52+
->withCallsigns(array($callsign))
53+
->executeOne();
54+
55+
if (!$repository) {
56+
throw new Exception(
57+
pht('No repository "%s" exists!', $callsign));
58+
}
59+
60+
$is_push = !$this->isReadOnly();
61+
62+
switch ($repository->getServeOverSSH()) {
63+
case PhabricatorRepository::SERVE_READONLY:
64+
if ($is_push) {
65+
throw new Exception(
66+
pht('This repository is read-only over SSH.'));
67+
}
68+
break;
69+
case PhabricatorRepository::SERVE_READWRITE:
70+
if ($is_push) {
71+
$can_push = PhabricatorPolicyFilter::hasCapability(
72+
$viewer,
73+
$repository,
74+
DiffusionCapabilityPush::CAPABILITY);
75+
if (!$can_push) {
76+
throw new Exception(
77+
pht('You do not have permission to push to this repository.'));
78+
}
79+
}
80+
break;
81+
case PhabricatorRepository::SERVE_OFF:
82+
default:
83+
throw new Exception(
84+
pht('This repository is not available over SSH.'));
85+
}
86+
87+
return $repository;
88+
}
89+
90+
}

src/infrastructure/ssh/PhabricatorSSHWorkflow.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ abstract class PhabricatorSSHWorkflow extends PhutilArgumentWorkflow {
44

55
private $user;
66
private $iochannel;
7+
private $errorChannel;
8+
9+
public function setErrorChannel(PhutilChannel $error_channel) {
10+
$this->errorChannel = $error_channel;
11+
return $this;
12+
}
13+
14+
public function getErrorChannel() {
15+
return $this->errorChannel;
16+
}
717

818
public function setUser(PhabricatorUser $user) {
919
$this->user = $user;
@@ -38,4 +48,9 @@ public function readAllInput() {
3848
return $channel->read();
3949
}
4050

51+
public function writeIO($data) {
52+
$this->getIOChannel()->write($data);
53+
return $this;
54+
}
55+
4156
}

0 commit comments

Comments
 (0)