new: [internal] Add more metadata to ECS log

pull/9465/head
Jakub Onderka 2023-12-24 15:07:20 +01:00
parent 2d3c29d908
commit 31f40c8d43
2 changed files with 87 additions and 23 deletions

View File

@ -437,16 +437,17 @@ class Log extends AppModel
*/
private function sendToEcs(array $data)
{
$action = 'info';
if (isset($data['Log']['action'])) {
if (in_array($data['Log']['action'], self::ERROR_ACTIONS, true)) {
$action = 'error';
} else if (in_array($data['Log']['action'], self::WARNING_ACTIONS, true)) {
$action = 'warning';
$action = $data['Log']['action'];
$type = 'info';
if (isset($action)) {
if (in_array($action, self::ERROR_ACTIONS, true)) {
$type = 'error';
} else if (in_array($action, self::WARNING_ACTIONS, true)) {
$type = 'warning';
}
}
$message = $data['Log']['action'];
$message = $action;
if (!empty($data['Log']['title'])) {
$message .= " -- {$data['Log']['title']}";
}
@ -456,7 +457,7 @@ class Log extends AppModel
$message .= " -- " . JsonTool::encode($data['Log']['change']);
}
EcsLog::writeApplicationLog($action, $message);
EcsLog::writeApplicationLog($type, $action, $message);
}
public function filterSiteAdminSensitiveLogs($list)

View File

@ -1,4 +1,5 @@
<?php
App::uses('JsonTool', 'Tools');
/**
* Logging class that sends logs in JSON format to UNIX socket in Elastic Common Schema (ECS) format
@ -47,11 +48,13 @@ class EcsLog implements CakeLogInterface
/**
* @param string $type
* @param string $action
* @param string $userEmail
* @param string $message
* @return void
* @throws JsonException
*/
public static function writeApplicationLog($type, $message)
public static function writeApplicationLog($type, $action, $message)
{
$message = [
'@timestamp' => date('Y-m-d\TH:i:s.uP'),
@ -63,6 +66,7 @@ class EcsLog implements CakeLogInterface
'provider' => 'misp',
'module' => 'application',
'dataset' => 'application.logs',
'action' => $action,
],
'log' => [
'level' => $type,
@ -70,6 +74,14 @@ class EcsLog implements CakeLogInterface
'message' => $message,
];
if (in_array($action, ['auth', 'auth_fail', 'auth_alert', 'change_pw', 'login', 'login_fail', 'logout', 'password_reset'], true)) {
$message['event']['category'] = 'authentication';
if (in_array($action, ['auth_fail', 'login_fail'], true)) {
$message['event']['outcome'] = 'failure';
}
}
static::writeMessage($message);
}
@ -89,17 +101,13 @@ class EcsLog implements CakeLogInterface
/**
* @return array[]
*/
private static function generateMeta()
private static function createLogMeta()
{
if (self::$meta) {
return self::$meta;
}
$meta = [
'process' => [
'pid' => getmypid(),
],
];
$meta = ['process' => ['pid' => getmypid()]];
// Add metadata if log was generated because of HTTP request
if (PHP_SAPI !== 'cli') {
@ -108,14 +116,17 @@ class EcsLog implements CakeLogInterface
}
$clientIp = static::clientIp();
$client = [
'ip' => $_SERVER['REMOTE_ADDR'],
'port' => $_SERVER['REMOTE_PORT'],
];
if ($clientIp === $_SERVER['REMOTE_ADDR']) {
$meta['client'] = ['ip' => static::clientIp()];
$meta['client'] = $client;
} else {
$meta['client'] = [
'ip' => static::clientIp(),
'nat' => [
'ip' => $_SERVER['REMOTE_ADDR'],
],
'nat' => $client,
];
}
@ -132,11 +143,52 @@ class EcsLog implements CakeLogInterface
'path' => $_SERVER['REQUEST_URI'],
];
}
} else {
$meta['process']['argv'] = $_SERVER['argv'];
}
$userMeta = self::createUserMeta();
if ($userMeta) {
$meta['user'] = $userMeta;
}
return self::$meta = $meta;
}
private static function createUserMeta()
{
if (PHP_SAPI === 'cli') {
$currentUserId = Configure::read('CurrentUserId');
if (!empty($currentUserId)) {
/** @var User $userModel */
$userModel = ClassRegistry::init('User');
$user = $userModel->find('first', [
'recursive' => -1,
'conditions' => ['id' => $currentUserId],
'fields' => ['sub', 'email'],
]);
if (!empty($user)) {
return [
'id' => empty($user['User']['sub']) ? $currentUserId : $user['User']['sub'],
'email' => $user['User']['email'],
];
}
}
} else {
App::uses('AuthComponent', 'Controller/Component');
$authUser = AuthComponent::user();
if (!empty($authUser)) {
return [
'id' => empty($authUser['sub']) ? $authUser['id'] : $authUser['sub'],
'email' => $authUser['email'],
];
}
}
return null;
}
/**
* @param array $message
* @return void
@ -145,13 +197,24 @@ class EcsLog implements CakeLogInterface
private static function writeMessage(array $message)
{
if (static::$socket === null) {
static::$socket = stream_socket_client('unix://' . static::SOCKET_PATH, $errorCode, $errorMessage);
static::connect();
}
$message = array_merge($message, self::generateMeta());
if (static::$socket) {
fwrite(static::$socket, JsonTool::encode($message) . "\n");
$message = array_merge($message, self::createLogMeta());
$data = JsonTool::encode($message) . "\n";
$bytesWritten = fwrite(static::$socket, $data);
// In case of failure, try reconnect and send log again
if ($bytesWritten === false) {
static::connect();
fwrite(static::$socket, $data);
}
}
}
private static function connect()
{
static::$socket = stream_socket_client('unix://' . static::SOCKET_PATH, $errorCode, $errorMessage);
}
}