diff --git a/scripts/ssh/ssh-exec.php b/scripts/ssh/ssh-exec.php index 90544a714e..33fa75898d 100755 --- a/scripts/ssh/ssh-exec.php +++ b/scripts/ssh/ssh-exec.php @@ -1,9 +1,13 @@ #!/usr/bin/env php setTagline('receive SSH requests'); @@ -38,6 +42,12 @@ try { throw new Exception("Invalid username."); } + $ssh_log->setData( + array( + 'u' => $user->getUsername(), + 'P' => $user->getPHID(), + )); + if (!$user->isUserActivated()) { throw new Exception(pht("Your account is not activated.")); } @@ -54,6 +64,15 @@ try { if (!$original_argv) { throw new Exception("No interactive logins."); } + + $ssh_log->setData( + array( + 'C' => $original_argv[0], + 'U' => phutil_utf8_shorten( + implode(' ', array_slice($original_argv, 1)), + 128), + )); + $command = head($original_argv); array_unshift($original_argv, 'phabricator-ssh-exec'); @@ -98,12 +117,35 @@ try { $workflow->setIOChannel($metrics_channel); $workflow->setErrorChannel($error_channel); - $err = $workflow->execute($original_args); + $rethrow = null; + try { + $err = $workflow->execute($original_args); + $metrics_channel->flush(); + $error_channel->flush(); + } catch (Exception $ex) { + $rethrow = $ex; + } - $metrics_channel->flush(); - $error_channel->flush(); + // Always write this if we got as far as building a metrics channel. + $ssh_log->setData( + array( + 'i' => $metrics_channel->getBytesRead(), + 'o' => $metrics_channel->getBytesWritten(), + )); + + if ($rethrow) { + throw $ex; + } } catch (Exception $ex) { fwrite(STDERR, "phabricator-ssh-exec: ".$ex->getMessage()."\n"); - exit(1); + $err = 1; } + +$ssh_log->setData( + array( + 'c' => $err, + 'T' => (int)(1000000 * (microtime(true) - $ssh_start_time)), + )); + +exit($err); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 69ea053f5e..c029dddb6b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -996,7 +996,7 @@ phutil_register_library_map(array( 'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php', 'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php', 'PhabricatorAccessControlTestCase' => 'applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php', - 'PhabricatorAccessLog' => 'infrastructure/PhabricatorAccessLog.php', + 'PhabricatorAccessLog' => 'infrastructure/log/PhabricatorAccessLog.php', 'PhabricatorAccessLogConfigOptions' => 'applications/config/option/PhabricatorAccessLogConfigOptions.php', 'PhabricatorActionHeaderExample' => 'applications/uiexample/examples/PhabricatorActionHeaderExample.php', 'PhabricatorActionHeaderView' => 'view/layout/PhabricatorActionHeaderView.php', @@ -1811,6 +1811,7 @@ phutil_register_library_map(array( 'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php', 'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php', 'PhabricatorSQLPatchList' => 'infrastructure/storage/patch/PhabricatorSQLPatchList.php', + 'PhabricatorSSHLog' => 'infrastructure/log/PhabricatorSSHLog.php', 'PhabricatorSSHPassthruCommand' => 'infrastructure/ssh/PhabricatorSSHPassthruCommand.php', 'PhabricatorSSHWorkflow' => 'infrastructure/ssh/PhabricatorSSHWorkflow.php', 'PhabricatorSavedQuery' => 'applications/search/storage/PhabricatorSavedQuery.php', @@ -4337,6 +4338,7 @@ phutil_register_library_map(array( 'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO', 'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine', + 'PhabricatorSSHLog' => 'Phobject', 'PhabricatorSSHPassthruCommand' => 'Phobject', 'PhabricatorSSHWorkflow' => 'PhutilArgumentWorkflow', 'PhabricatorSavedQuery' => diff --git a/src/applications/config/option/PhabricatorAccessLogConfigOptions.php b/src/applications/config/option/PhabricatorAccessLogConfigOptions.php index 5503b66870..f03056b72b 100644 --- a/src/applications/config/option/PhabricatorAccessLogConfigOptions.php +++ b/src/applications/config/option/PhabricatorAccessLogConfigOptions.php @@ -4,48 +4,56 @@ final class PhabricatorAccessLogConfigOptions extends PhabricatorApplicationConfigOptions { public function getName() { - return pht("Access Log"); + return pht("Access Logs"); } public function getDescription() { - return pht("Configure the access log, which logs all requests."); + return pht("Configure the access logs, which log HTTP/SSH requests."); } public function getOptions() { - $map = array( - 'c' => pht("The HTTP response code."), - 'C' => pht("The controller which handled the request."), + $common_map = array( + 'C' => pht("The controller or workflow which handled the request."), + 'c' => pht("The HTTP response code or process exit code."), 'D' => pht("The request date."), 'e' => pht("Epoch timestamp."), 'h' => pht("The webserver's host name."), 'p' => pht("The PID of the server process."), - 'R' => pht("The HTTP referrer."), 'r' => pht("The remote IP."), 'T' => pht("The request duration, in microseconds."), - 'U' => pht("The request path."), + 'U' => pht("The request path, or request target."), + 'm' => pht("For conduit, the Conduit method which was invoked."), 'u' => pht("The logged-in username, if one is logged in."), 'P' => pht("The logged-in user PHID, if one is logged in."), - 'M' => pht("The HTTP method."), - 'm' => pht("For conduit, the Conduit method which was invoked."), + 'i' => pht("Request input, in bytes."), + 'o' => pht("Request output, in bytes."), ); - $fdesc = pht("Format for the access log. Available variables are:"); - $fdesc .= "\n\n"; - foreach ($map as $key => $desc) { - $fdesc .= " - %".$key." ".$desc."\n"; - } - $fdesc .= "\n"; - $fdesc .= pht( - "If a variable isn't available (for example, %%m appears in the file ". - "format but the request is not a Conduit request), it will be rendered ". - "as '-'"); - $fdesc .= "\n\n"; - $fdesc .= pht( - "Note that the default format is subject to change in the future, so ". - "if you rely on the log's format, specify it explicitly."); + $http_map = $common_map + array( + 'R' => pht("The HTTP referrer."), + 'M' => pht("The HTTP method."), + ); + + $ssh_map = $common_map + array( + 's' => pht("The system user."), + 'S' => pht("The system sudo user."), + ); + + $http_desc = pht( + "Format for the HTTP access log. Use {{log.access.path}} to set the ". + "path. Available variables are:"); + $http_desc .= "\n\n"; + $http_desc .= $this->renderMapHelp($http_map); + + $ssh_desc = pht( + "Format for the SSH access log. Use {{log.ssh.path}} to set the ". + "path. Available variables are:"); + $ssh_desc .= "\n\n"; + $ssh_desc .= $this->renderMapHelp($ssh_map); return array( $this->newOption('log.access.path', 'string', null) + ->setLocked(true) ->setSummary(pht("Access log location.")) ->setDescription( pht( @@ -57,19 +65,61 @@ final class PhabricatorAccessLogConfigOptions "If not set, no log will be written.")) ->addExample( null, - pht('Disable access log')) + pht('Disable access log.')) ->addExample( '/var/log/phabricator/access.log', - pht('Write access log here')), + pht('Write access log here.')), $this->newOption( 'log.access.format', // NOTE: This is 'wild' intead of 'string' so "\t" and such can be // specified. 'wild', "[%D]\t%p\t%h\t%r\t%u\t%C\t%m\t%U\t%R\t%c\t%T") + ->setLocked(true) ->setSummary(pht("Access log format.")) - ->setDescription($fdesc), + ->setDescription($http_desc), + $this->newOption('log.ssh.path', 'string', null) + ->setLocked(true) + ->setSummary(pht("SSH log location.")) + ->setDescription( + pht( + "To enable the Phabricator SSH log, specify a path. The ". + "access log can provide more detailed information about SSH ". + "access than a normal SSH log (for instance, it can show ". + "logged-in users, commands, and other application data).\n\n". + "If not set, no log will be written.")) + ->addExample( + null, + pht('Disable SSH log.')) + ->addExample( + '/var/log/phabricator/ssh.log', + pht('Write SSH log here.')), + $this->newOption( + 'log.ssh.format', + 'wild', + "[%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") + ->setLocked(true) + ->setSummary(pht("SSH log format.")) + ->setDescription($ssh_desc), ); } + private function renderMapHelp(array $map) { + $desc = ''; + foreach ($map as $key => $kdesc) { + $desc .= " - `%".$key."` ".$kdesc."\n"; + } + $desc .= "\n"; + $desc .= pht( + "If a variable isn't available (for example, %%m appears in the file ". + "format but the request is not a Conduit request), it will be rendered ". + "as '-'"); + $desc .= "\n\n"; + $desc .= pht( + "Note that the default format is subject to change in the future, so ". + "if you rely on the log's format, specify it explicitly."); + + return $desc; + } + } diff --git a/src/infrastructure/PhabricatorAccessLog.php b/src/infrastructure/log/PhabricatorAccessLog.php similarity index 100% rename from src/infrastructure/PhabricatorAccessLog.php rename to src/infrastructure/log/PhabricatorAccessLog.php diff --git a/src/infrastructure/log/PhabricatorSSHLog.php b/src/infrastructure/log/PhabricatorSSHLog.php new file mode 100644 index 0000000000..12b8001b5a --- /dev/null +++ b/src/infrastructure/log/PhabricatorSSHLog.php @@ -0,0 +1,52 @@ + date('r'), + 'h' => php_uname('n'), + 'p' => getmypid(), + 'e' => time(), + ); + + $sudo_user = PhabricatorEnv::getEnvConfig('phd.user'); + if (strlen($sudo_user)) { + $data['S'] = $sudo_user; + } + + if (function_exists('posix_geteuid')) { + $system_uid = posix_geteuid(); + $system_info = posix_getpwuid($system_uid); + $data['s'] = idx($system_info, 'name'); + } + + $client = getenv('SSH_CLIENT'); + if (strlen($client)) { + $remote_address = head(explode(' ', $client)); + $data['r'] = $remote_address; + } + + $log = id(new PhutilDeferredLog($path, $format)) + ->setFailQuietly(true) + ->setData($data); + + self::$log = $log; + } + + return self::$log; + } + +}