Commit f6defce8 authored by Marcin Haba's avatar Marcin Haba
Browse files

baculum: Improve logging and add audit log

parent 34718ec5
......@@ -106,11 +106,8 @@ class BaculaConfig extends ConfigFileModule {
$error = is_array($result['output']) ? implode('', $result['output']) : $result['output'];
$emsg = "ERROR [$component_type] $error";
$this->getModule('logging')->log(
__FUNCTION__,
$emsg,
Logging::CATEGORY_APPLICATION,
__FILE__,
__LINE__
$emsg
);
}
return $ret;
......
......@@ -384,11 +384,8 @@ class BaculaSetting extends APIModule {
} else {
// It shouldn't happen.
$this->getModule('logging')->log(
__FUNCTION__,
"Attemp to update resource with different resource types.",
Logging::CATEGORY_APPLICATION,
__FILE__,
__LINE__
'Attemp to update resource with different resource types.'
);
$resource = $resource_orig;
}
......@@ -497,11 +494,8 @@ class BaculaSetting extends APIModule {
} else {
$emsg = sprintf("Attemp to format a directive value with not supported value type '%s'.", gettype($value));
$this->getModule('logging')->log(
__FUNCTION__,
$emsg,
Logging::CATEGORY_APPLICATION,
__FILE__,
__LINE__
$emsg
);
}
return $directive_value;
......
......@@ -212,14 +212,7 @@ abstract class BaculumAPIServer extends TPage {
break;
}
}
} catch(TException $e) {
$this->getModule('logging')->log(
__FUNCTION__,
"Method: {$_SERVER['REQUEST_METHOD']} $e",
Logging::CATEGORY_APPLICATION,
__FILE__,
__LINE__
);
} catch (TException $e) {
if ($e instanceof BAPIException) {
$this->output = $e->getErrorMessage();
$this->error = $e->getErrorCode();
......@@ -261,6 +254,8 @@ abstract class BaculumAPIServer extends TPage {
} else {
$json = json_encode($output);
}
$out = json_encode($output, JSON_PRETTY_PRINT);
$this->audit($out);
return $json;
}
......@@ -285,6 +280,27 @@ abstract class BaculumAPIServer extends TPage {
echo $this->getOutput();
}
/**
* Write each request output to audit log.
* This method is dedicated for logging requests to API.
*
* @param string $output output string
*/
private function audit($output) {
$username = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : '-';
$msg = sprintf(
"[%s] %s %s\n%s",
$_SERVER['REQUEST_METHOD'],
$username,
$this->Request->getRequestUri(),
$output
);
$this->getModule('logging')->log(
Logging::CATEGORY_AUDIT,
$msg
);
}
/**
* Shortcut method for getting application modules instances by
* module name.
......
......@@ -122,11 +122,8 @@ class BasicConfig extends ConfigFileModule {
$is_valid = false;
$emsg = 'Invalid Basic user config. Missing ' . $this->required_options[$i] . ' option.';
$this->getModule('logging')->log(
__FUNCTION__,
$emsg,
Logging::CATEGORY_APPLICATION,
__FILE__,
__LINE__
$emsg
);
break;
}
......
......@@ -202,6 +202,7 @@ class Bconsole extends APIModule {
private function execCommand($director, array $command, $ptype = null) {
$cmd = '';
$result = null;
$output = [];
if(!is_null($director) && $this->isValidDirector($director) === false) {
throw new BConsoleException(
BconsoleError::MSG_ERROR_INVALID_DIRECTOR,
......@@ -215,7 +216,7 @@ class Bconsole extends APIModule {
$cmd = $this->getCommand($pattern, $sudo, $dir, $bconsole_command);
exec($cmd['cmd'], $output, $exitcode);
if($exitcode != 0) {
$emsg = ' Output=>' . implode("\n", $output) . ', Exitcode=>' . $exitcode;
$emsg = ' Output=>' . implode(PHP_EOL, $output) . ', Exitcode=>' . $exitcode;
throw new BConsoleException(
BconsoleError::MSG_ERROR_BCONSOLE_CONNECTION_PROBLEM . $emsg,
BconsoleError::ERROR_BCONSOLE_CONNECTION_PROBLEM
......@@ -232,11 +233,8 @@ class Bconsole extends APIModule {
}
}
$this->Application->getModule('logging')->log(
$cmd['cmd'],
$output,
Logging::CATEGORY_EXECUTE,
__FILE__,
__LINE__
Logging::prepareOutput($cmd['cmd'], $output)
);
return $result;
......
......@@ -294,11 +294,8 @@ class ChangerCommand extends APIModule {
public function execCommand($cmd, $ptype = null) {
exec($cmd['cmd'], $output, $exitcode);
$this->getModule('logging')->log(
$cmd['cmd'],
$output,
Logging::CATEGORY_EXECUTE,
__FILE__,
__LINE__
Logging::prepareOutput($cmd['cmd'], $output)
);
if ($ptype === self::PTYPE_BG_CMD) {
$output = [
......
......@@ -113,7 +113,10 @@ class ComponentActions extends APIModule {
$cmd_pattern = $this->getCmdPattern();
$cmd = sprintf($cmd_pattern, $sudo, $bin);
exec($cmd, $output, $exitcode);
$this->getModule('logging')->log($cmd, $output, Logging::CATEGORY_EXECUTE, __FILE__, __LINE__);
$this->getModule('logging')->log(
Logging::CATEGORY_EXECUTE,
Logging::prepareOutput($cmd, $output)
);
$result = $this->prepareResult($output, $exitcode);
return $result;
}
......
......@@ -77,11 +77,8 @@ class Database extends APIModule {
$logmsg = 'DBParams=%s, Connection=%s, TablesFormat=%s';
$msg = sprintf($logmsg, print_r($db_params, true), var_export($is_connection, true), var_export($tables_format, true));
$this->getModule('logging')->log(
__FUNCTION__,
$msg,
Logging::CATEGORY_APPLICATION,
__FILE__,
__LINE__
$msg
);
return $is_connection;
}
......
......@@ -49,7 +49,10 @@ class JSONTools extends APIModule
$output_txt = implode('', $output);
$out = json_decode($output_txt, true);
if (!is_array($out)) {
$this->getModule('logging')->log('Parse output', $output_txt, Logging::CATEGORY_EXTERNAL, __FILE__, __LINE__);
$this->getModule('logging')->log(
Logging::CATEGORY_EXTERNAL,
"Parse output: $output_txt"
);
$out = null;
}
return $out;
......@@ -113,7 +116,10 @@ class JSONTools extends APIModule
}
$cmd = sprintf($cmd_pattern, $sudo, $bin, $cfg, $options);
exec($cmd, $output, $exitcode);
$this->getModule('logging')->log($cmd, $output, Logging::CATEGORY_EXECUTE, __FILE__, __LINE__);
$this->getModule('logging')->log(
Logging::CATEGORY_EXECUTE,
Logging::prepareOutput($cmd, $output)
);
if (!empty($config)) {
unlink($cfg);
if ($exitcode === 0) {
......
......@@ -120,11 +120,8 @@ class OAuth2Config extends ConfigFileModule {
$is_valid = false;
$emsg = 'Invalid OAuth2 config. Missing ' . $this->required_options[$i] . ' option.';
$this->getModule('logging')->log(
__FUNCTION__,
$emsg,
Logging::CATEGORY_APPLICATION,
__FILE__,
__LINE__
$emsg
);
break;
}
......
......@@ -40,7 +40,8 @@
<module id="device_config" class="Baculum\API\Modules\DeviceConfig" />
<!-- logging modules -->
<module id="log" class="System\Util\TLogRouter">
<route class="TFileLogRoute" Categories="Execute, External, Application, General, Security" LogPath="Baculum\API\Logs" LogFile="baculum-api.log" MaxFileSize="1000" MaxLogFiles="5" />
<route class="Baculum\Common\Modules\BFileLogRoute" Categories="Execute, External, Application, General, Security" LogPath="Baculum\API\Logs" LogFile="baculum-api-debug.log" MaxFileSize="10000" MaxLogFiles="5" />
<route class="Baculum\Common\Modules\BFileLogRoute" Categories="Audit" LogPath="Baculum\API\Logs" LogFile="baculum-api.log" MaxFileSize="10000" MaxLogFiles="5" />
</module>
<!-- component status modules -->
<module id="status_dir" class="Baculum\API\Modules\StatusDirector" />
......
......@@ -10,5 +10,10 @@
<module id="oauth2_token" class="Baculum\API\Modules\OAuth2\TokenManager" />
<!-- config modules -->
<module id="api_config" class="Baculum\API\Modules\APIConfig" />
<!-- logging modules -->
<module id="log" class="System\Util\TLogRouter">
<route class="Baculum\Common\Modules\BFileLogRoute" Categories="Execute, External, Application, General, Security" LogPath="Baculum\API\Logs" LogFile="baculum-api-debug.log" MaxFileSize="10000" MaxLogFiles="5" />
<route class="Baculum\Common\Modules\BFileLogRoute" Categories="Audit" LogPath="Baculum\API\Logs" LogFile="baculum-api.log" MaxFileSize="10000" MaxLogFiles="5" />
</module>
</modules>
</configuration>
......@@ -18,7 +18,8 @@
</module>
<!-- logging modules -->
<module id="log" class="System\Util\TLogRouter">
<route class="TFileLogRoute" Categories="Execute, External, Application, General, Security" LogPath="Baculum\API\Logs" LogFile="baculum-api.log" MaxFileSize="1000" MaxLogFiles="5" />
<route class="Baculum\Common\Modules\BFileLogRoute" Categories="Execute, External, Application, General, Security" LogPath="Baculum\API\Logs" LogFile="baculum-api-debug.log" MaxFileSize="10000" MaxLogFiles="5" />
<route class="Baculum\Common\Modules\BFileLogRoute" Categories="Audit" LogPath="Baculum\API\Logs" LogFile="baculum-api.log" MaxFileSize="10000" MaxLogFiles="5" />
</module>
<!-- auth modules -->
<module id="basic_apiuser" class="Baculum\API\Modules\BasicAPIUserConfig" />
......
<?php
/*
* Bacula(R) - The Network Backup Solution
* Baculum - Bacula web interface
*
* Copyright (C) 2013-2022 Kern Sibbald
*
* The main author of Baculum is Marcin Haba.
* The original author of Bacula is Kern Sibbald, with contributions
* from many others, a complete list can be found in the file AUTHORS.
*
* You may use this file and others of this release according to the
* license defined in the LICENSE file, which includes the Affero General
* Public License, v3.0 ("AGPLv3") and some additional permissions and
* terms pursuant to its AGPLv3 Section 7.
*
* This notice must be preserved when any source code is
* conveyed and/or propagated.
*
* Bacula(R) is a registered trademark of Kern Sibbald.
*/
namespace Baculum\Common\Modules;
use Prado\Util\TFileLogRoute;
/**
* File log route class.
*
* @author Marcin Haba <marcin.haba@bacula.pl>
* @category Module
* @package Baculum Common
*/
class BFileLogRoute extends TFileLogRoute {
protected function formatLogMessage($message, $level, $category, $time) {
$t = date('Y-m-d H:i:s');
$c = sprintf('[%s]', $category);
return join(
' ',
[$t, $c, $message]
);
}
}
......@@ -163,11 +163,8 @@ class ConfigIni extends CommonModule implements IConfigFormat {
}
$this->getModule('logging')->log(
__FUNCTION__,
$emsg,
Logging::CATEGORY_APPLICATION,
__FILE__,
__LINE__
$emsg
);
}
return $valid;
......
......@@ -252,11 +252,8 @@ class Ldap extends CommonModule {
);
$this->error = $emsg;
$this->getModule('logging')->log(
__FUNCTION__,
$emsg,
Logging::CATEGORY_EXTERNAL,
__FILE__,
__LINE__
$emsg
);
}
......
......@@ -23,9 +23,10 @@
namespace Baculum\Common\Modules;
use Prado\Prado;
use Prado\Util\TLogger;
/**
* Logger class.
* Main logger class.
*
* @author Marcin Haba <marcin.haba@bacula.pl>
* @category Module
......@@ -33,51 +34,97 @@ use Prado\Prado;
*/
class Logging extends CommonModule {
/*
* Stores debug enable state.
*
* @var bool
*/
public static $debug_enabled = false;
/**
* Log categories.
*/
const CATEGORY_EXECUTE = 'Execute';
const CATEGORY_EXTERNAL = 'External';
const CATEGORY_APPLICATION = 'Application';
const CATEGORY_GENERAL = 'General';
const CATEGORY_SECURITY = 'Security';
const CATEGORY_AUDIT = 'Audit';
private function getLogCategories() {
$categories = array(
self::CATEGORY_EXECUTE,
self::CATEGORY_EXTERNAL,
self::CATEGORY_APPLICATION,
self::CATEGORY_GENERAL,
self::CATEGORY_SECURITY
);
return $categories;
}
public function log($cmd, $output, $category, $file, $line) {
if(self::$debug_enabled !== true) {
/**
* Main log method used to log message.
*
* @param string $category log category
*/
public function log($category, $message) {
if (!$this->isEnabled($category)) {
return;
}
$current_mode = $this->Application->getMode();
$this->prepareMessage($category, $message);
Prado::log($message, TLogger::INFO, $category);
}
// switch application to debug mode
$this->Application->setMode('Debug');
/**
* Check if log is enabled.
*
* @param string $category log category
* @return bool true if log is enabled, otherwise false
*/
private function isEnabled($category) {
$is_enabled = false;
if (self::$debug_enabled === true || $category === self::CATEGORY_AUDIT) {
// NOTE: Audit log is written always, it is not possible to disable it
$is_enabled = true;
}
return $is_enabled;
}
if(!in_array($category, $this->getLogCategories())) {
$category = self::CATEGORY_SECURITY;
/**
* Prepare log to send to log manager.
*
* @param string $category log category
* @param array|string|object &$message log message reference
*/
private function prepareMessage($category, &$message) {
if (is_object($message) || is_array($message)) {
// make a message as string if needed
$message = print_r($message, true);
}
if (self::$debug_enabled === true && $category !== self::CATEGORY_AUDIT) {
// If debug enabled, add file and line to log message
$f = '';
$trace = debug_backtrace();
if (isset($trace[1]['file']) && isset($trace[1]['line'])) {
$f = sprintf(
'%s:%s:',
basename($trace[1]['file']),
$trace[1]['line']
);
}
$message = $f . $message;
}
$message .= PHP_EOL . PHP_EOL;
}
$log = sprintf(
'Command=%s, Output=%s, File=%s, Line=%d',
/**
* Helper method for preparing logs that come from executing programs or scripts.
* This log consists of command and output.
* Useful for bconsole, b*json... and others.
*
* @param string $cmd command
* @param array|object|string $output command output
* @return string formatted command log
*/
public static function prepareOutput($cmd, $output) {
if (is_array($output)) {
$output = implode(PHP_EOL, $output);
} elseif(is_object($output)) {
$output = print_r($output, true);
}
return sprintf(
"\n\n===> Command:\n\n%s\n\n===> Output:\n\n%s",
$cmd,
print_r($output, true),
$file,
intval($line)
$output
);
Prado::trace($log, $category);
// switch back application to original mode
$this->Application->setMode($current_mode);
}
}
?>
......@@ -68,11 +68,8 @@ class SessionRecord extends CommonModule implements ISessionItem {
} else {
$emsg = 'Unable to exclusive lock ' . $sessfile;
$this->getModule('logging')->log(
__FUNCTION__,
$emsg,
Logging::CATEGORY_APPLICATION,
__FILE__,
__LINE__
$emsg
);
}
fclose($fp);
......@@ -94,11 +91,8 @@ class SessionRecord extends CommonModule implements ISessionItem {
} else {
$emsg = 'Unable to shared lock ' . $sessfile;
$this->getModule('logging')->log(
__FUNCTION__,
$emsg,
Logging::CATEGORY_APPLICATION,
__FILE__,
__LINE__
$emsg
);
}
fclose($fp);
......
......@@ -173,11 +173,8 @@ class BaculumAPIClient extends WebModule {
$this->addSpecialParams($uri);
$this->Application->getModule('logging')->log(
__FUNCTION__,
PHP_EOL . PHP_EOL . 'EXECUTE URI ==> ' . $uri . ' <==' . PHP_EOL . PHP_EOL,
Logging::CATEGORY_APPLICATION,
__FILE__,
__LINE__
'REQUEST URI => ' . $uri
);
}
return $uri;
......@@ -433,11 +430,8 @@ class BaculumAPIClient extends WebModule {
private function preParseOutput($result, $error, $errno, $show_error = true) {
// first write log with that what comes
$this->Application->getModule('logging')->log(
__FUNCTION__,
$result,
Logging::CATEGORY_APPLICATION,
__FILE__,
__LINE__
$result
);
// decode JSON to object
......@@ -462,11 +456,8 @@ class BaculumAPIClient extends WebModule {
}
$this->Application->getModule('logging')->log(
__FUNCTION__,
$resource,
Logging::CATEGORY_APPLICATION,
__FILE__,
__LINE__
$resource
);
return $resource;
......
......@@ -61,7 +61,10 @@ class DataDependencies extends WebModule {
$data_deps = json_decode($deps_file);
} else {
$emsg = "Data dependencies file '$deps_file' does not exist or is not readable.";
$this->Application->getModule('logging')->log(__FUNCTION__, $emsg, Logging::CATEGORY_APPLICATION, __FILE__, __LINE__);
$this->Application->getModule('logging')->log(
Logging::CATEGORY_APPLICATION,
$emsg
);
}
return $data_deps;
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment