@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+176 -30
+46 -4
scripts/ssh/ssh-exec.php
··· 1 1 #!/usr/bin/env php 2 2 <?php 3 3 4 + $ssh_start_time = microtime(true); 5 + 4 6 $root = dirname(dirname(dirname(__FILE__))); 5 7 require_once $root.'/scripts/__init_script__.php'; 8 + 9 + $ssh_log = PhabricatorSSHLog::getLog(); 6 10 7 11 // First, figure out the authenticated user. 8 12 $args = new PhutilArgumentParser($argv); ··· 38 42 throw new Exception("Invalid username."); 39 43 } 40 44 45 + $ssh_log->setData( 46 + array( 47 + 'u' => $user->getUsername(), 48 + 'P' => $user->getPHID(), 49 + )); 50 + 41 51 if (!$user->isUserActivated()) { 42 52 throw new Exception(pht("Your account is not activated.")); 43 53 } ··· 54 64 if (!$original_argv) { 55 65 throw new Exception("No interactive logins."); 56 66 } 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 + 57 76 $command = head($original_argv); 58 77 array_unshift($original_argv, 'phabricator-ssh-exec'); 59 78 ··· 98 117 $workflow->setIOChannel($metrics_channel); 99 118 $workflow->setErrorChannel($error_channel); 100 119 101 - $err = $workflow->execute($original_args); 120 + $rethrow = null; 121 + try { 122 + $err = $workflow->execute($original_args); 102 123 124 + $metrics_channel->flush(); 125 + $error_channel->flush(); 126 + } catch (Exception $ex) { 127 + $rethrow = $ex; 128 + } 103 129 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 + } 106 140 } catch (Exception $ex) { 107 141 fwrite(STDERR, "phabricator-ssh-exec: ".$ex->getMessage()."\n"); 108 - exit(1); 142 + $err = 1; 109 143 } 144 + 145 + $ssh_log->setData( 146 + array( 147 + 'c' => $err, 148 + 'T' => (int)(1000000 * (microtime(true) - $ssh_start_time)), 149 + )); 150 + 151 + exit($err);
+3 -1
src/__phutil_library_map__.php
··· 996 996 'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php', 997 997 'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php', 998 998 'PhabricatorAccessControlTestCase' => 'applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php', 999 - 'PhabricatorAccessLog' => 'infrastructure/PhabricatorAccessLog.php', 999 + 'PhabricatorAccessLog' => 'infrastructure/log/PhabricatorAccessLog.php', 1000 1000 'PhabricatorAccessLogConfigOptions' => 'applications/config/option/PhabricatorAccessLogConfigOptions.php', 1001 1001 'PhabricatorActionHeaderExample' => 'applications/uiexample/examples/PhabricatorActionHeaderExample.php', 1002 1002 'PhabricatorActionHeaderView' => 'view/layout/PhabricatorActionHeaderView.php', ··· 1811 1811 'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php', 1812 1812 'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php', 1813 1813 'PhabricatorSQLPatchList' => 'infrastructure/storage/patch/PhabricatorSQLPatchList.php', 1814 + 'PhabricatorSSHLog' => 'infrastructure/log/PhabricatorSSHLog.php', 1814 1815 'PhabricatorSSHPassthruCommand' => 'infrastructure/ssh/PhabricatorSSHPassthruCommand.php', 1815 1816 'PhabricatorSSHWorkflow' => 'infrastructure/ssh/PhabricatorSSHWorkflow.php', 1816 1817 'PhabricatorSavedQuery' => 'applications/search/storage/PhabricatorSavedQuery.php', ··· 4337 4338 'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 4338 4339 'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO', 4339 4340 'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine', 4341 + 'PhabricatorSSHLog' => 'Phobject', 4340 4342 'PhabricatorSSHPassthruCommand' => 'Phobject', 4341 4343 'PhabricatorSSHWorkflow' => 'PhutilArgumentWorkflow', 4342 4344 'PhabricatorSavedQuery' =>
+75 -25
src/applications/config/option/PhabricatorAccessLogConfigOptions.php
··· 4 4 extends PhabricatorApplicationConfigOptions { 5 5 6 6 public function getName() { 7 - return pht("Access Log"); 7 + return pht("Access Logs"); 8 8 } 9 9 10 10 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."); 12 12 } 13 13 14 14 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."), 18 18 'D' => pht("The request date."), 19 19 'e' => pht("Epoch timestamp."), 20 20 'h' => pht("The webserver's host name."), 21 21 'p' => pht("The PID of the server process."), 22 - 'R' => pht("The HTTP referrer."), 23 22 'r' => pht("The remote IP."), 24 23 '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."), 26 26 'u' => pht("The logged-in username, if one is logged in."), 27 27 '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."), 28 34 'M' => pht("The HTTP method."), 29 - 'm' => pht("For conduit, the Conduit method which was invoked."), 30 35 ); 31 36 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); 46 53 47 54 return array( 48 55 $this->newOption('log.access.path', 'string', null) 56 + ->setLocked(true) 49 57 ->setSummary(pht("Access log location.")) 50 58 ->setDescription( 51 59 pht( ··· 57 65 "If not set, no log will be written.")) 58 66 ->addExample( 59 67 null, 60 - pht('Disable access log')) 68 + pht('Disable access log.')) 61 69 ->addExample( 62 70 '/var/log/phabricator/access.log', 63 - pht('Write access log here')), 71 + pht('Write access log here.')), 64 72 $this->newOption( 65 73 'log.access.format', 66 74 // NOTE: This is 'wild' intead of 'string' so "\t" and such can be 67 75 // specified. 68 76 'wild', 69 77 "[%D]\t%p\t%h\t%r\t%u\t%C\t%m\t%U\t%R\t%c\t%T") 78 + ->setLocked(true) 70 79 ->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), 72 104 ); 105 + } 106 + 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; 73 123 } 74 124 75 125 }
src/infrastructure/PhabricatorAccessLog.php src/infrastructure/log/PhabricatorAccessLog.php
+52
src/infrastructure/log/PhabricatorSSHLog.php
··· 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 + }