Skip to content

Commit 5ca8458

Browse files
author
epriestley
committed
Add an SSH access log
Summary: Ref T4107. Ref T4189. This implements an SSH access log, similar to the HTTP access log. Test Plan: [Thu, 05 Dec 2013 13:45:41 -0800] 77841 orbital ::1 dweller epriestley epriestley git-receive-pack /diffusion/POEMS/ 0 324765 402 232 [Thu, 05 Dec 2013 13:45:48 -0800] 77860 orbital ::1 dweller epriestley epriestley git-receive-pack /diffusion/POEMS/ 0 325634 402 232 Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T4107, T4189 Differential Revision: https://secure.phabricator.com/D7719
1 parent 39b3840 commit 5ca8458

File tree

5 files changed

+176
-30
lines changed

5 files changed

+176
-30
lines changed

scripts/ssh/ssh-exec.php

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
#!/usr/bin/env php
22
<?php
33

4+
$ssh_start_time = microtime(true);
5+
46
$root = dirname(dirname(dirname(__FILE__)));
57
require_once $root.'/scripts/__init_script__.php';
68

9+
$ssh_log = PhabricatorSSHLog::getLog();
10+
711
// First, figure out the authenticated user.
812
$args = new PhutilArgumentParser($argv);
913
$args->setTagline('receive SSH requests');
@@ -38,6 +42,12 @@
3842
throw new Exception("Invalid username.");
3943
}
4044

45+
$ssh_log->setData(
46+
array(
47+
'u' => $user->getUsername(),
48+
'P' => $user->getPHID(),
49+
));
50+
4151
if (!$user->isUserActivated()) {
4252
throw new Exception(pht("Your account is not activated."));
4353
}
@@ -54,6 +64,15 @@
5464
if (!$original_argv) {
5565
throw new Exception("No interactive logins.");
5666
}
67+
68+
$ssh_log->setData(
69+
array(
70+
'C' => $original_argv[0],
71+
'U' => phutil_utf8_shorten(
72+
implode(' ', array_slice($original_argv, 1)),
73+
128),
74+
));
75+
5776
$command = head($original_argv);
5877
array_unshift($original_argv, 'phabricator-ssh-exec');
5978

@@ -98,12 +117,35 @@
98117
$workflow->setIOChannel($metrics_channel);
99118
$workflow->setErrorChannel($error_channel);
100119

101-
$err = $workflow->execute($original_args);
120+
$rethrow = null;
121+
try {
122+
$err = $workflow->execute($original_args);
102123

124+
$metrics_channel->flush();
125+
$error_channel->flush();
126+
} catch (Exception $ex) {
127+
$rethrow = $ex;
128+
}
103129

104-
$metrics_channel->flush();
105-
$error_channel->flush();
130+
// Always write this if we got as far as building a metrics channel.
131+
$ssh_log->setData(
132+
array(
133+
'i' => $metrics_channel->getBytesRead(),
134+
'o' => $metrics_channel->getBytesWritten(),
135+
));
136+
137+
if ($rethrow) {
138+
throw $ex;
139+
}
106140
} catch (Exception $ex) {
107141
fwrite(STDERR, "phabricator-ssh-exec: ".$ex->getMessage()."\n");
108-
exit(1);
142+
$err = 1;
109143
}
144+
145+
$ssh_log->setData(
146+
array(
147+
'c' => $err,
148+
'T' => (int)(1000000 * (microtime(true) - $ssh_start_time)),
149+
));
150+
151+
exit($err);

