howler-api 2.13.0.dev329__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.

Potentially problematic release.


This version of howler-api might be problematic. Click here for more details.

Files changed (200) hide show
  1. howler/__init__.py +0 -0
  2. howler/actions/__init__.py +167 -0
  3. howler/actions/add_label.py +111 -0
  4. howler/actions/add_to_bundle.py +159 -0
  5. howler/actions/change_field.py +76 -0
  6. howler/actions/demote.py +160 -0
  7. howler/actions/example_plugin.py +104 -0
  8. howler/actions/prioritization.py +93 -0
  9. howler/actions/promote.py +147 -0
  10. howler/actions/remove_from_bundle.py +133 -0
  11. howler/actions/remove_label.py +111 -0
  12. howler/actions/transition.py +200 -0
  13. howler/api/__init__.py +249 -0
  14. howler/api/base.py +88 -0
  15. howler/api/socket.py +114 -0
  16. howler/api/v1/__init__.py +97 -0
  17. howler/api/v1/action.py +372 -0
  18. howler/api/v1/analytic.py +748 -0
  19. howler/api/v1/auth.py +382 -0
  20. howler/api/v1/borealis.py +101 -0
  21. howler/api/v1/configs.py +55 -0
  22. howler/api/v1/dossier.py +222 -0
  23. howler/api/v1/help.py +28 -0
  24. howler/api/v1/hit.py +1181 -0
  25. howler/api/v1/notebook.py +82 -0
  26. howler/api/v1/overview.py +191 -0
  27. howler/api/v1/search.py +715 -0
  28. howler/api/v1/template.py +206 -0
  29. howler/api/v1/tool.py +183 -0
  30. howler/api/v1/user.py +414 -0
  31. howler/api/v1/utils/__init__.py +0 -0
  32. howler/api/v1/utils/etag.py +84 -0
  33. howler/api/v1/view.py +288 -0
  34. howler/app.py +235 -0
  35. howler/common/README.md +144 -0
  36. howler/common/__init__.py +0 -0
  37. howler/common/classification.py +979 -0
  38. howler/common/classification.yml +107 -0
  39. howler/common/exceptions.py +167 -0
  40. howler/common/hexdump.py +48 -0
  41. howler/common/iprange.py +171 -0
  42. howler/common/loader.py +154 -0
  43. howler/common/logging/__init__.py +241 -0
  44. howler/common/logging/audit.py +138 -0
  45. howler/common/logging/format.py +38 -0
  46. howler/common/net.py +79 -0
  47. howler/common/net_static.py +1494 -0
  48. howler/common/random_user.py +316 -0
  49. howler/common/swagger.py +117 -0
  50. howler/config.py +64 -0
  51. howler/cronjobs/__init__.py +29 -0
  52. howler/cronjobs/retention.py +61 -0
  53. howler/cronjobs/rules.py +274 -0
  54. howler/cronjobs/view_cleanup.py +88 -0
  55. howler/datastore/README.md +112 -0
  56. howler/datastore/__init__.py +0 -0
  57. howler/datastore/bulk.py +72 -0
  58. howler/datastore/collection.py +2327 -0
  59. howler/datastore/constants.py +117 -0
  60. howler/datastore/exceptions.py +41 -0
  61. howler/datastore/howler_store.py +105 -0
  62. howler/datastore/migrations/fix_process.py +41 -0
  63. howler/datastore/operations.py +130 -0
  64. howler/datastore/schemas.py +90 -0
  65. howler/datastore/store.py +231 -0
  66. howler/datastore/support/__init__.py +0 -0
  67. howler/datastore/support/build.py +214 -0
  68. howler/datastore/support/schemas.py +90 -0
  69. howler/datastore/types.py +22 -0
  70. howler/error.py +91 -0
  71. howler/external/__init__.py +0 -0
  72. howler/external/generate_mitre.py +96 -0
  73. howler/external/generate_sigma_rules.py +31 -0
  74. howler/external/generate_tlds.py +47 -0
  75. howler/external/reindex_data.py +46 -0
  76. howler/external/wipe_databases.py +58 -0
  77. howler/gunicorn_config.py +25 -0
  78. howler/healthz.py +47 -0
  79. howler/helper/__init__.py +0 -0
  80. howler/helper/azure.py +50 -0
  81. howler/helper/discover.py +59 -0
  82. howler/helper/hit.py +236 -0
  83. howler/helper/oauth.py +247 -0
  84. howler/helper/search.py +92 -0
  85. howler/helper/workflow.py +110 -0
  86. howler/helper/ws.py +378 -0
  87. howler/odm/README.md +102 -0
  88. howler/odm/__init__.py +1 -0
  89. howler/odm/base.py +1504 -0
  90. howler/odm/charter.txt +146 -0
  91. howler/odm/helper.py +416 -0
  92. howler/odm/howler_enum.py +25 -0
  93. howler/odm/models/__init__.py +0 -0
  94. howler/odm/models/action.py +33 -0
  95. howler/odm/models/analytic.py +90 -0
  96. howler/odm/models/assemblyline.py +48 -0
  97. howler/odm/models/aws.py +23 -0
  98. howler/odm/models/azure.py +16 -0
  99. howler/odm/models/cbs.py +44 -0
  100. howler/odm/models/config.py +558 -0
  101. howler/odm/models/dossier.py +33 -0
  102. howler/odm/models/ecs/__init__.py +0 -0
  103. howler/odm/models/ecs/agent.py +17 -0
  104. howler/odm/models/ecs/autonomous_system.py +16 -0
  105. howler/odm/models/ecs/client.py +149 -0
  106. howler/odm/models/ecs/cloud.py +141 -0
  107. howler/odm/models/ecs/code_signature.py +27 -0
  108. howler/odm/models/ecs/container.py +32 -0
  109. howler/odm/models/ecs/dns.py +62 -0
  110. howler/odm/models/ecs/egress.py +10 -0
  111. howler/odm/models/ecs/elf.py +74 -0
  112. howler/odm/models/ecs/email.py +122 -0
  113. howler/odm/models/ecs/error.py +14 -0
  114. howler/odm/models/ecs/event.py +140 -0
  115. howler/odm/models/ecs/faas.py +24 -0
  116. howler/odm/models/ecs/file.py +84 -0
  117. howler/odm/models/ecs/geo.py +30 -0
  118. howler/odm/models/ecs/group.py +18 -0
  119. howler/odm/models/ecs/hash.py +16 -0
  120. howler/odm/models/ecs/host.py +17 -0
  121. howler/odm/models/ecs/http.py +37 -0
  122. howler/odm/models/ecs/ingress.py +12 -0
  123. howler/odm/models/ecs/interface.py +21 -0
  124. howler/odm/models/ecs/network.py +30 -0
  125. howler/odm/models/ecs/observer.py +45 -0
  126. howler/odm/models/ecs/organization.py +12 -0
  127. howler/odm/models/ecs/os.py +21 -0
  128. howler/odm/models/ecs/pe.py +17 -0
  129. howler/odm/models/ecs/process.py +216 -0
  130. howler/odm/models/ecs/registry.py +26 -0
  131. howler/odm/models/ecs/related.py +45 -0
  132. howler/odm/models/ecs/rule.py +51 -0
  133. howler/odm/models/ecs/server.py +24 -0
  134. howler/odm/models/ecs/threat.py +247 -0
  135. howler/odm/models/ecs/tls.py +58 -0
  136. howler/odm/models/ecs/url.py +51 -0
  137. howler/odm/models/ecs/user.py +57 -0
  138. howler/odm/models/ecs/user_agent.py +20 -0
  139. howler/odm/models/ecs/vulnerability.py +41 -0
  140. howler/odm/models/gcp.py +16 -0
  141. howler/odm/models/hit.py +356 -0
  142. howler/odm/models/howler_data.py +328 -0
  143. howler/odm/models/lead.py +33 -0
  144. howler/odm/models/localized_label.py +13 -0
  145. howler/odm/models/overview.py +16 -0
  146. howler/odm/models/pivot.py +40 -0
  147. howler/odm/models/template.py +24 -0
  148. howler/odm/models/user.py +83 -0
  149. howler/odm/models/view.py +34 -0
  150. howler/odm/random_data.py +888 -0
  151. howler/odm/randomizer.py +606 -0
  152. howler/patched.py +5 -0
  153. howler/plugins/__init__.py +25 -0
  154. howler/plugins/config.py +123 -0
  155. howler/remote/__init__.py +0 -0
  156. howler/remote/datatypes/README.md +355 -0
  157. howler/remote/datatypes/__init__.py +98 -0
  158. howler/remote/datatypes/counters.py +63 -0
  159. howler/remote/datatypes/events.py +66 -0
  160. howler/remote/datatypes/hash.py +206 -0
  161. howler/remote/datatypes/lock.py +42 -0
  162. howler/remote/datatypes/queues/__init__.py +0 -0
  163. howler/remote/datatypes/queues/comms.py +59 -0
  164. howler/remote/datatypes/queues/multi.py +32 -0
  165. howler/remote/datatypes/queues/named.py +93 -0
  166. howler/remote/datatypes/queues/priority.py +215 -0
  167. howler/remote/datatypes/set.py +118 -0
  168. howler/remote/datatypes/user_quota_tracker.py +54 -0
  169. howler/security/__init__.py +253 -0
  170. howler/security/socket.py +108 -0
  171. howler/security/utils.py +185 -0
  172. howler/services/__init__.py +0 -0
  173. howler/services/action_service.py +111 -0
  174. howler/services/analytic_service.py +128 -0
  175. howler/services/auth_service.py +323 -0
  176. howler/services/config_service.py +128 -0
  177. howler/services/dossier_service.py +252 -0
  178. howler/services/event_service.py +93 -0
  179. howler/services/hit_service.py +893 -0
  180. howler/services/jwt_service.py +158 -0
  181. howler/services/lucene_service.py +286 -0
  182. howler/services/notebook_service.py +119 -0
  183. howler/services/overview_service.py +44 -0
  184. howler/services/template_service.py +45 -0
  185. howler/services/user_service.py +330 -0
  186. howler/utils/__init__.py +0 -0
  187. howler/utils/annotations.py +28 -0
  188. howler/utils/chunk.py +38 -0
  189. howler/utils/dict_utils.py +200 -0
  190. howler/utils/isotime.py +17 -0
  191. howler/utils/list_utils.py +11 -0
  192. howler/utils/lucene.py +77 -0
  193. howler/utils/path.py +27 -0
  194. howler/utils/socket_utils.py +61 -0
  195. howler/utils/str_utils.py +256 -0
  196. howler/utils/uid.py +47 -0
  197. howler_api-2.13.0.dev329.dist-info/METADATA +71 -0
  198. howler_api-2.13.0.dev329.dist-info/RECORD +200 -0
  199. howler_api-2.13.0.dev329.dist-info/WHEEL +4 -0
  200. howler_api-2.13.0.dev329.dist-info/entry_points.txt +8 -0
