howler-api 3.0.0.dev374__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.
- howler/__init__.py +0 -0
- howler/actions/__init__.py +168 -0
- howler/actions/add_label.py +111 -0
- howler/actions/add_to_bundle.py +159 -0
- howler/actions/change_field.py +76 -0
- howler/actions/demote.py +160 -0
- howler/actions/example_plugin.py +104 -0
- howler/actions/prioritization.py +93 -0
- howler/actions/promote.py +147 -0
- howler/actions/remove_from_bundle.py +133 -0
- howler/actions/remove_label.py +111 -0
- howler/actions/transition.py +200 -0
- howler/api/__init__.py +249 -0
- howler/api/base.py +88 -0
- howler/api/socket.py +114 -0
- howler/api/v1/__init__.py +97 -0
- howler/api/v1/action.py +372 -0
- howler/api/v1/analytic.py +748 -0
- howler/api/v1/auth.py +382 -0
- howler/api/v1/clue.py +99 -0
- howler/api/v1/configs.py +58 -0
- howler/api/v1/dossier.py +222 -0
- howler/api/v1/help.py +28 -0
- howler/api/v1/hit.py +1181 -0
- howler/api/v1/notebook.py +82 -0
- howler/api/v1/overview.py +191 -0
- howler/api/v1/search.py +788 -0
- howler/api/v1/template.py +206 -0
- howler/api/v1/tool.py +183 -0
- howler/api/v1/user.py +416 -0
- howler/api/v1/utils/__init__.py +0 -0
- howler/api/v1/utils/etag.py +84 -0
- howler/api/v1/view.py +288 -0
- howler/app.py +235 -0
- howler/common/README.md +125 -0
- howler/common/__init__.py +0 -0
- howler/common/classification.py +979 -0
- howler/common/classification.yml +107 -0
- howler/common/exceptions.py +167 -0
- howler/common/loader.py +154 -0
- howler/common/logging/__init__.py +241 -0
- howler/common/logging/audit.py +138 -0
- howler/common/logging/format.py +38 -0
- howler/common/net.py +79 -0
- howler/common/net_static.py +1494 -0
- howler/common/random_user.py +316 -0
- howler/common/swagger.py +117 -0
- howler/config.py +64 -0
- howler/cronjobs/__init__.py +29 -0
- howler/cronjobs/retention.py +61 -0
- howler/cronjobs/rules.py +274 -0
- howler/cronjobs/view_cleanup.py +88 -0
- howler/datastore/README.md +112 -0
- howler/datastore/__init__.py +0 -0
- howler/datastore/bulk.py +72 -0
- howler/datastore/collection.py +2342 -0
- howler/datastore/constants.py +119 -0
- howler/datastore/exceptions.py +41 -0
- howler/datastore/howler_store.py +105 -0
- howler/datastore/migrations/fix_process.py +41 -0
- howler/datastore/operations.py +130 -0
- howler/datastore/schemas.py +90 -0
- howler/datastore/store.py +231 -0
- howler/datastore/support/__init__.py +0 -0
- howler/datastore/support/build.py +215 -0
- howler/datastore/support/schemas.py +90 -0
- howler/datastore/types.py +22 -0
- howler/error.py +91 -0
- howler/external/__init__.py +0 -0
- howler/external/generate_mitre.py +96 -0
- howler/external/generate_sigma_rules.py +31 -0
- howler/external/generate_tlds.py +47 -0
- howler/external/reindex_data.py +66 -0
- howler/external/wipe_databases.py +58 -0
- howler/gunicorn_config.py +25 -0
- howler/healthz.py +47 -0
- howler/helper/__init__.py +0 -0
- howler/helper/azure.py +50 -0
- howler/helper/discover.py +59 -0
- howler/helper/hit.py +236 -0
- howler/helper/oauth.py +247 -0
- howler/helper/search.py +92 -0
- howler/helper/workflow.py +110 -0
- howler/helper/ws.py +378 -0
- howler/odm/README.md +102 -0
- howler/odm/__init__.py +1 -0
- howler/odm/base.py +1543 -0
- howler/odm/charter.txt +146 -0
- howler/odm/helper.py +416 -0
- howler/odm/howler_enum.py +25 -0
- howler/odm/models/__init__.py +0 -0
- howler/odm/models/action.py +33 -0
- howler/odm/models/analytic.py +90 -0
- howler/odm/models/assemblyline.py +48 -0
- howler/odm/models/aws.py +23 -0
- howler/odm/models/azure.py +16 -0
- howler/odm/models/cbs.py +44 -0
- howler/odm/models/config.py +558 -0
- howler/odm/models/dossier.py +33 -0
- howler/odm/models/ecs/__init__.py +0 -0
- howler/odm/models/ecs/agent.py +17 -0
- howler/odm/models/ecs/autonomous_system.py +16 -0
- howler/odm/models/ecs/client.py +149 -0
- howler/odm/models/ecs/cloud.py +141 -0
- howler/odm/models/ecs/code_signature.py +27 -0
- howler/odm/models/ecs/container.py +32 -0
- howler/odm/models/ecs/dns.py +62 -0
- howler/odm/models/ecs/egress.py +10 -0
- howler/odm/models/ecs/elf.py +74 -0
- howler/odm/models/ecs/email.py +122 -0
- howler/odm/models/ecs/error.py +14 -0
- howler/odm/models/ecs/event.py +140 -0
- howler/odm/models/ecs/faas.py +24 -0
- howler/odm/models/ecs/file.py +84 -0
- howler/odm/models/ecs/geo.py +30 -0
- howler/odm/models/ecs/group.py +18 -0
- howler/odm/models/ecs/hash.py +16 -0
- howler/odm/models/ecs/host.py +17 -0
- howler/odm/models/ecs/http.py +37 -0
- howler/odm/models/ecs/ingress.py +12 -0
- howler/odm/models/ecs/interface.py +21 -0
- howler/odm/models/ecs/network.py +30 -0
- howler/odm/models/ecs/observer.py +45 -0
- howler/odm/models/ecs/organization.py +12 -0
- howler/odm/models/ecs/os.py +21 -0
- howler/odm/models/ecs/pe.py +17 -0
- howler/odm/models/ecs/process.py +216 -0
- howler/odm/models/ecs/registry.py +26 -0
- howler/odm/models/ecs/related.py +45 -0
- howler/odm/models/ecs/rule.py +51 -0
- howler/odm/models/ecs/server.py +24 -0
- howler/odm/models/ecs/threat.py +247 -0
- howler/odm/models/ecs/tls.py +58 -0
- howler/odm/models/ecs/url.py +51 -0
- howler/odm/models/ecs/user.py +57 -0
- howler/odm/models/ecs/user_agent.py +20 -0
- howler/odm/models/ecs/vulnerability.py +41 -0
- howler/odm/models/gcp.py +16 -0
- howler/odm/models/hit.py +356 -0
- howler/odm/models/howler_data.py +328 -0
- howler/odm/models/lead.py +24 -0
- howler/odm/models/localized_label.py +13 -0
- howler/odm/models/overview.py +16 -0
- howler/odm/models/pivot.py +40 -0
- howler/odm/models/template.py +24 -0
- howler/odm/models/user.py +83 -0
- howler/odm/models/view.py +34 -0
- howler/odm/random_data.py +888 -0
- howler/odm/randomizer.py +609 -0
- howler/patched.py +5 -0
- howler/plugins/__init__.py +25 -0
- howler/plugins/config.py +123 -0
- howler/remote/__init__.py +0 -0
- howler/remote/datatypes/README.md +355 -0
- howler/remote/datatypes/__init__.py +98 -0
- howler/remote/datatypes/counters.py +63 -0
- howler/remote/datatypes/events.py +66 -0
- howler/remote/datatypes/hash.py +206 -0
- howler/remote/datatypes/lock.py +42 -0
- howler/remote/datatypes/queues/__init__.py +0 -0
- howler/remote/datatypes/queues/comms.py +59 -0
- howler/remote/datatypes/queues/multi.py +32 -0
- howler/remote/datatypes/queues/named.py +93 -0
- howler/remote/datatypes/queues/priority.py +215 -0
- howler/remote/datatypes/set.py +118 -0
- howler/remote/datatypes/user_quota_tracker.py +54 -0
- howler/security/__init__.py +253 -0
- howler/security/socket.py +108 -0
- howler/security/utils.py +185 -0
- howler/services/__init__.py +0 -0
- howler/services/action_service.py +111 -0
- howler/services/analytic_service.py +128 -0
- howler/services/auth_service.py +323 -0
- howler/services/config_service.py +128 -0
- howler/services/dossier_service.py +252 -0
- howler/services/event_service.py +93 -0
- howler/services/hit_service.py +893 -0
- howler/services/jwt_service.py +158 -0
- howler/services/lucene_service.py +286 -0
- howler/services/notebook_service.py +119 -0
- howler/services/overview_service.py +44 -0
- howler/services/template_service.py +45 -0
- howler/services/user_service.py +331 -0
- howler/utils/__init__.py +0 -0
- howler/utils/annotations.py +28 -0
- howler/utils/chunk.py +38 -0
- howler/utils/dict_utils.py +200 -0
- howler/utils/isotime.py +17 -0
- howler/utils/list_utils.py +11 -0
- howler/utils/lucene.py +77 -0
- howler/utils/path.py +27 -0
- howler/utils/socket_utils.py +61 -0
- howler/utils/str_utils.py +256 -0
- howler/utils/uid.py +47 -0
- howler_api-3.0.0.dev374.dist-info/METADATA +71 -0
- howler_api-3.0.0.dev374.dist-info/RECORD +198 -0
- howler_api-3.0.0.dev374.dist-info/WHEEL +4 -0
- howler_api-3.0.0.dev374.dist-info/entry_points.txt +8 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from howler import odm
|
|
5
|
+
from howler.common.exceptions import HowlerValueError
|
|
6
|
+
from howler.odm.howler_enum import HowlerEnum
|
|
7
|
+
from howler.odm.models.lead import Lead
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Scrutiny(str, HowlerEnum):
|
|
11
|
+
UNSEEN = "unseen"
|
|
12
|
+
SURVEYED = "surveyed"
|
|
13
|
+
SCANNED = "scanned"
|
|
14
|
+
INSPECTED = "inspected"
|
|
15
|
+
INVESTIGATED = "investigated"
|
|
16
|
+
|
|
17
|
+
def __str__(self) -> str:
|
|
18
|
+
return self.value
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class HitStatus(str, HowlerEnum):
|
|
22
|
+
OPEN = "open"
|
|
23
|
+
IN_PROGRESS = "in-progress"
|
|
24
|
+
ON_HOLD = "on-hold"
|
|
25
|
+
RESOLVED = "resolved"
|
|
26
|
+
|
|
27
|
+
def __str__(self) -> str:
|
|
28
|
+
return self.value
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class HitStatusTransition(str, HowlerEnum):
|
|
32
|
+
ASSIGN_TO_ME = "assign_to_me"
|
|
33
|
+
ASSIGN_TO_OTHER = "assign_to_other"
|
|
34
|
+
VOTE = "vote"
|
|
35
|
+
ASSESS = "assess"
|
|
36
|
+
RELEASE = "release"
|
|
37
|
+
START = "start"
|
|
38
|
+
PAUSE = "pause"
|
|
39
|
+
RESUME = "resume"
|
|
40
|
+
RE_EVALUATE = "re_evaluate"
|
|
41
|
+
PROMOTE = "promote"
|
|
42
|
+
DEMOTE = "demote"
|
|
43
|
+
|
|
44
|
+
def __str__(self) -> str:
|
|
45
|
+
return self.value
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class HitOperationType(str, HowlerEnum):
|
|
49
|
+
APPENDED = "appended"
|
|
50
|
+
REMOVED = "removed"
|
|
51
|
+
SET = "set"
|
|
52
|
+
|
|
53
|
+
def __str__(self) -> str:
|
|
54
|
+
return self.value
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Escalation(str, HowlerEnum):
|
|
58
|
+
MISS = "miss"
|
|
59
|
+
HIT = "hit"
|
|
60
|
+
ALERT = "alert"
|
|
61
|
+
EVIDENCE = "evidence"
|
|
62
|
+
|
|
63
|
+
def __str__(self) -> str:
|
|
64
|
+
return self.value
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Vote(str, HowlerEnum):
|
|
68
|
+
MALICIOUS = "malicious"
|
|
69
|
+
OBSCURE = "obscure"
|
|
70
|
+
BEINIGN = "benign"
|
|
71
|
+
|
|
72
|
+
def __str__(self) -> str:
|
|
73
|
+
return self.value
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Assessment(str, HowlerEnum):
|
|
77
|
+
# Keep this order!
|
|
78
|
+
AMBIGUOUS = "ambiguous"
|
|
79
|
+
SECURITY = "security"
|
|
80
|
+
DEVELOPMENT = "development"
|
|
81
|
+
FALSE_POSITIVE = "false-positive"
|
|
82
|
+
LEGITIMATE = "legitimate"
|
|
83
|
+
|
|
84
|
+
TRIVIAL = "trivial"
|
|
85
|
+
RECON = "recon"
|
|
86
|
+
ATTEMPT = "attempt"
|
|
87
|
+
COMPROMISE = "compromise"
|
|
88
|
+
MITIGATED = "mitigated"
|
|
89
|
+
|
|
90
|
+
def __str__(self) -> str:
|
|
91
|
+
return self.value
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class AssessmentEscalationMap(str, HowlerEnum):
|
|
95
|
+
AMBIGUOUS = Escalation.MISS.value
|
|
96
|
+
ATTEMPT = Escalation.EVIDENCE.value
|
|
97
|
+
COMPROMISE = Escalation.EVIDENCE.value
|
|
98
|
+
DEVELOPMENT = Escalation.MISS.value
|
|
99
|
+
FALSE_POSITIVE = Escalation.MISS.value
|
|
100
|
+
LEGITIMATE = Escalation.MISS.value
|
|
101
|
+
MITIGATED = Escalation.EVIDENCE.value
|
|
102
|
+
RECON = Escalation.EVIDENCE.value
|
|
103
|
+
SECURITY = Escalation.MISS.value
|
|
104
|
+
TRIVIAL = Escalation.EVIDENCE.value
|
|
105
|
+
|
|
106
|
+
def __int__(self) -> int:
|
|
107
|
+
return self.value
|
|
108
|
+
|
|
109
|
+
def __str__(self) -> str:
|
|
110
|
+
return str(self.value)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@odm.model(index=True, store=True, description="Howler Link definition.")
|
|
114
|
+
class Link(odm.Model):
|
|
115
|
+
href = odm.Keyword(description="Timestamp at which the comment was last edited.")
|
|
116
|
+
title = odm.Keyword(description="The title to use for the link.", optional=True)
|
|
117
|
+
icon = odm.Keyword(
|
|
118
|
+
description=(
|
|
119
|
+
"The icon to show. Either an ID corresponding to an "
|
|
120
|
+
"analytical platform application, or an external link."
|
|
121
|
+
),
|
|
122
|
+
optional=True,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@odm.model(index=True, store=True, description="Comment definition.")
|
|
127
|
+
class Comment(odm.Model):
|
|
128
|
+
id = odm.UUID(description="A unique ID for the comment.")
|
|
129
|
+
timestamp = odm.Date(description="Timestamp at which the comment took place.", default="NOW")
|
|
130
|
+
modified = odm.Date(description="Timestamp at which the comment was last edited.", default="NOW")
|
|
131
|
+
value = odm.Text(description="The comment itself.")
|
|
132
|
+
user = odm.Keyword(description="User ID who created the comment.")
|
|
133
|
+
reactions: dict[str, str] = odm.Mapping(
|
|
134
|
+
odm.Keyword(),
|
|
135
|
+
default={},
|
|
136
|
+
description="A list of reactions to the comment.",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@odm.model(index=True, store=True, description="Log definition.")
|
|
141
|
+
class Log(odm.Model):
|
|
142
|
+
timestamp = odm.Date(description="Timestamp at which the Log event took place.")
|
|
143
|
+
key = odm.Optional(odm.Keyword(description="The key whose value changed."))
|
|
144
|
+
explanation = odm.Optional(odm.Text(description="A manual description of the changes made."))
|
|
145
|
+
previous_version = odm.Optional(odm.Keyword(description="The version this action was applied to."))
|
|
146
|
+
new_value = odm.Optional(odm.Keyword(description="The value the key is changing to."))
|
|
147
|
+
type = odm.Optional(odm.Enum(values=HitOperationType, description="The operation performed on the value."))
|
|
148
|
+
previous_value = odm.Optional(odm.Keyword(description="The value the key is changing from."))
|
|
149
|
+
user = odm.Keyword(description="User ID who created the log event.")
|
|
150
|
+
|
|
151
|
+
def __init__(self, data: dict = None, *args, **kwargs):
|
|
152
|
+
if "explanation" not in data:
|
|
153
|
+
required_keys = {"key", "new_value", "type", "previous_value"}
|
|
154
|
+
if required_keys.intersection(set(data.keys())) != required_keys:
|
|
155
|
+
raise HowlerValueError(
|
|
156
|
+
f"If no explanation provided, you must provide the following values: {','.join(required_keys)}"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
super().__init__(data, *args, **kwargs)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@odm.model(index=True, store=True, description="Hit outline header.")
|
|
163
|
+
class Header(odm.Model):
|
|
164
|
+
threat: Optional[str] = odm.Optional(odm.Keyword(description="The IP of the threat."))
|
|
165
|
+
target: Optional[str] = odm.Optional(odm.Keyword(description="The target of the hit."))
|
|
166
|
+
indicators: list[str] = odm.List(odm.Keyword(description="Indicators of the hit."), default=[])
|
|
167
|
+
summary: Optional[str] = odm.Optional(odm.Keyword(description="Summary of the hit."))
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@odm.model(index=True, store=True, description="Fields describing the location where this alert has been retained.")
|
|
171
|
+
class Incident(odm.Model):
|
|
172
|
+
platform: str = odm.Keyword(description="The name of the platform for this incident.")
|
|
173
|
+
incident_id: Optional[str] = odm.Keyword(description="The ID of the incident.", optional=True)
|
|
174
|
+
url: Optional[str] = odm.Keyword(description="The url where the incident can be found.", optional=True)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@odm.model(index=True, store=True, description="Labels for the hit")
|
|
178
|
+
class Label(odm.Model):
|
|
179
|
+
assignments = odm.List(odm.Keyword(description="List of assignments for the hit."), default=[])
|
|
180
|
+
generic = odm.List(odm.Keyword(description="List of generic labels for the hit."), default=[])
|
|
181
|
+
insight = odm.List(odm.Keyword(description="List of insight labels for the hit."), default=[])
|
|
182
|
+
mitigation = odm.List(odm.Keyword(description="List of mitigation labels for the hit."), default=[])
|
|
183
|
+
victim = odm.List(odm.Keyword(description="List of victim labels for the hit."), default=[])
|
|
184
|
+
campaign = odm.List(odm.Keyword(description="List of campaign labels for the hit."), default=[])
|
|
185
|
+
threat = odm.List(odm.Keyword(description="List of threat labels for the hit."), default=[])
|
|
186
|
+
tuning = odm.List(odm.Keyword(description="List of tuning labels for the hit."), default=[])
|
|
187
|
+
operation = odm.List(odm.Keyword(description="List of operation labels for the hit."), default=[])
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@odm.model(index=True, store=True, description="Votes for the hit")
|
|
191
|
+
class Votes(odm.Model):
|
|
192
|
+
benign: list[str] = odm.List(odm.Keyword(), default=[], description="List of users who voted benign.")
|
|
193
|
+
obscure: list[str] = odm.List(odm.Keyword(), default=[], description="List of users who voted obscure.")
|
|
194
|
+
malicious: list[str] = odm.List(odm.Keyword(), default=[], description="List of users who voted malicious.")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
DEFAULT_VOTES = {vote: [] for vote in Vote.list()}
|
|
198
|
+
DEFAULT_LABELS = {"assignments": [], "generic": []}
|
|
199
|
+
DEFAULT_ASSIGNMENT = "unassigned"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@odm.model(
|
|
203
|
+
index=True,
|
|
204
|
+
store=True,
|
|
205
|
+
description="Howler specific definition of the hit that matches the outline.",
|
|
206
|
+
)
|
|
207
|
+
class HowlerData(odm.Model):
|
|
208
|
+
id: str = odm.UUID(description="A UUID for this hit.")
|
|
209
|
+
analytic: str = odm.CaseInsensitiveKeyword(description="Title of the analytic.")
|
|
210
|
+
assignment: Optional[str] = odm.Keyword(
|
|
211
|
+
description="Unique identifier of the assigned user.",
|
|
212
|
+
default=DEFAULT_ASSIGNMENT,
|
|
213
|
+
)
|
|
214
|
+
bundles: list[str] = odm.List(
|
|
215
|
+
odm.Keyword(
|
|
216
|
+
description="A list of bundle IDs this hit is a part of. Corresponds to the howler.id of the bundle."
|
|
217
|
+
),
|
|
218
|
+
default=[],
|
|
219
|
+
)
|
|
220
|
+
data: list[str] = odm.List(
|
|
221
|
+
odm.Keyword(description="Raw telemetry records associated with this hit."),
|
|
222
|
+
default=[],
|
|
223
|
+
store=False,
|
|
224
|
+
)
|
|
225
|
+
links: list[Link] = odm.List(
|
|
226
|
+
odm.Compound(Link),
|
|
227
|
+
default=[],
|
|
228
|
+
description="A list of links associated with this hit.",
|
|
229
|
+
)
|
|
230
|
+
detection: Optional[str] = odm.Optional(
|
|
231
|
+
odm.CaseInsensitiveKeyword(description="The detection that produced this hit.")
|
|
232
|
+
)
|
|
233
|
+
hash: str = odm.HowlerHash(
|
|
234
|
+
description=(
|
|
235
|
+
"A hash of the event used for deduplicating hits. Supports any hexadecimal string between 1 "
|
|
236
|
+
"and 64 characters long."
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
hits: list[str] = odm.List(
|
|
240
|
+
odm.Keyword(
|
|
241
|
+
description="A list of hit IDs this bundle represents. Corresponds to the howler.id of the child hit."
|
|
242
|
+
),
|
|
243
|
+
default=[],
|
|
244
|
+
)
|
|
245
|
+
bundle_size: int = odm.Integer(
|
|
246
|
+
description="Number of hits in bundle",
|
|
247
|
+
default=0,
|
|
248
|
+
)
|
|
249
|
+
is_bundle: bool = odm.Boolean(description="Is this hit a bundle or a normal hit?", default=False)
|
|
250
|
+
related: list[str] = odm.List(
|
|
251
|
+
odm.Keyword(
|
|
252
|
+
description="Related hits grouped by the enrichment that correlated them. Populated by enrichments."
|
|
253
|
+
),
|
|
254
|
+
default=[],
|
|
255
|
+
)
|
|
256
|
+
reliability: Optional[float] = odm.Optional(
|
|
257
|
+
odm.Float(description="Metric decoupled from the value in the detection information.")
|
|
258
|
+
)
|
|
259
|
+
severity: Optional[float] = odm.Optional(
|
|
260
|
+
odm.Float(description="Metric decoupled from the value in the detection information.")
|
|
261
|
+
)
|
|
262
|
+
volume: Optional[float] = odm.Optional(
|
|
263
|
+
odm.Float(description="Metric decoupled from the value in the detection information.")
|
|
264
|
+
)
|
|
265
|
+
confidence: Optional[float] = odm.Optional(
|
|
266
|
+
odm.Float(description="Metric decoupled from the value in the detection information.")
|
|
267
|
+
)
|
|
268
|
+
score: Optional[float] = odm.Optional(
|
|
269
|
+
odm.Float(description="A score assigned by an enrichment to help prioritize triage.", default=0)
|
|
270
|
+
)
|
|
271
|
+
status = odm.Enum(values=HitStatus, default=HitStatus.OPEN, description="Status of the hit.")
|
|
272
|
+
scrutiny = odm.Enum(
|
|
273
|
+
values=Scrutiny,
|
|
274
|
+
default=Scrutiny.UNSEEN,
|
|
275
|
+
description="Level of scrutiny done to this hit.",
|
|
276
|
+
)
|
|
277
|
+
escalation = odm.Enum(
|
|
278
|
+
values=Escalation,
|
|
279
|
+
default=Escalation.HIT,
|
|
280
|
+
description="Level of escalation of this hit.",
|
|
281
|
+
)
|
|
282
|
+
expiry = odm.Optional(
|
|
283
|
+
odm.Date(
|
|
284
|
+
description="User selected time for hit expiry",
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
assessment: Optional[str] = odm.Optional(odm.Enum(values=Assessment, description="Assessment of the hit."))
|
|
288
|
+
rationale: Optional[str] = odm.Optional(
|
|
289
|
+
odm.Keyword(
|
|
290
|
+
description=(
|
|
291
|
+
"The rationale behind the hit assessment. Allows it to be understood and" " verified by other analysts."
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
comment: list[Comment] = odm.List(
|
|
296
|
+
odm.Compound(Comment),
|
|
297
|
+
default=[],
|
|
298
|
+
description="A list of comments with timestamps and attribution.",
|
|
299
|
+
)
|
|
300
|
+
log: list[Log] = odm.List(
|
|
301
|
+
odm.Compound(Log),
|
|
302
|
+
default=[],
|
|
303
|
+
description="A list of changes to the hit with timestamps and attribution.",
|
|
304
|
+
)
|
|
305
|
+
monitored: Optional[str] = odm.Optional(odm.Keyword(description="Link to the incident monitoring dashboard."))
|
|
306
|
+
reported: Optional[str] = odm.Optional(odm.Keyword(description="Link to the incident report."))
|
|
307
|
+
mitigated: Optional[str] = odm.Optional(odm.Keyword(description="Link to the mitigation record (tool dependent)."))
|
|
308
|
+
outline: Optional[Header] = odm.Optional(odm.Compound(Header), description="The user specified header of the hit")
|
|
309
|
+
incidents: list[Incident] = odm.List(
|
|
310
|
+
odm.Compound(Incident), default=[], description="Fields describing an incident associated with this alert."
|
|
311
|
+
)
|
|
312
|
+
labels: Label = odm.Optional(
|
|
313
|
+
odm.Compound(Label),
|
|
314
|
+
default=DEFAULT_LABELS,
|
|
315
|
+
description="List of labels relating to the hit",
|
|
316
|
+
)
|
|
317
|
+
votes: Votes = odm.Optional(
|
|
318
|
+
odm.Compound(Votes),
|
|
319
|
+
default=DEFAULT_VOTES,
|
|
320
|
+
description="Votes relating to the hit",
|
|
321
|
+
)
|
|
322
|
+
dossier: list[Lead] = odm.List(
|
|
323
|
+
odm.Compound(Lead), default=[], description="A list of leads forming the dossier associated with this hit"
|
|
324
|
+
)
|
|
325
|
+
viewers: list[str] = odm.List(
|
|
326
|
+
odm.Keyword(description="A list of users currently viewing the hit"),
|
|
327
|
+
default=[],
|
|
328
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from howler import odm
|
|
5
|
+
from howler.odm.models.localized_label import LocalizedLabel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@odm.model(
|
|
9
|
+
index=False,
|
|
10
|
+
store=True,
|
|
11
|
+
description="The dossier object stores individual tabs/fields for a given alert.",
|
|
12
|
+
)
|
|
13
|
+
class Lead(odm.Model):
|
|
14
|
+
icon: Optional[str] = odm.Text(
|
|
15
|
+
description="An optional icon to use in the tab display for this dossier.", optional=True
|
|
16
|
+
)
|
|
17
|
+
label: LocalizedLabel = odm.Compound(LocalizedLabel, description="Labels for the lead in the UI.")
|
|
18
|
+
format: str = odm.Keyword(description="The format of the lead.")
|
|
19
|
+
content: str = odm.Text(
|
|
20
|
+
description="The data for the content. Could be a link, raw markdown text, or other valid lead format.",
|
|
21
|
+
)
|
|
22
|
+
metadata: Optional[str] = odm.Json(
|
|
23
|
+
optional=True, description="Metadata associated with this dossier. Use varies based on format."
|
|
24
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
|
|
3
|
+
from howler import odm
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@odm.model(
|
|
7
|
+
index=True,
|
|
8
|
+
store=True,
|
|
9
|
+
description="The dossier object stores individual tabs/fields for a given alert.",
|
|
10
|
+
)
|
|
11
|
+
class LocalizedLabel(odm.Model):
|
|
12
|
+
en: str = odm.Text(description="The english localization of a label")
|
|
13
|
+
fr: str = odm.Text(description="The french localization of a label")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from howler import odm
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@odm.model(index=True, store=True, description="Model of overviews")
|
|
8
|
+
class Overview(odm.Model):
|
|
9
|
+
overview_id: str = odm.UUID(description="A UUID for this overview")
|
|
10
|
+
analytic: str = odm.Keyword(description="The analytic which this overview applies to.")
|
|
11
|
+
detection: Optional[str] = odm.Keyword(description="The detection which this overview applies to.", optional=True)
|
|
12
|
+
owner: Optional[str] = odm.Keyword(
|
|
13
|
+
description="The person to whom this overview belongs.",
|
|
14
|
+
optional=True,
|
|
15
|
+
)
|
|
16
|
+
content: str = odm.Keyword(description="The markdown to show when this overview is used.")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from howler import odm
|
|
5
|
+
from howler.odm.models.localized_label import LocalizedLabel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@odm.model(
|
|
9
|
+
index=True,
|
|
10
|
+
store=True,
|
|
11
|
+
description="The .",
|
|
12
|
+
)
|
|
13
|
+
class Mapping(odm.Model):
|
|
14
|
+
key: str = odm.Keyword(
|
|
15
|
+
description="The key to inject the given field as. Exact behaviour depends on the implementation type."
|
|
16
|
+
)
|
|
17
|
+
field: str = odm.Keyword(description="The field in the hit to associate with the given key.")
|
|
18
|
+
custom_value: Optional[str] = odm.Keyword(
|
|
19
|
+
description="An optional custom value to use if the value is not dependent on the alert we are pivoting on",
|
|
20
|
+
optional=True,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@odm.model(
|
|
25
|
+
index=False,
|
|
26
|
+
store=True,
|
|
27
|
+
description="The dossier object stores individual tabs/fields for a given alert.",
|
|
28
|
+
)
|
|
29
|
+
class Pivot(odm.Model):
|
|
30
|
+
icon: Optional[str] = odm.Text(
|
|
31
|
+
description="An optional icon to use in the tab display for this dossier.", optional=True
|
|
32
|
+
)
|
|
33
|
+
label: LocalizedLabel = odm.Compound(LocalizedLabel, description="Labels for the pivot in the UI.")
|
|
34
|
+
value: str = odm.Keyword(description="The link/plugin information to pivot on.")
|
|
35
|
+
format: str = odm.Keyword(description="The format of the pivot.")
|
|
36
|
+
mappings: list[Mapping] = odm.List(
|
|
37
|
+
odm.Compound(Mapping),
|
|
38
|
+
default=[],
|
|
39
|
+
description="A list of the mappings to use when activating a pivot.",
|
|
40
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
from typing import Literal, Optional, Union
|
|
3
|
+
|
|
4
|
+
from howler import odm
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@odm.model(index=True, store=True, description="Model of templates")
|
|
8
|
+
class Template(odm.Model):
|
|
9
|
+
template_id: str = odm.UUID(description="A UUID for this template")
|
|
10
|
+
analytic: str = odm.Keyword(description="The analytic which this template applies to.")
|
|
11
|
+
detection: Optional[str] = odm.Keyword(description="The detection which this template applies to.", optional=True)
|
|
12
|
+
type: Union[Literal["personal"], Literal["global"]] = odm.Enum(
|
|
13
|
+
values=["personal", "global"],
|
|
14
|
+
description="The type of template - personal or global?",
|
|
15
|
+
)
|
|
16
|
+
owner: Optional[str] = odm.Keyword(
|
|
17
|
+
description="The person to whom this template belongs. Applies to personal templates only.",
|
|
18
|
+
optional=True,
|
|
19
|
+
)
|
|
20
|
+
keys: list[str] = odm.List(
|
|
21
|
+
odm.Keyword(),
|
|
22
|
+
default=[],
|
|
23
|
+
description="The list of fields to show when this template is used.",
|
|
24
|
+
)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from howler import odm
|
|
6
|
+
from howler.common import loader
|
|
7
|
+
from howler.config import CLASSIFICATION
|
|
8
|
+
|
|
9
|
+
ACL = {"R", "W", "E", "I"}
|
|
10
|
+
|
|
11
|
+
DASHBOARD_TYPES = {"view", "analytic"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@odm.model(index=False, store=False, description="Model for API keys")
|
|
15
|
+
class ApiKey(odm.Model):
|
|
16
|
+
acl: list[str] = odm.List(odm.Enum(values=ACL), description="Access Control List for the API key")
|
|
17
|
+
agents: list[str] = odm.List(
|
|
18
|
+
odm.Keyword(),
|
|
19
|
+
default=[],
|
|
20
|
+
description="List of user ids permitted to use this api key for impersonation",
|
|
21
|
+
)
|
|
22
|
+
password: str = odm.Keyword(description="BCrypt hash of the password for the apikey")
|
|
23
|
+
expiry_date: Optional[datetime] = odm.Optional(odm.Date(description="Expiry date for the apikey"))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@odm.model(index=False, store=False, description="Model for user dashboard settings")
|
|
27
|
+
class DashboardEntry(odm.Model):
|
|
28
|
+
entry_id: str = odm.Keyword(description="A unique id for this entry")
|
|
29
|
+
type: str = odm.Enum(DASHBOARD_TYPES, description="The type of dashboard entry to render.")
|
|
30
|
+
config: str = odm.Keyword(description="A stringified JSON object containing additional configuration data")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@odm.model(index=True, store=True, description="Model of User")
|
|
34
|
+
class User(odm.Model):
|
|
35
|
+
api_quota = odm.Integer(
|
|
36
|
+
default=25,
|
|
37
|
+
store=False,
|
|
38
|
+
description="Maximum number of concurrent API requests",
|
|
39
|
+
)
|
|
40
|
+
apikeys: dict[str, ApiKey] = odm.Mapping(
|
|
41
|
+
odm.Compound(ApiKey),
|
|
42
|
+
default={},
|
|
43
|
+
index=False,
|
|
44
|
+
store=False,
|
|
45
|
+
description="Mapping of API keys",
|
|
46
|
+
)
|
|
47
|
+
classification = odm.Classification(
|
|
48
|
+
is_user_classification=True,
|
|
49
|
+
copyto="__text__",
|
|
50
|
+
default=CLASSIFICATION.UNRESTRICTED,
|
|
51
|
+
description="Maximum classification for the user",
|
|
52
|
+
)
|
|
53
|
+
email = odm.Optional(odm.Email(copyto="__text__"), description="User's email address")
|
|
54
|
+
groups = odm.List(
|
|
55
|
+
odm.Keyword(),
|
|
56
|
+
copyto="__text__",
|
|
57
|
+
default=["USERS"],
|
|
58
|
+
description="List of groups the user submits to",
|
|
59
|
+
)
|
|
60
|
+
is_active = odm.Boolean(default=True, description="Is the user active?")
|
|
61
|
+
name = odm.Keyword(copyto="__text__", description="Full name of the user")
|
|
62
|
+
password = odm.Keyword(index=False, store=False, description="BCrypt hash of the user's password")
|
|
63
|
+
type = odm.List(
|
|
64
|
+
odm.Enum(values=loader.USER_TYPES),
|
|
65
|
+
default=["user", "automation_basic"],
|
|
66
|
+
description="Type of user",
|
|
67
|
+
)
|
|
68
|
+
uname = odm.Keyword(copyto="__text__", description="Username")
|
|
69
|
+
favourite_views = odm.List(
|
|
70
|
+
odm.Keyword(),
|
|
71
|
+
default=[],
|
|
72
|
+
description="List of favourite views of the user",
|
|
73
|
+
)
|
|
74
|
+
favourite_analytics = odm.List(
|
|
75
|
+
odm.Keyword(),
|
|
76
|
+
default=[],
|
|
77
|
+
description="List of favourite analytics of the user",
|
|
78
|
+
)
|
|
79
|
+
dashboard: list[DashboardEntry] = odm.List(
|
|
80
|
+
odm.Compound(DashboardEntry),
|
|
81
|
+
default=[],
|
|
82
|
+
description="A list of dashboard entries to render on the UI.",
|
|
83
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
from typing import Literal, Union
|
|
3
|
+
|
|
4
|
+
from howler import odm
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@odm.model(index=True, store=True, description="Additional View Settings")
|
|
8
|
+
class Settings(odm.Model):
|
|
9
|
+
advance_on_triage: bool = odm.Boolean(
|
|
10
|
+
description="Should the user advance to the next alert when triage is complete?", default=False
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@odm.model(index=True, store=True, description="Model of views")
|
|
15
|
+
class View(odm.Model):
|
|
16
|
+
view_id: str = odm.UUID(description="A UUID for this view")
|
|
17
|
+
title: str = odm.CaseInsensitiveKeyword(description="The name of this view.")
|
|
18
|
+
query: str = odm.Keyword(description="The query to run in this view.")
|
|
19
|
+
sort: str = odm.Keyword(description="The sorting to use with this view.", optional=True)
|
|
20
|
+
span: str = odm.Keyword(
|
|
21
|
+
description="The time span to use by default when opening this view",
|
|
22
|
+
optional=True,
|
|
23
|
+
)
|
|
24
|
+
type: Union[Literal["personal"], Literal["global"], Literal["readonly"]] = odm.Enum(
|
|
25
|
+
values=["personal", "global", "readonly"],
|
|
26
|
+
description="The type of view",
|
|
27
|
+
)
|
|
28
|
+
owner: str = odm.Keyword(
|
|
29
|
+
description="The person to whom this view belongs.",
|
|
30
|
+
optional=True,
|
|
31
|
+
)
|
|
32
|
+
settings: Settings = odm.Compound(
|
|
33
|
+
Settings, description="Additional View Settings", default={"advance_on_triage": False}
|
|
34
|
+
)
|