src/__phutil_library_map__.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -996,7 +996,7 @@
996996
'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php',
997997
'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php',
998998
'PhabricatorAccessControlTestCase' => 'applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php',
999-
'PhabricatorAccessLog' => 'infrastructure/PhabricatorAccessLog.php',
999+
'PhabricatorAccessLog' => 'infrastructure/log/PhabricatorAccessLog.php',
10001000
'PhabricatorAccessLogConfigOptions' => 'applications/config/option/PhabricatorAccessLogConfigOptions.php',
10011001
'PhabricatorActionHeaderExample' => 'applications/uiexample/examples/PhabricatorActionHeaderExample.php',
10021002
'PhabricatorActionHeaderView' => 'view/layout/PhabricatorActionHeaderView.php',
@@ -1811,6 +1811,7 @@
18111811
'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php',
18121812
'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php',
18131813
'PhabricatorSQLPatchList' => 'infrastructure/storage/patch/PhabricatorSQLPatchList.php',
1814+
'PhabricatorSSHLog' => 'infrastructure/log/PhabricatorSSHLog.php',
18141815
'PhabricatorSSHPassthruCommand' => 'infrastructure/ssh/PhabricatorSSHPassthruCommand.php',
18151816
'PhabricatorSSHWorkflow' => 'infrastructure/ssh/PhabricatorSSHWorkflow.php',
18161817
'PhabricatorSavedQuery' => 'applications/search/storage/PhabricatorSavedQuery.php',
@@ -4337,6 +4338,7 @@
43374338
'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
43384339
'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO',
43394340
'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine',
4341+
'PhabricatorSSHLog' => 'Phobject',
43404342
'PhabricatorSSHPassthruCommand' => 'Phobject',
43414343
'PhabricatorSSHWorkflow' => 'PhutilArgumentWorkflow',
43424344
'PhabricatorSavedQuery' =>

src/applications/config/option/PhabricatorAccessLogConfigOptions.php