@@ -0,0 +1,241 @@
1
+ import json
2
+ import logging
3
+ import logging.handlers
4
+ import os
5
+ import re
6
+ from traceback import format_exception
7
+ from typing import TYPE_CHECKING, Optional, Union
8
+
9
+ from flask import request
10
+ from typing_extensions import override # type: ignore
11
+
12
+ from howler.common import loader
13
+ from howler.common.logging.format import (
14
+ HWL_DATE_FORMAT,
15
+ HWL_JSON_FORMAT,
16
+ HWL_LOG_FORMAT,
17
+ HWL_SYSLOG_FORMAT,
18
+ )
19
+
20
+ if TYPE_CHECKING:
21
+ from howler.odm.models.config import Config
22
+
23
+ LOG_LEVEL_MAP = {
24
+ "DEBUG": logging.DEBUG,
25
+ "INFO": logging.INFO,
26
+ "WARNING": logging.WARNING,
27
+ "ERROR": logging.ERROR,
28
+ "CRITICAL": logging.CRITICAL,
29
+ "DISABLED": 60,
30
+ }
31
+
32
+ DEBUG = False
33
+
34
+
35
+ class JsonFormatter(logging.Formatter):
36
+ """logging Formatter to output in JSON"""
37
+
38
+ @override
39
+ def formatMessage(self, record):
40
+ if record.exc_info:
41
+ record.exc_text = self.formatException(record.exc_info)
42
+ record.exc_info = None
43
+
44
+ if record.exc_text:
45
+ record.message += "\n" + record.exc_text
46
+ record.exc_text = None
47
+
48
+ record.message = json.dumps(record.message)
49
+ return self._style.format(record)
50
+
51
+ @override
52
+ def formatException(self, exc_info):
53
+ return "".join(format_exception(*exc_info))
54
+
55
+
56
+ def init_log_to_file(logger: logging.Logger, log_level: int, name: str, config: "Config"):
57
+ """Initialize file-based logging"""
58
+ if not os.path.isdir(config.logging.log_directory):
59
+ logger.warning(
60
+ "Log directory does not exist. Will try to create %s",
61
+ config.logging.log_directory,
62
+ )
63
+ os.makedirs(config.logging.log_directory)
64
+
65
+ if log_level <= logging.DEBUG:
66
+ dbg_file_handler = logging.handlers.RotatingFileHandler(
67
+ os.path.join(config.logging.log_directory, f"{name}.dbg"),
68
+ maxBytes=10485760,
69
+ backupCount=5,
70
+ )
71
+ dbg_file_handler.setLevel(logging.DEBUG)
72
+ if config.logging.log_as_json:
73
+ dbg_file_handler.setFormatter(JsonFormatter(HWL_JSON_FORMAT))
74
+ else:
75
+ dbg_file_handler.setFormatter(logging.Formatter(HWL_LOG_FORMAT, HWL_DATE_FORMAT))
76
+ logger.addHandler(dbg_file_handler)
77
+
78
+ if log_level <= logging.INFO:
79
+ op_file_handler = logging.handlers.RotatingFileHandler(
80
+ os.path.join(config.logging.log_directory, f"{name}.log"),
81
+ maxBytes=10485760,
82
+ backupCount=5,
83
+ )
84
+ op_file_handler.setLevel(logging.INFO)
85
+ if config.logging.log_as_json:
86
+ op_file_handler.setFormatter(JsonFormatter(HWL_JSON_FORMAT))
87
+ else:
88
+ op_file_handler.setFormatter(logging.Formatter(HWL_LOG_FORMAT, HWL_DATE_FORMAT))
89
+ logger.addHandler(op_file_handler)
90
+
91
+ if log_level <= logging.ERROR:
92
+ err_file_handler = logging.handlers.RotatingFileHandler(
93
+ os.path.join(config.logging.log_directory, f"{name}.err"),
94
+ maxBytes=10485760,
95
+ backupCount=5,
96
+ )
97
+ err_file_handler.setLevel(logging.ERROR)
98
+ if config.logging.log_as_json:
99
+ err_file_handler.setFormatter(JsonFormatter(HWL_JSON_FORMAT))
100
+ else:
101
+ err_file_handler.setFormatter(logging.Formatter(HWL_LOG_FORMAT, HWL_DATE_FORMAT))
102
+ err_file_handler.setFormatter(logging.Formatter(HWL_LOG_FORMAT, HWL_DATE_FORMAT))
103
+ logger.addHandler(err_file_handler)
104
+
105
+
106
+ def init_logging(name: str, log_level: Optional[int] = None):
107
+ """Initialize the logger"""
108
+ from howler.config import config
109
+
110
+ logger = logging.getLogger(loader.APP_NAME)
111
+
112
+ # Test if we've initialized the log handler already.
113
+ if len(logger.handlers) != 0:
114
+ return logger.getChild(name)
115
+
116
+ if name.startswith(f"{loader.APP_NAME}."):
117
+ name = name[len(loader.APP_NAME) + 1 :]
118
+
119
+ config.logging.log_to_console = config.logging.log_to_console or config.ui.debug
120
+
121
+ if log_level is None:
122
+ log_level = LOG_LEVEL_MAP[config.logging.log_level]
123
+
124
+ logging.root.setLevel(logging.CRITICAL)
125
+ logger.setLevel(log_level)
126
+
127
+ if config.logging.log_level == "DISABLED":
128
+ # While log_level is set to disable, we will not create any handlers
129
+ return logger.getChild(name)
130
+
131
+ if config.logging.log_to_file:
132
+ init_log_to_file(logger, log_level, name, config)
133
+
134
+ if config.logging.log_to_console:
135
+ console = logging.StreamHandler()
136
+ if config.logging.log_as_json:
137
+ console.setFormatter(JsonFormatter(HWL_JSON_FORMAT))
138
+ else:
139
+ console.setFormatter(logging.Formatter(HWL_LOG_FORMAT, HWL_DATE_FORMAT))
140
+ logger.addHandler(console)
141
+
142
+ if config.logging.log_to_syslog and config.logging.syslog_host and config.logging.syslog_port:
143
+ syslog_handler = logging.handlers.SysLogHandler(
144
+ address=(config.logging.syslog_host, config.logging.syslog_port)
145
+ )
146
+ syslog_handler.formatter = logging.Formatter(HWL_SYSLOG_FORMAT)
147
+ logger.addHandler(syslog_handler)
148
+
149
+ logger.debug("Logger ready!")
150
+ return logger.getChild(name)
151
+
152
+
153
+ def get_logger(name: str = "default") -> logging.Logger:
154
+ """Get a logger with a useful name given a filename"""
155
+ name = re.sub(r".+(howler|test)/", "", name).replace("/", ".").replace(".__init__", "").replace(".py", "")
156
+ name = re.sub(r"^api\.?", "", name)
157
+ logger = init_logging("api")
158
+ if name:
159
+ logger = logger.getChild(name)
160
+ return logger
161
+
162
+
163
+ def get_traceback_info(tb):
164
+ """Prase the traceback information for a given traceback"""
165
+ tb_list = []
166
+ tb_id = 0
167
+ last_ui = None
168
+ while tb is not None:
169
+ f = tb.tb_frame
170
+ line_no = tb.tb_lineno
171
+ tb_list.append((f, line_no))
172
+ tb = tb.tb_next
173
+ if "/ui/" in f.f_code.co_filename:
174
+ last_ui = tb_id
175
+ tb_id += 1
176
+
177
+ if last_ui is not None:
178
+ tb_frame, line = tb_list[last_ui]
179
+ user = tb_frame.f_locals.get("kwargs", {}).get("user", None)
180
+
181
+ if not user:
182
+ temp = tb_frame.f_locals.get("_", {})
183
+ if isinstance(temp, dict):
184
+ user = temp.get("user", None)
185
+
186
+ if not user:
187
+ user = tb_frame.f_locals.get("user", None)
188
+
189
+ if not user:
190
+ user = tb_frame.f_locals.get("impersonator", None)
191
+
192
+ if user:
193
+ return user, tb_frame.f_code.co_filename, tb_frame.f_code.co_name, line
194
+
195
+ return None
196
+
197
+ return None
198
+
199
+
200
+ def __dumb_log(log, msg, is_exception=False):
201
+ """Dumb logger for use with log_with_traceback"""
202
+ args: Union[str, bytes] = request.query_string
203
+ if isinstance(args, bytes):
204
+ args = args.decode()
205
+
206
+ if args:
207
+ args = f"?{args}"
208
+
209
+ message = f"{msg} - {request.path}{args}"
210
+ if is_exception:
211
+ log.exception(message)
212
+ else:
213
+ log.warning(message)
214
+
215
+
216
+ def log_with_traceback(traceback, msg, is_exception=False, audit=False):
217
+ """Log a message along with the stacktrace"""
218
+ log = get_logger("traceback") if not audit else logging.getLogger("howler.api.audit")
219
+
220
+ tb_info = get_traceback_info(traceback)
221
+ if tb_info:
222
+ tb_user, tb_file, tb_function, tb_line_no = tb_info
223
+ args: Optional[Union[str, bytes]] = request.query_string
224
+ if args:
225
+ args = f"?{args if isinstance(args, str) else args.decode()}"
226
+ else:
227
+ args = ""
228
+
229
+ try:
230
+ message = (
231
+ f'{tb_user["uname"]} [{tb_user["classification"]}] :: {msg} - {tb_file}:{tb_function}:{tb_line_no}'
232
+ f'[{os.environ.get("HOWLER_VERSION", "0.0.0.dev0")}] ({request.path}{args})'
233
+ )
234
+ if is_exception:
235
+ log.exception(message)
236
+ else:
237
+ log.warning(message)
238
+ except Exception:
239
+ __dumb_log(log, msg, is_exception=is_exception)
240
+ else:
241
+ __dumb_log(log, msg, is_exception=is_exception)
@@ -0,0 +1,138 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+
5
+ from flask import request
6
+
7
+ from howler.common.logging.format import HWL_AUDIT_FORMAT, HWL_DATE_FORMAT, HWL_ISO_DATE_FORMAT, HWL_LOG_FORMAT
8
+ from howler.config import DEBUG, config
9
+
10
+ AUDIT = config.ui.audit
11
+
12
+ AUDIT_KW_TARGET = [
13
+ "sid",
14
+ "sha256",
15
+ "copy_sid",
16
+ "filter",
17
+ "query",
18
+ "username",
19
+ "group",
20
+ "rev",
21
+ "wq_id",
22
+ "index",
23
+ "cache_key",
24
+ "alert_key",
25
+ "alert_id",
26
+ "url",
27
+ "q",
28
+ "fq",
29
+ "file_hash",
30
+ "heuristic_id",
31
+ "error_key",
32
+ "mac",
33
+ "vm_type",
34
+ "vm_name",
35
+ "config_name",
36
+ "servicename",
37
+ "vm",
38
+ "transition",
39
+ "data",
40
+ "id",
41
+ "comment_id",
42
+ "label_set",
43
+ "tool_name",
44
+ "operation_id",
45
+ "category",
46
+ "label",
47
+ ]
48
+
49
+ AUDIT_LOG = logging.getLogger("howler.api.audit")
50
+ AUDIT_LOG.propagate = False
51
+
52
+ if AUDIT:
53
+ AUDIT_LOG.setLevel(logging.DEBUG)
54
+
55
+ if not os.path.exists(config.logging.log_directory):
56
+ os.makedirs(config.logging.log_directory)
57
+
58
+ fh = logging.FileHandler(os.path.join(config.logging.log_directory, "hwl_audit.log"))
59
+ fh.setLevel(logging.DEBUG)
60
+ fh.setFormatter(
61
+ logging.Formatter(
62
+ HWL_LOG_FORMAT if DEBUG else HWL_AUDIT_FORMAT,
63
+ HWL_DATE_FORMAT if DEBUG else HWL_ISO_DATE_FORMAT,
64
+ )
65
+ )
66
+ AUDIT_LOG.addHandler(fh)
67
+
68
+ ch = logging.StreamHandler(sys.stdout)
69
+ ch.setLevel(logging.INFO)
70
+ ch.setFormatter(
71
+ logging.Formatter(
72
+ HWL_LOG_FORMAT if DEBUG else HWL_AUDIT_FORMAT,
73
+ HWL_DATE_FORMAT if DEBUG else HWL_ISO_DATE_FORMAT,
74
+ )
75
+ )
76
+ AUDIT_LOG.addHandler(ch)
77
+
78
+ #########################
79
+ # End of prepare logger #
80
+ #########################
81
+
82
+
83
+ def audit(args, kwargs, logged_in_uname, user, func, impersonator=None):
84
+ """Log audit information for a given function executed by a given user."""
85
+ try:
86
+ json_blob = request.json
87
+ if not isinstance(json_blob, dict):
88
+ json_blob = {}
89
+ except Exception:
90
+ json_blob = {}
91
+
92
+ try:
93
+ req_args = ["%s='%s'" % (k, v) for k, v in request.args.items() if k in AUDIT_KW_TARGET]
94
+ except RuntimeError:
95
+ req_args = []
96
+
97
+ params_list = (
98
+ list(args)
99
+ + ["%s='%s'" % (k, v) for k, v in kwargs.items() if k in AUDIT_KW_TARGET]
100
+ + req_args
101
+ + ["%s='%s'" % (k, v) for k, v in json_blob.items() if k in AUDIT_KW_TARGET]
102
+ )
103
+
104
+ if impersonator:
105
+ audit_user = f"{impersonator} on behalf of {logged_in_uname}"
106
+ else:
107
+ audit_user = logged_in_uname
108
+ if DEBUG:
109
+ # In debug mode, you'll get an output like:
110
+ # 23/03/20 14:26:56 DEBUG howler.api.audit | goose - search(index='...', query='...')
111
+ AUDIT_LOG.debug(
112
+ "%s - %s(%s)",
113
+ audit_user,
114
+ func.__name__,
115
+ ", ".join(params_list),
116
+ )
117
+ else:
118
+ # In prod, you'll get an output like:
119
+ # {
120
+ # "date": "2023-03-20T18:33:27-0400",
121
+ # "type": "audit",
122
+ # "app_name": "howler",
123
+ # "api": "howler.api.audit",
124
+ # "severity": "INFO",
125
+ # "user": "goose",
126
+ # "function": "search(index='hit', query='howler.escalation:alert AND howler.status:open')",
127
+ # "method": "POST",
128
+ # "path": "/api/v1/search/hit/"
129
+ # }
130
+ AUDIT_LOG.info(
131
+ "",
132
+ extra={
133
+ "user": audit_user,
134
+ "function": f"{func.__name__}({', '.join(params_list)})",
135
+ "method": request.method,
136
+ "path": request.path,
137
+ },
138
+ )
@@ -0,0 +1,38 @@
1
+ import json
2
+ import os
3
+
4
+ APP_NAME = os.environ.get("APP_NAME", "howler")
5
+
6
+ try:
7
+ from howler.common.net import get_hostname
8
+
9
+ hostname = get_hostname()
10
+ except Exception:
11
+ hostname = "unknownhost"
12
+
13
+ HWL_SYSLOG_FORMAT = f"HWL %(levelname)8s {hostname} %(process)5d %(name)40s | %(message)s"
14
+ HWL_LOG_FORMAT = "%(asctime)s %(levelname)s %(name)s | %(message)s"
15
+ HWL_DATE_FORMAT = "%y/%m/%d %H:%M:%S"
16
+ HWL_JSON_FORMAT = (
17
+ f"{{"
18
+ f'"@timestamp": "%(asctime)s", '
19
+ f'"event": {{ "module": "{APP_NAME}", "dataset": "%(name)s" }}, '
20
+ f'"host": {{ "hostname": "{hostname}" }}, '
21
+ f'"log": {{ "level": "%(levelname)s", "logger": "%(name)s" }}, '
22
+ f'"process": {{ "pid": "%(process)d" }}, '
23
+ f'"message": %(message)s}}'
24
+ )
25
+ HWL_ISO_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
26
+ HWL_AUDIT_FORMAT = json.dumps(
27
+ {
28
+ "date": "%(asctime)s",
29
+ "type": "audit",
30
+ "app_name": APP_NAME,
31
+ "api": "howler.api.audit",
32
+ "severity": "%(levelname)s",
33
+ "user": "%(user)s",
34
+ "function": "%(function)s",
35
+ "method": "%(method)s",
36
+ "path": "%(path)s",
37
+ }
38
+ ).replace('"msg"', "%(message)s")
howler/common/net.py ADDED
@@ -0,0 +1,79 @@
1
+ import socket
2
+ import uuid
3
+ from ipaddress import IPv4Network, ip_address
4
+ from typing import Union
5
+
6
+ from howler.common.net_static import TLDS_ALPHA_BY_DOMAIN
7
+
8
+
9
+ def is_valid_port(value: Union[int, str, float]) -> bool:
10
+ "Check if a port is valid"
11
+ try:
12
+ if 1 <= int(value) <= 65535:
13
+ return True
14
+ except ValueError:
15
+ pass
16
+
17
+ return False
18
+
19
+
20
+ def is_valid_domain(domain: str) -> bool:
21
+ "Check if a domain is valid"
22
+ if "@" in domain:
23
+ return False
24
+
25
+ if "." in domain:
26
+ tld = domain.split(".")[-1]
27
+ return tld.upper() in TLDS_ALPHA_BY_DOMAIN
28
+
29
+ return False
30
+
31
+
32
+ def is_valid_ip(ip: str) -> bool:
33
+ "Check if an ip is valid"
34
+ parts = ip.split(".")
35
+ if len(parts) == 4:
36
+ for p in parts:
37
+ try:
38
+ if not (0 <= int(p) <= 255):
39
+ return False
40
+ except ValueError:
41
+ return False
42
+
43
+ if int(parts[0]) == 0:
44
+ return False
45
+
46
+ if int(parts[3]) == 0:
47
+ return False
48
+
49
+ return True
50
+
51
+ return False
52
+
53
+
54
+ def is_ip_in_network(ip: str, network: IPv4Network) -> bool:
55
+ "Check if an ip is in a given network"
56
+ if not is_valid_ip(ip):
57
+ return False
58
+
59
+ return ip_address(ip) in network
60
+
61
+
62
+ def is_valid_email(email: str) -> bool:
63
+ "Check if an email is valid"
64
+ parts = email.split("@")
65
+ if len(parts) == 2:
66
+ if is_valid_domain(parts[1]):
67
+ return True
68
+
69
+ return False
70
+
71
+
72
+ def get_hostname() -> str:
73
+ "Get the hostname of the computer howler is running on"
74
+ return socket.gethostname()
75
+
76
+
77
+ def get_mac_address() -> str:
78
+ "Get the mac address of the computer howler is running on"
79
+ return "".join(["{0:02x}".format((uuid.getnode() >> i) & 0xFF) for i in range(0, 8 * 6, 8)][::-1]).upper()