-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch '4.4' into dev-cloud-limits
- Loading branch information
Showing
129 changed files
with
452,940 additions
and
309,860 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,23 +2,26 @@ | |
# Created by Wazuh, Inc. <[email protected]>. | ||
# This program is a free software; you can redistribute it and/or modify it under the terms of GPLv2 | ||
import binascii | ||
import hashlib | ||
import json | ||
import logging | ||
import re | ||
from base64 import b64decode | ||
|
||
from aiohttp.abc import AbstractAccessLogger | ||
from pythonjsonlogger import jsonlogger | ||
from wazuh.core.wlogging import WazuhLogger | ||
|
||
from api.configuration import api_conf | ||
from wazuh.core.wlogging import WazuhLogger | ||
|
||
# compile regex when the module is imported so it's not necessary to compile it everytime log.info is called | ||
# Compile regex when the module is imported so it's not necessary to compile it everytime log.info is called | ||
request_pattern = re.compile(r'\[.+]|\s+\*\s+') | ||
|
||
# Variable used to specify an unknown user | ||
UNKNOWN_USER_STRING = "unknown_user" | ||
|
||
# Run_as login endpoint path | ||
RUN_AS_LOGIN_ENDPOINT = "/security/user/authenticate/run_as" | ||
|
||
|
||
class AccessLogger(AbstractAccessLogger): | ||
""" | ||
|
@@ -30,7 +33,7 @@ def check_stream(self): | |
if not handler.stream or handler.stream.closed: | ||
handler.stream = handler._open() | ||
|
||
def custom_logging(self, user, remote, method, path, query, body, time, status): | ||
def custom_logging(self, user, remote, method, path, query, body, time, status, hash_auth_context=''): | ||
"""Provide the log entry structure depending on the logging format. | ||
Parameters | ||
|
@@ -51,27 +54,35 @@ def custom_logging(self, user, remote, method, path, query, body, time, status): | |
Required time to compute the request. | ||
status : int | ||
Status code of the request. | ||
hash_auth_context : str, optional | ||
Hash representing the authorization context. Default: '' | ||
""" | ||
|
||
self.logger.info(f'{user} ' | ||
f'{remote} ' | ||
f'"{method} {path}" ' | ||
f'with parameters {json.dumps(query)} and body {json.dumps(body)} ' | ||
f'done in {time:.3f}s: {status}', | ||
extra={'log_type': 'log'} | ||
) | ||
|
||
self.logger.info({'user': user, | ||
'ip': remote, | ||
'http_method': method, | ||
'uri': f'{method} {path}', | ||
'parameters': query, | ||
'body': body, | ||
'time': f'{time:.3f}s', | ||
'status_code': status | ||
}, | ||
extra={'log_type': 'json'} | ||
) | ||
if not hash_auth_context: | ||
log_info = f'{user} {remote} "{method} {path}" with parameters {json.dumps(query)} ' \ | ||
f'and body {json.dumps(body)} done in {time:.3f}s: {status}' | ||
json_info = {'user': user, | ||
'ip': remote, | ||
'http_method': method, | ||
'uri': f'{method} {path}', | ||
'parameters': query, | ||
'body': body, | ||
'time': f'{time:.3f}s', | ||
'status_code': status} | ||
else: | ||
log_info = f'{user} ({hash_auth_context}) {remote} "{method} {path}" with parameters {json.dumps(query)} ' \ | ||
f'and body {json.dumps(body)} done in {time:.3f}s: {status}' | ||
json_info = {'user': user, | ||
'hash_auth_context': hash_auth_context, | ||
'ip': remote, | ||
'http_method': method, | ||
'uri': f'{method} {path}', | ||
'parameters': query, | ||
'body': body, | ||
'time': f'{time:.3f}s', | ||
'status_code': status} | ||
|
||
self.logger.info(log_info, extra={'log_type': 'log'}) | ||
self.logger.info(json_info, extra={'log_type': 'json'}) | ||
|
||
def log(self, request, response, time): | ||
self.check_stream() | ||
|
@@ -83,6 +94,7 @@ def log(self, request, response, time): | |
body['password'] = '****' | ||
if 'key' in body and '/agents' in request.path: | ||
body['key'] = '****' | ||
|
||
# With permanent redirect, not found responses or any response with no token information, | ||
# decode the JWT token to get the username | ||
user = request.get('user', '') | ||
|
@@ -92,15 +104,17 @@ def log(self, request, response, time): | |
except (KeyError, IndexError, binascii.Error): | ||
user = UNKNOWN_USER_STRING | ||
|
||
self.custom_logging(user, | ||
request.remote, | ||
request.method, | ||
request.path, | ||
query, | ||
body, | ||
time, | ||
response.status | ||
) | ||
# Get or create authorization context hash | ||
hash_auth_context = '' | ||
# Get hash from token information | ||
if 'token_info' in request: | ||
hash_auth_context = request['token_info'].get('hash_auth_context', '') | ||
# Create hash if run_as login | ||
if not hash_auth_context and request.path == RUN_AS_LOGIN_ENDPOINT: | ||
hash_auth_context = hashlib.blake2b(json.dumps(body).encode(), digest_size=16).hexdigest() | ||
|
||
self.custom_logging(user, request.remote, request.method, request.path, query, body, time, response.status, | ||
hash_auth_context=hash_auth_context) | ||
|
||
|
||
class APILogger(WazuhLogger): | ||
|
@@ -143,6 +157,7 @@ class WazuhJsonFormatter(jsonlogger.JsonFormatter): | |
""" | ||
Define the custom JSON log formatter used by wlogging. | ||
""" | ||
|
||
def add_fields(self, log_record, record, message_dict): | ||
"""Implement custom logic for adding fields in a log entry. | ||
|
@@ -160,21 +175,21 @@ def add_fields(self, log_record, record, message_dict): | |
record.message = { | ||
'type': 'request', | ||
'payload': message_dict | ||
} | ||
} | ||
else: | ||
# Traceback handling | ||
traceback = message_dict.get('exc_info') | ||
if traceback is not None: | ||
record.message = { | ||
'type': 'error', | ||
'payload': f'{record.message}. {traceback}' | ||
} | ||
} | ||
else: | ||
# Plain text messages | ||
record.message = { | ||
'type': 'informative', | ||
'payload': record.message | ||
} | ||
} | ||
log_record['timestamp'] = self.formatTime(record, self.datefmt) | ||
log_record['levelname'] = record.levelname | ||
log_record['data'] = record.message |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.