clue-api 1.0.0.dev7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clue/.gitignore +21 -0
- clue/__init__.py +0 -0
- clue/api/__init__.py +211 -0
- clue/api/base.py +99 -0
- clue/api/v1/__init__.py +82 -0
- clue/api/v1/actions.py +92 -0
- clue/api/v1/auth.py +243 -0
- clue/api/v1/configs.py +83 -0
- clue/api/v1/fetchers.py +94 -0
- clue/api/v1/lookup.py +221 -0
- clue/api/v1/registration.py +109 -0
- clue/api/v1/static.py +94 -0
- clue/app.py +166 -0
- clue/cache/__init__.py +129 -0
- clue/common/__init__.py +0 -0
- clue/common/classification.py +1006 -0
- clue/common/classification.yml +130 -0
- clue/common/dict_utils.py +130 -0
- clue/common/exceptions.py +199 -0
- clue/common/forge.py +152 -0
- clue/common/json_utils.py +10 -0
- clue/common/list_utils.py +11 -0
- clue/common/logging/__init__.py +291 -0
- clue/common/logging/audit.py +157 -0
- clue/common/logging/format.py +42 -0
- clue/common/regex.py +31 -0
- clue/common/str_utils.py +213 -0
- clue/common/swagger.py +139 -0
- clue/common/uid.py +47 -0
- clue/config.py +60 -0
- clue/constants/__init__.py +0 -0
- clue/constants/supported_types.py +38 -0
- clue/cronjobs/__init__.py +30 -0
- clue/cronjobs/plugins.py +32 -0
- clue/error.py +129 -0
- clue/gunicorn_config.py +29 -0
- clue/healthz.py +74 -0
- clue/helper/discover.py +53 -0
- clue/helper/headers.py +30 -0
- clue/helper/oauth.py +128 -0
- clue/models/__init__.py +0 -0
- clue/models/actions.py +243 -0
- clue/models/config.py +456 -0
- clue/models/fetchers.py +136 -0
- clue/models/graph.py +162 -0
- clue/models/model_list.py +52 -0
- clue/models/network.py +430 -0
- clue/models/results/__init__.py +34 -0
- clue/models/results/base.py +10 -0
- clue/models/results/graph.py +26 -0
- clue/models/results/image.py +22 -0
- clue/models/results/status.py +55 -0
- clue/models/results/validation.py +57 -0
- clue/models/selector.py +67 -0
- clue/models/utils.py +52 -0
- clue/models/validators.py +19 -0
- clue/patched.py +8 -0
- clue/plugin/__init__.py +1008 -0
- clue/plugin/helpers/__init__.py +0 -0
- clue/plugin/helpers/central_server.py +27 -0
- clue/plugin/helpers/email_render.py +228 -0
- clue/plugin/helpers/token.py +34 -0
- clue/plugin/helpers/trino.py +103 -0
- clue/plugin/interactive.py +270 -0
- clue/plugin/models.py +19 -0
- clue/plugin/utils.py +78 -0
- clue/remote/__init__.py +0 -0
- clue/remote/datatypes/__init__.py +130 -0
- clue/remote/datatypes/cache.py +62 -0
- clue/remote/datatypes/events.py +118 -0
- clue/remote/datatypes/hash.py +193 -0
- clue/remote/datatypes/queues/__init__.py +0 -0
- clue/remote/datatypes/queues/comms.py +62 -0
- clue/remote/datatypes/set.py +96 -0
- clue/remote/datatypes/user_quota_tracker.py +54 -0
- clue/security/__init__.py +211 -0
- clue/security/obo.py +95 -0
- clue/security/utils.py +34 -0
- clue/services/action_service.py +186 -0
- clue/services/auth_service.py +348 -0
- clue/services/config_service.py +38 -0
- clue/services/fetcher_service.py +203 -0
- clue/services/jwt_service.py +233 -0
- clue/services/lookup_service.py +786 -0
- clue/services/type_service.py +165 -0
- clue/services/user_service.py +152 -0
- clue_api-1.0.0.dev7.dist-info/METADATA +111 -0
- clue_api-1.0.0.dev7.dist-info/RECORD +91 -0
- clue_api-1.0.0.dev7.dist-info/WHEEL +4 -0
- clue_api-1.0.0.dev7.dist-info/entry_points.txt +8 -0
- clue_api-1.0.0.dev7.dist-info/licenses/LICENSE +11 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import logging.handlers
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import uuid
|
|
7
|
+
from traceback import format_exception
|
|
8
|
+
from typing import Optional, Self, Union
|
|
9
|
+
|
|
10
|
+
from flask import request
|
|
11
|
+
|
|
12
|
+
from clue.common.logging.format import (
|
|
13
|
+
BRL_DATE_FORMAT,
|
|
14
|
+
BRL_JSON_FORMAT,
|
|
15
|
+
BRL_LOG_FORMAT,
|
|
16
|
+
BRL_SYSLOG_FORMAT,
|
|
17
|
+
)
|
|
18
|
+
from clue.common.str_utils import default_string_value
|
|
19
|
+
|
|
20
|
+
LOG_LEVEL_MAP = {
|
|
21
|
+
"DEBUG": logging.DEBUG,
|
|
22
|
+
"INFO": logging.INFO,
|
|
23
|
+
"WARNING": logging.WARNING,
|
|
24
|
+
"ERROR": logging.ERROR,
|
|
25
|
+
"CRITICAL": logging.CRITICAL,
|
|
26
|
+
"DISABLED": 60,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
DEBUG = False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class JsonFormatter(logging.Formatter):
|
|
33
|
+
"""A custom implementation of logging.Formatter that supports json logs as well as traceback for exceptions.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
logging (str): The template to use for the logs
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def formatMessage(self: Self, record: logging.LogRecord): # noqa: N802
|
|
40
|
+
"""Formats record.message as json, and formats exceptions using traceback.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
record (logging.LogRecord): The log record to format
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
str: The formatted log
|
|
47
|
+
"""
|
|
48
|
+
if record.exc_info:
|
|
49
|
+
record.exc_text = self.formatException(record.exc_info)
|
|
50
|
+
record.exc_info = None
|
|
51
|
+
|
|
52
|
+
if record.exc_text:
|
|
53
|
+
record.message += "\n" + record.exc_text
|
|
54
|
+
record.exc_text = None
|
|
55
|
+
|
|
56
|
+
record.message = json.dumps(record.message)
|
|
57
|
+
return self._style.format(record)
|
|
58
|
+
|
|
59
|
+
def formatException(self, exc_info): # noqa: N802
|
|
60
|
+
"""Formats the exception using traceback
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
exc_info (logging._SysExcInfoType): The exc_info object from the LogRecord
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
str: The formatted traceback error
|
|
67
|
+
"""
|
|
68
|
+
return "".join(format_exception(*exc_info))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def init_logging(name: str, log_level: Optional[int] = None): # noqa: C901
|
|
72
|
+
"""Initializes the logging stack for an app
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
name (str): The name of the app
|
|
76
|
+
log_level (Optional[int], optional): The log level to use. Defaults to None.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Logger: The initialized logger
|
|
80
|
+
"""
|
|
81
|
+
from clue.config import config
|
|
82
|
+
|
|
83
|
+
app_name: str = default_string_value(env_name="APP_NAME", default="clue") # type: ignore[assignment]
|
|
84
|
+
|
|
85
|
+
logger = logging.getLogger(app_name)
|
|
86
|
+
|
|
87
|
+
# Test if we've initialized the log handler already.
|
|
88
|
+
if len(logger.handlers) != 0:
|
|
89
|
+
return logger.getChild(name)
|
|
90
|
+
|
|
91
|
+
if name.startswith(f"{app_name}."):
|
|
92
|
+
name = name[len(app_name) + 1 :]
|
|
93
|
+
|
|
94
|
+
debug = config.api.debug
|
|
95
|
+
config.logging.log_to_console = config.logging.log_to_console or debug
|
|
96
|
+
|
|
97
|
+
if log_level is None:
|
|
98
|
+
log_level = LOG_LEVEL_MAP[config.logging.log_level]
|
|
99
|
+
|
|
100
|
+
logging.root.setLevel(logging.CRITICAL)
|
|
101
|
+
logger.setLevel(log_level)
|
|
102
|
+
|
|
103
|
+
if config.logging.log_level == "DISABLED":
|
|
104
|
+
# While log_level is set to disable, we will not create any handlers
|
|
105
|
+
return logger.getChild(name)
|
|
106
|
+
|
|
107
|
+
if config.logging.log_to_file:
|
|
108
|
+
if not os.path.isdir(config.logging.log_directory):
|
|
109
|
+
logger.warning(
|
|
110
|
+
"Log directory does not exist. Will try to create %s",
|
|
111
|
+
config.logging.log_directory,
|
|
112
|
+
)
|
|
113
|
+
os.makedirs(config.logging.log_directory)
|
|
114
|
+
|
|
115
|
+
if log_level <= logging.DEBUG:
|
|
116
|
+
dbg_file_handler = logging.handlers.RotatingFileHandler(
|
|
117
|
+
os.path.join(config.logging.log_directory, f"{name}.dbg"),
|
|
118
|
+
maxBytes=10485760,
|
|
119
|
+
backupCount=5,
|
|
120
|
+
)
|
|
121
|
+
dbg_file_handler.setLevel(logging.DEBUG)
|
|
122
|
+
if config.logging.log_as_json:
|
|
123
|
+
dbg_file_handler.setFormatter(JsonFormatter(BRL_JSON_FORMAT))
|
|
124
|
+
else:
|
|
125
|
+
dbg_file_handler.setFormatter(logging.Formatter(BRL_LOG_FORMAT, BRL_DATE_FORMAT))
|
|
126
|
+
logger.addHandler(dbg_file_handler)
|
|
127
|
+
|
|
128
|
+
if log_level <= logging.INFO:
|
|
129
|
+
op_file_handler = logging.handlers.RotatingFileHandler(
|
|
130
|
+
os.path.join(config.logging.log_directory, f"{name}.log"),
|
|
131
|
+
maxBytes=10485760,
|
|
132
|
+
backupCount=5,
|
|
133
|
+
)
|
|
134
|
+
op_file_handler.setLevel(logging.INFO)
|
|
135
|
+
if config.logging.log_as_json:
|
|
136
|
+
op_file_handler.setFormatter(JsonFormatter(BRL_JSON_FORMAT))
|
|
137
|
+
else:
|
|
138
|
+
op_file_handler.setFormatter(logging.Formatter(BRL_LOG_FORMAT, BRL_DATE_FORMAT))
|
|
139
|
+
logger.addHandler(op_file_handler)
|
|
140
|
+
|
|
141
|
+
if log_level <= logging.ERROR:
|
|
142
|
+
err_file_handler = logging.handlers.RotatingFileHandler(
|
|
143
|
+
os.path.join(config.logging.log_directory, f"{name}.err"),
|
|
144
|
+
maxBytes=10485760,
|
|
145
|
+
backupCount=5,
|
|
146
|
+
)
|
|
147
|
+
err_file_handler.setLevel(logging.ERROR)
|
|
148
|
+
if config.logging.log_as_json:
|
|
149
|
+
err_file_handler.setFormatter(JsonFormatter(BRL_JSON_FORMAT))
|
|
150
|
+
else:
|
|
151
|
+
err_file_handler.setFormatter(logging.Formatter(BRL_LOG_FORMAT, BRL_DATE_FORMAT))
|
|
152
|
+
logger.addHandler(err_file_handler)
|
|
153
|
+
|
|
154
|
+
if config.logging.log_to_console:
|
|
155
|
+
console = logging.StreamHandler()
|
|
156
|
+
if config.logging.log_as_json:
|
|
157
|
+
console.setFormatter(JsonFormatter(BRL_JSON_FORMAT))
|
|
158
|
+
else:
|
|
159
|
+
console.setFormatter(logging.Formatter(BRL_LOG_FORMAT, BRL_DATE_FORMAT))
|
|
160
|
+
logger.addHandler(console)
|
|
161
|
+
|
|
162
|
+
if config.logging.log_to_syslog and config.logging.syslog_host and config.logging.syslog_port:
|
|
163
|
+
syslog_handler = logging.handlers.SysLogHandler(
|
|
164
|
+
address=(config.logging.syslog_host, config.logging.syslog_port)
|
|
165
|
+
)
|
|
166
|
+
syslog_handler.formatter = logging.Formatter(BRL_SYSLOG_FORMAT)
|
|
167
|
+
logger.addHandler(syslog_handler)
|
|
168
|
+
|
|
169
|
+
return logger.getChild(name)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def get_logger(name: Optional[str] = None, parent: Optional[logging.Logger] = None) -> logging.Logger:
|
|
173
|
+
"""Gets the logger for a specified python file
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
name (Optional[str], optional): The name of the python file. Defaults to None.
|
|
177
|
+
parent (Optional[logging.Logger], optional): The parent of the file. Defaults to None.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
logging.Logger: _description_
|
|
181
|
+
"""
|
|
182
|
+
if name:
|
|
183
|
+
name = (
|
|
184
|
+
re.sub(r".+clue(-api)?/", "", re.sub(r".+plugins/", "", name))
|
|
185
|
+
.replace("/", ".")
|
|
186
|
+
.replace(".__init__", "")
|
|
187
|
+
.replace(".py", "")
|
|
188
|
+
)
|
|
189
|
+
name = re.sub(r"^api\.?", "", name)
|
|
190
|
+
|
|
191
|
+
logger = parent or init_logging("api")
|
|
192
|
+
|
|
193
|
+
if name:
|
|
194
|
+
logger = logger.getChild(name)
|
|
195
|
+
|
|
196
|
+
return logger
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def get_traceback_info(tb):
|
|
200
|
+
"""Gets the traceback info of a traceback"""
|
|
201
|
+
tb_list = []
|
|
202
|
+
tb_id = 0
|
|
203
|
+
last_ui = None
|
|
204
|
+
while tb is not None:
|
|
205
|
+
f = tb.tb_frame
|
|
206
|
+
line_no = tb.tb_lineno
|
|
207
|
+
tb_list.append((f, line_no))
|
|
208
|
+
tb = tb.tb_next
|
|
209
|
+
if "/ui/" in f.f_code.co_filename:
|
|
210
|
+
last_ui = tb_id
|
|
211
|
+
tb_id += 1
|
|
212
|
+
|
|
213
|
+
if last_ui is not None:
|
|
214
|
+
tb_frame, line = tb_list[last_ui]
|
|
215
|
+
user = tb_frame.f_locals.get("kwargs", {}).get("user", None)
|
|
216
|
+
|
|
217
|
+
if not user:
|
|
218
|
+
temp = tb_frame.f_locals.get("_", {})
|
|
219
|
+
if isinstance(temp, dict):
|
|
220
|
+
user = temp.get("user", None)
|
|
221
|
+
|
|
222
|
+
if not user:
|
|
223
|
+
user = tb_frame.f_locals.get("user", None)
|
|
224
|
+
|
|
225
|
+
if not user:
|
|
226
|
+
user = tb_frame.f_locals.get("impersonator", None)
|
|
227
|
+
|
|
228
|
+
if user:
|
|
229
|
+
return user, tb_frame.f_code.co_filename, tb_frame.f_code.co_name, line
|
|
230
|
+
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
return None
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def dumb_log(log, msg, is_exception=False):
|
|
237
|
+
"""Logs a message to a generic logger."""
|
|
238
|
+
args: Union[str, bytes] = request.query_string
|
|
239
|
+
if isinstance(args, bytes):
|
|
240
|
+
args = args.decode()
|
|
241
|
+
|
|
242
|
+
if args:
|
|
243
|
+
args = f"?{args}"
|
|
244
|
+
|
|
245
|
+
message = f"{msg} - {request.path}{args}"
|
|
246
|
+
if is_exception:
|
|
247
|
+
log.exception(message)
|
|
248
|
+
else:
|
|
249
|
+
log.warning(message)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def log_with_traceback(traceback, msg, is_exception=False, audit=False):
|
|
253
|
+
"""Logs a message with a traceback"""
|
|
254
|
+
log = get_logger("traceback") if not audit else logging.getLogger("clue.api.audit")
|
|
255
|
+
|
|
256
|
+
tb_info = get_traceback_info(traceback)
|
|
257
|
+
if tb_info:
|
|
258
|
+
tb_user, tb_file, tb_function, tb_line_no = tb_info
|
|
259
|
+
args: Optional[Union[str, bytes]] = request.query_string
|
|
260
|
+
if args:
|
|
261
|
+
args = f"?{args if isinstance(args, str) else args.decode()}"
|
|
262
|
+
else:
|
|
263
|
+
args = ""
|
|
264
|
+
|
|
265
|
+
# noinspection PyBroadException
|
|
266
|
+
try:
|
|
267
|
+
message = (
|
|
268
|
+
f'{tb_user["uname"]} [{tb_user["classification"]}] :: {msg} - {tb_file}:{tb_function}:{tb_line_no}'
|
|
269
|
+
f'[{os.environ.get("CLUE_VERSION", "0.0.0.dev0")}] ({request.path}{args})'
|
|
270
|
+
)
|
|
271
|
+
if is_exception:
|
|
272
|
+
log.exception(message)
|
|
273
|
+
else:
|
|
274
|
+
log.warning(message)
|
|
275
|
+
except Exception:
|
|
276
|
+
dumb_log(log, msg, is_exception=is_exception)
|
|
277
|
+
else:
|
|
278
|
+
dumb_log(log, msg, is_exception=is_exception)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def log_error(logger, msg, err=None, status_code=None):
|
|
282
|
+
"""Log a standard error string, with a unique id reference for logging."""
|
|
283
|
+
err_id = str(uuid.uuid4())
|
|
284
|
+
error = [msg]
|
|
285
|
+
if status_code:
|
|
286
|
+
error.append(f"{status_code=}")
|
|
287
|
+
if err:
|
|
288
|
+
error.append(f"{err=}")
|
|
289
|
+
error.append(f"{err_id}")
|
|
290
|
+
logger.error(" :: ".join(error))
|
|
291
|
+
return err_id
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from flask import request
|
|
6
|
+
|
|
7
|
+
from clue.common.logging.format import (
|
|
8
|
+
BRL_AUDIT_FORMAT,
|
|
9
|
+
BRL_DATE_FORMAT,
|
|
10
|
+
BRL_ISO_DATE_FORMAT,
|
|
11
|
+
BRL_LOG_FORMAT,
|
|
12
|
+
)
|
|
13
|
+
from clue.config import DEBUG, config
|
|
14
|
+
|
|
15
|
+
AUDIT = config.api.audit
|
|
16
|
+
|
|
17
|
+
AUDIT_KW_TARGET = [
|
|
18
|
+
"sid",
|
|
19
|
+
"sha256",
|
|
20
|
+
"copy_sid",
|
|
21
|
+
"filter",
|
|
22
|
+
"query",
|
|
23
|
+
"username",
|
|
24
|
+
"group",
|
|
25
|
+
"rev",
|
|
26
|
+
"wq_id",
|
|
27
|
+
"index",
|
|
28
|
+
"cache_key",
|
|
29
|
+
"alert_key",
|
|
30
|
+
"alert_id",
|
|
31
|
+
"url",
|
|
32
|
+
"q",
|
|
33
|
+
"fq",
|
|
34
|
+
"file_hash",
|
|
35
|
+
"heuristic_id",
|
|
36
|
+
"error_key",
|
|
37
|
+
"mac",
|
|
38
|
+
"vm_type",
|
|
39
|
+
"vm_name",
|
|
40
|
+
"config_name",
|
|
41
|
+
"servicename",
|
|
42
|
+
"vm",
|
|
43
|
+
"transition",
|
|
44
|
+
"data",
|
|
45
|
+
"id",
|
|
46
|
+
"comment_id",
|
|
47
|
+
"label_set",
|
|
48
|
+
"tool_name",
|
|
49
|
+
"operation_id",
|
|
50
|
+
"category",
|
|
51
|
+
"label",
|
|
52
|
+
"type_name",
|
|
53
|
+
"type",
|
|
54
|
+
"value",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
AUDIT_LOG = logging.getLogger("clue.api.audit")
|
|
58
|
+
AUDIT_LOG.propagate = False
|
|
59
|
+
|
|
60
|
+
if AUDIT:
|
|
61
|
+
AUDIT_LOG.setLevel(logging.DEBUG)
|
|
62
|
+
|
|
63
|
+
if not os.path.exists(config.logging.log_directory):
|
|
64
|
+
os.makedirs(config.logging.log_directory)
|
|
65
|
+
|
|
66
|
+
fh = logging.FileHandler(os.path.join(config.logging.log_directory, "brl_audit.log"))
|
|
67
|
+
fh.setLevel(logging.DEBUG)
|
|
68
|
+
fh.setFormatter(
|
|
69
|
+
logging.Formatter(
|
|
70
|
+
BRL_LOG_FORMAT if DEBUG else BRL_AUDIT_FORMAT,
|
|
71
|
+
BRL_DATE_FORMAT if DEBUG else BRL_ISO_DATE_FORMAT,
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
AUDIT_LOG.addHandler(fh)
|
|
75
|
+
|
|
76
|
+
ch = logging.StreamHandler(sys.stdout)
|
|
77
|
+
ch.setLevel(logging.INFO)
|
|
78
|
+
ch.setFormatter(
|
|
79
|
+
logging.Formatter(
|
|
80
|
+
BRL_LOG_FORMAT if DEBUG else BRL_AUDIT_FORMAT,
|
|
81
|
+
BRL_DATE_FORMAT if DEBUG else BRL_ISO_DATE_FORMAT,
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
AUDIT_LOG.addHandler(ch)
|
|
85
|
+
|
|
86
|
+
#########################
|
|
87
|
+
# End of prepare logger #
|
|
88
|
+
#########################
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def audit(args, kwargs, user, func, impersonator=None):
|
|
92
|
+
"""Audit
|
|
93
|
+
|
|
94
|
+
Arguments:
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
try:
|
|
98
|
+
json_blob = request.json
|
|
99
|
+
if not isinstance(json_blob, dict):
|
|
100
|
+
json_blob = {}
|
|
101
|
+
except Exception:
|
|
102
|
+
json_blob = {}
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
req_args = ["%s='%s'" % (k, v) for k, v in request.args.items() if k in AUDIT_KW_TARGET]
|
|
106
|
+
method = request.method
|
|
107
|
+
path = request.path
|
|
108
|
+
except RuntimeError:
|
|
109
|
+
req_args = []
|
|
110
|
+
method = "N/A"
|
|
111
|
+
path = "N/A"
|
|
112
|
+
|
|
113
|
+
params_list = (
|
|
114
|
+
list(args)
|
|
115
|
+
+ ["%s='%s'" % (k, v) for k, v in kwargs.items() if k in AUDIT_KW_TARGET]
|
|
116
|
+
+ req_args
|
|
117
|
+
+ ["%s='%s'" % (k, v) for k, v in json_blob.items() if k in AUDIT_KW_TARGET]
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if impersonator:
|
|
121
|
+
audit_user = f"{impersonator} on behalf of {user['uname']}"
|
|
122
|
+
else:
|
|
123
|
+
audit_user = user["uname"]
|
|
124
|
+
|
|
125
|
+
if DEBUG:
|
|
126
|
+
# In debug mode, you'll get an output like:
|
|
127
|
+
# 23/03/20 14:26:56 DEBUG clue.api.audit | goose - search(index='...', query='...')
|
|
128
|
+
AUDIT_LOG.debug(
|
|
129
|
+
"%s [%s]- %s(%s)",
|
|
130
|
+
audit_user,
|
|
131
|
+
user["classification"],
|
|
132
|
+
func.__name__,
|
|
133
|
+
", ".join(params_list),
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
# In prod, you'll get an output like:
|
|
137
|
+
# {
|
|
138
|
+
# "date": "2023-03-20T18:33:27-0400",
|
|
139
|
+
# "type": "audit",
|
|
140
|
+
# "app_name": "clue",
|
|
141
|
+
# "api": "clue.api.audit",
|
|
142
|
+
# "severity": "INFO",
|
|
143
|
+
# "user": "goose",
|
|
144
|
+
# "function": "search(index='hit', query='blah blah')",
|
|
145
|
+
# "method": "POST",
|
|
146
|
+
# "path": "/api/v1/search/hit/"
|
|
147
|
+
# }
|
|
148
|
+
AUDIT_LOG.info(
|
|
149
|
+
"",
|
|
150
|
+
extra={
|
|
151
|
+
"user": audit_user,
|
|
152
|
+
"function": f"{func.__name__}({', '.join(params_list)})",
|
|
153
|
+
"method": method,
|
|
154
|
+
"path": path,
|
|
155
|
+
"classification": user["classification"],
|
|
156
|
+
},
|
|
157
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from clue.common.str_utils import default_string_value
|
|
4
|
+
|
|
5
|
+
hostname = "unknownhost"
|
|
6
|
+
# noinspection PyBroadException
|
|
7
|
+
try:
|
|
8
|
+
import socket
|
|
9
|
+
|
|
10
|
+
hostname = socket.gethostname()
|
|
11
|
+
except Exception: # noqa: S110
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
APP_NAME: str = default_string_value(env_name="APP_NAME", default="clue") # type: ignore[assignment]
|
|
15
|
+
|
|
16
|
+
BRL_SYSLOG_FORMAT = f"HWL %(levelname)8s {hostname} %(process)5d %(name)40s | %(message)s"
|
|
17
|
+
BRL_LOG_FORMAT = "%(asctime)s %(levelname)s %(name)s | %(message)s"
|
|
18
|
+
BRL_DATE_FORMAT = "%y/%m/%d %H:%M:%S"
|
|
19
|
+
BRL_JSON_FORMAT = (
|
|
20
|
+
f"{{"
|
|
21
|
+
f'"@timestamp": "%(asctime)s", '
|
|
22
|
+
f'"event": {{ "module": "{APP_NAME}", "dataset": "%(name)s" }}, '
|
|
23
|
+
f'"host": {{ "hostname": "{hostname}" }}, '
|
|
24
|
+
f'"log": {{ "level": "%(levelname)s", "logger": "%(name)s" }}, '
|
|
25
|
+
f'"process": {{ "pid": "%(process)d" }}, '
|
|
26
|
+
f'"message": %(message)s}}'
|
|
27
|
+
)
|
|
28
|
+
BRL_ISO_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
|
|
29
|
+
BRL_AUDIT_FORMAT = json.dumps(
|
|
30
|
+
{
|
|
31
|
+
"date": "%(asctime)s",
|
|
32
|
+
"type": "audit",
|
|
33
|
+
"app_name": APP_NAME,
|
|
34
|
+
"api": f"{APP_NAME}.api.audit",
|
|
35
|
+
"severity": "%(levelname)s",
|
|
36
|
+
"user": "%(user)s",
|
|
37
|
+
"classification": "%(classification)s",
|
|
38
|
+
"function": "%(function)s",
|
|
39
|
+
"method": "%(method)s",
|
|
40
|
+
"path": "%(path)s",
|
|
41
|
+
}
|
|
42
|
+
).replace('"msg"', "%(message)s")
|
clue/common/regex.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
DOMAIN_REGEX = r"(?:(?=[a-z0-9-]{1,63}\.)(?:xn--)?[a-z0-9-]+(?:-[a-z0-9]+)*\.)+[a-z]{2,63}"
|
|
2
|
+
DOMAIN_ONLY_REGEX = f"^{DOMAIN_REGEX}$"
|
|
3
|
+
DOMAIN_EXCLUDED_NORM_CHARS = "./?@#"
|
|
4
|
+
EMAIL_REGEX = f"^[a-zA-Z0-9!#$%&'*+/=?^_‘{{|}}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_‘{{|}}~-]+)*@({DOMAIN_REGEX})$"
|
|
5
|
+
IPV4_REGEX = r"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
|
|
6
|
+
IPV6_REGEX = (
|
|
7
|
+
r"(?:(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|"
|
|
8
|
+
r"(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|"
|
|
9
|
+
r"(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|"
|
|
10
|
+
r"(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|"
|
|
11
|
+
r":(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|"
|
|
12
|
+
r"::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|"
|
|
13
|
+
r"(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|"
|
|
14
|
+
r"(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"
|
|
15
|
+
)
|
|
16
|
+
IP_REGEX = f"(?:{IPV4_REGEX}|{IPV6_REGEX})"
|
|
17
|
+
IP_ONLY_REGEX = f"^{IP_REGEX}$"
|
|
18
|
+
IPV4_ONLY_REGEX = f"^{IPV4_REGEX}$"
|
|
19
|
+
IPV6_ONLY_REGEX = f"^{IPV6_REGEX}$"
|
|
20
|
+
PORT_REGEX = r"^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$"
|
|
21
|
+
MD5_REGEX = r"^[a-fA-F0-9]{32}$"
|
|
22
|
+
SHA1_REGEX = r"^[a-fA-F0-9]{40}$"
|
|
23
|
+
SHA256_REGEX = r"^[a-fA-F0-9]{64}$"
|
|
24
|
+
URI_PATH = r"([/?#]\S*)"
|
|
25
|
+
URI_REGEX = (
|
|
26
|
+
f"((?:(?:[A-Za-z][A-Za-z0-9+.-]*:)//)(?:[^/?#\\s]+@)?({IP_REGEX}|{DOMAIN_REGEX})(?::\\d{{1,5}})?{URI_PATH}?)"
|
|
27
|
+
)
|
|
28
|
+
URI_ONLY = f"^{URI_REGEX}$"
|
|
29
|
+
|
|
30
|
+
EMAIL_PATH_REGEX = r"^[A-Z]*_EMAIL://.*"
|
|
31
|
+
HBS_AGENT_ID_REGEX = r"[0-9a-fA-F]{1,4}.[0-9a-fA-F]{1,4}.[0-9a-fA-F]{1,4}.[0-9a-fA-F]{1,4}"
|