Lines changed: 75 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,56 @@ final class PhabricatorAccessLogConfigOptions
44
extends PhabricatorApplicationConfigOptions {
55

66
public function getName() {
7-
return pht("Access Log");
7+
return pht("Access Logs");
88
}
99

1010
public function getDescription() {
11-
return pht("Configure the access log, which logs all requests.");
11+
return pht("Configure the access logs, which log HTTP/SSH requests.");
1212
}
1313

1414
public function getOptions() {
15-
$map = array(
16-
'c' => pht("The HTTP response code."),
17-
'C' => pht("The controller which handled the request."),
15+
$common_map = array(
16+
'C' => pht("The controller or workflow which handled the request."),
17+
'c' => pht("The HTTP response code or process exit code."),
1818
'D' => pht("The request date."),
1919
'e' => pht("Epoch timestamp."),
2020
'h' => pht("The webserver's host name."),
2121
'p' => pht("The PID of the server process."),
22-
'R' => pht("The HTTP referrer."),
2322
'r' => pht("The remote IP."),
2423
'T' => pht("The request duration, in microseconds."),
25-
'U' => pht("The request path."),
24+
'U' => pht("The request path, or request target."),
25+
'm' => pht("For conduit, the Conduit method which was invoked."),
2626
'u' => pht("The logged-in username, if one is logged in."),
2727
'P' => pht("The logged-in user PHID, if one is logged in."),
28+
'i' => pht("Request input, in bytes."),
29+
'o' => pht("Request output, in bytes."),
30+
);
31+
32+
$http_map = $common_map + array(
33+
'R' => pht("The HTTP referrer."),
2834
'M' => pht("The HTTP method."),
29-
'm' => pht("For conduit, the Conduit method which was invoked."),
3035
);
3136

32-
$fdesc = pht("Format for the access log. Available variables are:");
33-
$fdesc .= "\n\n";
34-
foreach ($map as $key => $desc) {
35-
$fdesc .= " - %".$key." ".$desc."\n";
36-
}
37-
$fdesc .= "\n";
38-
$fdesc .= pht(
39-
"If a variable isn't available (for example, %%m appears in the file ".
40-
"format but the request is not a Conduit request), it will be rendered ".
41-
"as '-'");
42-
$fdesc .= "\n\n";
43-
$fdesc .= pht(
44-
"Note that the default format is subject to change in the future, so ".
45-
"if you rely on the log's format, specify it explicitly.");
37+
$ssh_map = $common_map + array(
38+
's' => pht("The system user."),
39+
'S' => pht("The system sudo user."),
40+
);
41+
42+
$http_desc = pht(
43+
"Format for the HTTP access log. Use {{log.access.path}} to set the ".
44+
"path. Available variables are:");
45+
$http_desc .= "\n\n";
46+
$http_desc .= $this->renderMapHelp($http_map);
47+
48+
$ssh_desc = pht(
49+
"Format for the SSH access log. Use {{log.ssh.path}} to set the ".
50+
"path. Available variables are:");
51+
$ssh_desc .= "\n\n";
52+
$ssh_desc .= $this->renderMapHelp($ssh_map);
4653

4754
return array(
4855
$this->newOption('log.access.path', 'string', null)
56+
->setLocked(true)
4957
->setSummary(pht("Access log location."))
5058
->setDescription(
5159
pht(
@@ -57,19 +65,61 @@ public function getOptions() {
5765
"If not set, no log will be written."))
5866
->addExample(
5967
null,
60-
pht('Disable access log'))
68+
pht('Disable access log.'))
6169
->addExample(
6270
'/var/log/phabricator/access.log',
63-
pht('Write access log here')),
71+
pht('Write access log here.')),
6472
$this->newOption(
6573
'log.access.format',
6674
// NOTE: This is 'wild' intead of 'string' so "\t" and such can be
6775
// specified.
6876
'wild',
6977
"[%D]\t%p\t%h\t%r\t%u\t%C\t%m\t%U\t%R\t%c\t%T")
78+
->setLocked(true)
7079
->setSummary(pht("Access log format."))
71-
->setDescription($fdesc),
80+
->setDescription($http_desc),
81+
$this->newOption('log.ssh.path', 'string', null)
82+
->setLocked(true)
83+
->setSummary(pht("SSH log location."))
84+
->setDescription(
85+
pht(
86+
"To enable the Phabricator SSH log, specify a path. The ".
87+
"access log can provide more detailed information about SSH ".
88+
"access than a normal SSH log (for instance, it can show ".
89+
"logged-in users, commands, and other application data).\n\n".
90+
"If not set, no log will be written."))
91+
->addExample(
92+
null,
93+
pht('Disable SSH log.'))
94+
->addExample(
95+
'/var/log/phabricator/ssh.log',
96+
pht('Write SSH log here.')),
97+
$this->newOption(
98+
'log.ssh.format',
99+
'wild',
100+
"[%D]\t%p\t%h\t%r\t%s\t%S\t%u\t%C\t%U\t%c\t%T\t%i\t%o")
101+
->setLocked(true)
102+
->setSummary(pht("SSH log format."))
103+
->setDescription($ssh_desc),
72104
);
73105
}
74106

107+
private function renderMapHelp(array $map) {
108+
$desc = '';
109+
foreach ($map as $key => $kdesc) {
110+
$desc .= " - `%".$key."` ".$kdesc."\n";
111+
}
112+
$desc .= "\n";
113+
$desc .= pht(
114+
"If a variable isn't available (for example, %%m appears in the file ".
115+
"format but the request is not a Conduit request), it will be rendered ".
116+
"as '-'");
117+
$desc .= "\n\n";
118+
$desc .= pht(
119+
"Note that the default format is subject to change in the future, so ".
120+
"if you rely on the log's format, specify it explicitly.");
121+
122+
return $desc;
123+
}
124+
75125
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
final class PhabricatorSSHLog extends Phobject {
4+
5+
static $log;
6+
7+
public static function getLog() {
8+
if (!self::$log) {
9+
$path = PhabricatorEnv::getEnvConfig('log.ssh.path');
10+
$format = PhabricatorEnv::getEnvConfig('log.ssh.format');
11+
$format = nonempty(
12+
$format,
13+
"[%D]\t%p\t%h\t%r\t%s\t%S\t%u\t%C\t%U\t%c\t%T\t%i\t%o");
14+
15+
// NOTE: Path may be null. We still create the log, it just won't write
16+
// anywhere.
17+
18+
$data = array(
19+
'D' => date('r'),
20+
'h' => php_uname('n'),
21+
'p' => getmypid(),
22+
'e' => time(),
23+
);
24+
25+
$sudo_user = PhabricatorEnv::getEnvConfig('phd.user');
26+
if (strlen($sudo_user)) {
27+
$data['S'] = $sudo_user;
28+
}
29+
30+
if (function_exists('posix_geteuid')) {
31+
$system_uid = posix_geteuid();
32+
$system_info = posix_getpwuid($system_uid);
33+
$data['s'] = idx($system_info, 'name');
34+
}
35+
36+
$client = getenv('SSH_CLIENT');
37+
if (strlen($client)) {
38+
$remote_address = head(explode(' ', $client));
39+
$data['r'] = $remote_address;
40+
}
41+
42+
$log = id(new PhutilDeferredLog($path, $format))
43+
->setFailQuietly(true)
44+
->setData($data);
45+
46+
self::$log = $log;
47+
}
48+
49+
return self::$log;
50+
}
51+
52+
}

0 commit comments

Comments
 (0)