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.
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,107 @@
1
+ # This the default classification engine provided,
2
+ # it showcases all the different features of the classification engine
3
+ # while providing a useful configuration
4
+
5
+ # Turn on/off classification enforcement. When this flag is off, this
6
+ # completely disables the classification engine, any documents added while
7
+ # the classification engine is off gets the default unrestricted value
8
+ enforce: false
9
+ dynamic_groups: false
10
+
11
+ # List of Classification level:
12
+ # Graded list were a smaller number is less restricted then an higher number.
13
+ levels:
14
+ # List of alternate names for the current marking
15
+ - aliases:
16
+ - UNRESTRICTED
17
+ - UNCLASSIFIED
18
+ - U
19
+ # Stylesheet applied in the UI for the different levels
20
+ css:
21
+ # Name of the color scheme used for display (default, primary, secondary, success, info, warning, error)
22
+ color: default
23
+ # Description of the classification level
24
+ description: Subject to standard copyright rules, TLP:WHITE information may be distributed without restriction.
25
+ # Interger value of the Classification level (higher is more classified)
26
+ lvl: 100
27
+ # Long name of the classification item
28
+ name: TLP:WHITE
29
+ # Short name of the classification item
30
+ short_name: TLP:W
31
+ - aliases: []
32
+ css:
33
+ color: success
34
+ description:
35
+ Recipients may share TLP:GREEN information with peers and partner organizations
36
+ within their sector or community, but not via publicly accessible channels. Information
37
+ in this category can be circulated widely within a particular community. TLP:GREEN
38
+ information may not be released outside of the community.
39
+ lvl: 110
40
+ name: TLP:GREEN
41
+ short_name: TLP:G
42
+ - aliases:
43
+ - RESTRICTED
44
+ css:
45
+ color: warning
46
+ description:
47
+ Recipients may only share TLP:AMBER information with members of their
48
+ own organization and with clients or customers who need to know the information
49
+ to protect themselves or prevent further harm.
50
+ lvl: 120
51
+ name: TLP:AMBER
52
+ short_name: TLP:A
53
+
54
+ # List of required tokens:
55
+ # A user requesting access to an item must have all the
56
+ # required tokens the item has to gain access to it
57
+ required:
58
+ - aliases: []
59
+ description: Produced using a commercial tool with limited distribution
60
+ name: COMMERCIAL
61
+ short_name: CMR
62
+ # The minimum classification level an item must have
63
+ # for this token to be valid. (optional)
64
+ # require_lvl: 100
65
+
66
+ # List of groups:
67
+ # A user requesting access to an item must be part of a least
68
+ # of one the group the item is part of to gain access
69
+ groups:
70
+ - aliases: []
71
+ # This is a special flag that when set to true, if any groups are selected
72
+ # in a classification. This group will automatically be selected too. (optional)
73
+ auto_select: true
74
+ description: Employees of CSE
75
+ name: CSE
76
+ short_name: CSE
77
+ # Assuming that this groups is the only group selected, this is the display name
78
+ # that will be used in the classification (that values has to be in the aliases
79
+ # of this group and only this group) (optional)
80
+ # solitary_display_name: ANY
81
+
82
+ # List of subgroups:
83
+ # A user requesting access to an item must be part of a least
84
+ # of one the subgroup the item is part of to gain access
85
+ subgroups:
86
+ - aliases: []
87
+ description: Member of Incident Response team
88
+ name: IR TEAM
89
+ short_name: IR
90
+ - aliases: []
91
+ description: Member of the Canadian Centre for Cyber Security
92
+ # This is a special flag that auto-select the corresponding group
93
+ # when this subgroup is selected (optional)
94
+ require_group: CSE
95
+ name: CCCS
96
+ short_name: CCCS
97
+ # This is a special flag that makes sure that none other then the
98
+ # corresponding group is selected when this subgroup is selected (optional)
99
+ # limited_to_group: CSE
100
+
101
+ # Default restricted classification
102
+ restricted: TLP:A//CMR
103
+
104
+ # Default unrestricted classification:
105
+ # When no classification are provided or that the classification engine is
106
+ # disabled, this is the classification value each items will get
107
+ unrestricted: TLP:W
@@ -0,0 +1,167 @@
1
+ from inspect import getmembers, isfunction
2
+ from sys import exc_info
3
+ from traceback import format_tb
4
+ from typing import Optional
5
+
6
+
7
+ class HowlerException(Exception):
8
+ """Wrapper for all exceptions thrown in howler's code"""
9
+
10
+ message: str
11
+ cause: Optional[Exception]
12
+
13
+ def __init__(self, message: str = "Something went wrong", cause: Optional[Exception] = None) -> None:
14
+ super().__init__(message)
15
+ self.message = message
16
+ self.cause = cause
17
+
18
+ def __repr__(self) -> str:
19
+ """String reproduction of the howler exception. Pass the message on"""
20
+ return self.message
21
+
22
+
23
+ class InvalidClassification(HowlerException):
24
+ """Exception for Invalid Classification"""
25
+
26
+
27
+ class InvalidDefinition(HowlerException):
28
+ """Exception for Invalid Definition"""
29
+
30
+
31
+ class InvalidRangeException(HowlerException):
32
+ """Exception for Invalid Range"""
33
+
34
+
35
+ class NonRecoverableError(HowlerException):
36
+ """Exception for an unrecoverable error"""
37
+
38
+
39
+ class RecoverableError(HowlerException):
40
+ """Exception for a recoverable error"""
41
+
42
+
43
+ class ConfigException(HowlerException):
44
+ """Exception thrown due to invalid configuration"""
45
+
46
+
47
+ class ResourceExists(HowlerException):
48
+ """Exception thrown due to a pre-existing resource"""
49
+
50
+
51
+ class VersionConflict(HowlerException):
52
+ """Exception thrown due to a version conflict"""
53
+
54
+ def __init__(self, message: str = "Something went wrong", cause: Optional[Exception] = None) -> None:
55
+ HowlerException.__init__(self, message, cause)
56
+
57
+
58
+ class HowlerTypeError(HowlerException, TypeError):
59
+ """TypeError child specifically for exceptions thrown by us"""
60
+
61
+ def __init__(self, message: str = "Something went wrong", cause: Optional[Exception] = None) -> None:
62
+ HowlerException.__init__(self, message, cause if cause is not None else TypeError(message))
63
+
64
+
65
+ class HowlerAttributeError(HowlerException, AttributeError):
66
+ """AttributeError child specifically for exceptions thrown by us"""
67
+
68
+ def __init__(self, message: str = "Something went wrong", cause: Optional[Exception] = None) -> None:
69
+ HowlerException.__init__(self, message, cause if cause is not None else AttributeError(message))
70
+
71
+
72
+ class HowlerValueError(HowlerException, ValueError):
73
+ """ValueError child specifically for exceptions thrown by us"""
74
+
75
+ def __init__(self, message: str = "Something went wrong", cause: Optional[Exception] = None) -> None:
76
+ HowlerException.__init__(self, message, cause if cause is not None else ValueError(message))
77
+
78
+
79
+ class HowlerNotImplementedError(HowlerException, NotImplementedError):
80
+ """NotImplementedError child specifically for exceptions thrown by us"""
81
+
82
+ def __init__(self, message: str = "Something went wrong", cause: Optional[Exception] = None) -> None:
83
+ HowlerException.__init__(self, message, cause if cause is not None else NotImplementedError(message))
84
+
85
+
86
+ class HowlerKeyError(HowlerException, KeyError):
87
+ """KeyError child specifically for exceptions thrown by us"""
88
+
89
+ def __init__(self, message: str = "Something went wrong", cause: Optional[Exception] = None) -> None:
90
+ HowlerException.__init__(self, message, cause if cause is not None else KeyError(message))
91
+
92
+
93
+ class HowlerRuntimeError(HowlerException, RuntimeError):
94
+ """RuntimeError child specifically for exceptions thrown by us"""
95
+
96
+ def __init__(self, message: str = "Something went wrong", cause: Optional[Exception] = None) -> None:
97
+ HowlerException.__init__(self, message, cause if cause is not None else RuntimeError(message))
98
+
99
+
100
+ class NotFoundException(HowlerException):
101
+ """Exception thrown when a resource cannot be found"""
102
+
103
+
104
+ class ForbiddenException(HowlerException):
105
+ """Exception thrown when a user is not permitted to perform an action"""
106
+
107
+
108
+ class AccessDeniedException(HowlerException):
109
+ """Exception thrown when a resource cannot be accessed by a user"""
110
+
111
+
112
+ class InvalidDataException(HowlerException):
113
+ """Exception thrown when user-provided data is invalid"""
114
+
115
+
116
+ class AuthenticationException(HowlerException):
117
+ """Exception thrown when a user cannot be authenticated"""
118
+
119
+
120
+ class Chain(object):
121
+ """This class can be used as a decorator to override the type of exceptions returned by a function"""
122
+
123
+ def __init__(self, exception):
124
+ self.exception = exception
125
+
126
+ def __call__(self, original):
127
+ """Execute a function and wrap any resulting exceptions"""
128
+
129
+ def wrapper(*args, **kwargs):
130
+ try:
131
+ return original(*args, **kwargs)
132
+ except Exception as e:
133
+ wrapped = self.exception(str(e), e)
134
+ raise wrapped.with_traceback(exc_info()[2])
135
+
136
+ wrapper.__name__ = original.__name__
137
+ wrapper.__doc__ = original.__doc__
138
+ wrapper.__dict__.update(original.__dict__)
139
+
140
+ return wrapper
141
+
142
+ def execute(self, func, *args, **kwargs):
143
+ """Execute a function and wrap any resulting exceptions"""
144
+ try:
145
+ return func(*args, **kwargs)
146
+ except Exception as e:
147
+ wrapped = self.exception(str(e), e)
148
+ raise wrapped.with_traceback(exc_info()[2])
149
+
150
+
151
+ class ChainAll:
152
+ """This class can be used as a decorator to override the type of exceptions returned by every method of a class"""
153
+
154
+ def __init__(self, exception):
155
+ self.exception = Chain(exception)
156
+
157
+ def __call__(self, cls):
158
+ """We can use an instance of this class as a decorator."""
159
+ for method in getmembers(cls, predicate=isfunction):
160
+ setattr(cls, method[0], self.exception(method[1]))
161
+
162
+ return cls
163
+
164
+
165
+ def get_stacktrace_info(ex: Exception) -> str:
166
+ """Get and format traceback information from a given exception"""
167
+ return "".join(format_tb(exc_info()[2]) + [": ".join((ex.__class__.__name__, str(ex)))])
@@ -0,0 +1,48 @@
1
+ import binascii
2
+
3
+ from howler.utils.chunk import chunk
4
+
5
+ FILTER = b"".join([bytes([x]) if x in range(32, 127) else b"." for x in range(256)])
6
+
7
+
8
+ def dump(binary: bytes, size: int = 2, sep: bytes = b" ") -> bytes:
9
+ "Convert a binary file into a hexadecimal representation"
10
+ hexstr = binascii.hexlify(binary)
11
+ return sep.join(chunk(hexstr, size))
12
+
13
+
14
+ def hexdump(
15
+ binary: bytes,
16
+ length: int = 16,
17
+ indent: str = "",
18
+ indent_size: int = 0,
19
+ newline: str = "\n",
20
+ prefix_offset: int = 0,
21
+ ) -> str:
22
+ """Create a string buffer that shows the given data in hexdump format.
23
+
24
+ src -> source buffer
25
+ length = 16 -> number of bytes per line
26
+ indent = "" -> indentation before each lines
27
+ indent_size = 0 -> number of time to repeat that indentation
28
+ newline = "\n" -> chars used as newline char
29
+
30
+ Example of output:
31
+ 00000000: 48 54 54 50 2F 31 2E 31 20 34 30 34 20 4E 6F 74 HTTP/1.1 404 Not
32
+ 00000010: 20 46 6F 75 6E 64 0D 0A 43 6F 6E 74 Found..Cont
33
+ ...
34
+ """
35
+ generator = chunk(binary, length)
36
+ line_frmt = "%%s%%08X: %%-%ss %%s" % ((length * 3) - 1)
37
+
38
+ out = [
39
+ line_frmt
40
+ % (
41
+ indent * indent_size,
42
+ prefix_offset + (addr * length),
43
+ dump(d).decode(),
44
+ d.translate(FILTER).decode(),
45
+ )
46
+ for addr, d in enumerate(generator)
47
+ ]
48
+ return newline.join(out)
@@ -0,0 +1,171 @@
1
+ from math import log
2
+ from socket import inet_aton
3
+ from struct import pack, unpack
4
+
5
+ from howler.common.exceptions import InvalidRangeException
6
+
7
+ # If you are tempted to extend this module to add support for IPv6 (or some
8
+ # similar invasive change) take a look at using PySubnetTree and extending it
9
+ # to allow arbitrary ranges instead.
10
+
11
+
12
+ def _convert(ip):
13
+ return unpack("!I", inet_aton(ip))[0]
14
+
15
+
16
+ def _next(lower, upper):
17
+ size = 2
18
+ while lower + size - 1 <= upper and _valid(lower, lower + size - 1):
19
+ size *= 2
20
+
21
+ return int(size / 2)
22
+
23
+
24
+ def _valid(lower, upper):
25
+ return lower & (upper - lower) == 0
26
+
27
+
28
+ # noinspection PyPep8Naming
29
+ class _dict(dict): # noqa: N801
30
+ pass
31
+
32
+
33
+ def ip_to_int(ip):
34
+ if isinstance(ip, int):
35
+ return ip
36
+
37
+ return _convert(ip)
38
+
39
+
40
+ # noinspection PyTypeChecker
41
+ class RangeTable(object):
42
+ """Efficient storage of IPv4 ranges and lookup of IPv4 addresses."""
43
+
44
+ def __init__(self):
45
+ self.clear()
46
+ self._trie = _dict()
47
+
48
+ def _add_cidr(self, lower, upper, value):
49
+ if not _valid(lower, upper):
50
+ # The public add_range method should ensure this never happens.
51
+ raise InvalidRangeException("invalid range: %d-%d" % (lower, upper))
52
+
53
+ size = upper - lower
54
+ points = 3 - int(log(size + 1, 256))
55
+
56
+ lower = self._to_path(lower)
57
+ upper = self._to_path(upper)
58
+
59
+ for x in range(lower[points], upper[points] + 1):
60
+ self._add_path(lower[:points] + (x,), value)
61
+
62
+ def _add_path(self, path, value):
63
+ trie = self._trie
64
+ for point in path[:-1]:
65
+ d = trie.get(point, _dict())
66
+ if not isinstance(d, _dict):
67
+ prev = d
68
+ d = _dict()
69
+ d.update({x: prev for x in range(256)})
70
+ trie[point] = d
71
+ trie = d
72
+ trie[path[-1]] = value
73
+
74
+ def _add_range(self, lower, upper, value):
75
+ while lower <= upper:
76
+ size = _next(lower, upper)
77
+ self._add_cidr(lower, lower + size - 1, value)
78
+ lower += size
79
+
80
+ def _follow_path(self, path):
81
+ entry = self._trie
82
+ while isinstance(entry, _dict):
83
+ entry = entry.get(path[0], None)
84
+ path = path[1:]
85
+ return entry
86
+
87
+ @staticmethod
88
+ def _to_path(integer):
89
+ return unpack("B" * 4, pack("!I", integer))
90
+
91
+ def __getitem__(self, key):
92
+ return self._follow_path(self._to_path(ip_to_int(key)))
93
+
94
+ def __setitem__(self, key, value):
95
+ if isinstance(key, int):
96
+ self._add_cidr(key, key, value)
97
+ return
98
+
99
+ span = key.split("-", 1)
100
+ if len(span) == 1:
101
+ span = key.split("/", 1)
102
+ if len(span) == 1:
103
+ span.append(span[0])
104
+
105
+ span[0] = _convert(span[0].strip())
106
+ if span[1].find(".") == -1:
107
+ mask = 2 ** (32 - int(span[1])) - 1
108
+ span[0] -= span[0] & mask
109
+ span[1] = span[0] | mask
110
+ else:
111
+ span[1] = _convert(span[1].strip())
112
+
113
+ self._add_range(span[0], span[1], value)
114
+
115
+ def add_range(self, lower, upper, value):
116
+ lower = ip_to_int(lower)
117
+ upper = ip_to_int(upper)
118
+
119
+ self._add_range(lower, upper, value)
120
+
121
+ while lower <= upper:
122
+ size = _next(lower, upper)
123
+ self._add_cidr(lower, lower + size - 1, value)
124
+ lower += size
125
+
126
+ def clear(self):
127
+ self._trie = _dict() # pylint:disable=W0201
128
+
129
+ def dump(self):
130
+ from pprint import pformat
131
+
132
+ return pformat(self._trie)
133
+
134
+
135
+ PRIVATE_NETWORKS = [
136
+ "10.0.0.0/8",
137
+ "172.16.0.0/12",
138
+ "192.168.0.0/16",
139
+ ]
140
+
141
+ RESERVED_NETWORKS = [
142
+ "0.0.0.0/8",
143
+ "100.64.0.0/10",
144
+ "127.0.0.0/8",
145
+ "169.254.0.0/16",
146
+ "192.0.0.0/24",
147
+ "192.0.2.0/24",
148
+ "192.88.99.0/24",
149
+ "198.18.0.0/15",
150
+ "198.51.100.0/24",
151
+ "203.0.113.0/24",
152
+ "240.0.0.0/4",
153
+ "224.0.0.0/4",
154
+ "255.255.255.255/32",
155
+ ]
156
+
157
+ _private_ips = RangeTable()
158
+ for cidr in PRIVATE_NETWORKS:
159
+ _private_ips[cidr] = True
160
+
161
+ _reserved_ips = RangeTable()
162
+ for cidr in RESERVED_NETWORKS:
163
+ _reserved_ips[cidr] = True
164
+
165
+
166
+ def is_ip_private(ip):
167
+ return _private_ips[ip] or False
168
+
169
+
170
+ def is_ip_reserved(ip):
171
+ return _private_ips[ip] or _reserved_ips[ip] or False
@@ -0,0 +1,154 @@
1
+ import logging
2
+ import os
3
+ from pathlib import Path
4
+ from string import Template
5
+ from typing import TYPE_CHECKING, Optional, Union
6
+
7
+ import yaml
8
+
9
+ from howler.odm.models.config import config
10
+
11
+ if TYPE_CHECKING:
12
+ from howler.common.classification import Classification
13
+
14
+ APP_NAME = os.environ.get("APP_NAME", "howler")
15
+ APP_PREFIX = os.environ.get("APP_PREFIX", "hwl")
16
+ USER_TYPES = {"admin", "user", "automation_basic", "automation_advanced"}
17
+
18
+
19
+ def env_substitute(buffer):
20
+ """Replace environment variables in the buffer with their value.
21
+
22
+ Use the built in template expansion tool that expands environment variable style strings ${}
23
+ We set the idpattern to none so that $abc doesn't get replaced but ${abc} does.
24
+
25
+ Case insensitive.
26
+ Variables that are found in the buffer, but are not defined as environment variables are ignored.
27
+ """
28
+ return Template(buffer).safe_substitute(os.environ, idpattern=None, bracedidpattern="(?a:[_a-z][_a-z0-9]*)")
29
+
30
+
31
+ _CLASSIFICATIONS: dict[Union[str, Path], "Classification"] = {}
32
+
33
+
34
+ def get_classification(yml_config: Optional[str] = None): # noqa: C901
35
+ "Get the classification from a given classification.yml file, caching results"
36
+ if yml_config in _CLASSIFICATIONS:
37
+ return _CLASSIFICATIONS[yml_config]
38
+
39
+ log = logging.getLogger(f"{APP_NAME}.common.loader")
40
+
41
+ if not yml_config:
42
+ yml_config_path = Path("/etc") / APP_NAME.replace("-dev", "") / "conf" / "classification.yml"
43
+ if yml_config_path.is_symlink():
44
+ log.info("%s is a symbolic link!", yml_config_path)
45
+ if str(yml_config_path.readlink()).startswith("..data"):
46
+ yml_config_path = yml_config_path.parent / yml_config_path.readlink()
47
+ log.info(
48
+ "This symbolic link links to a configmap, handling accordingly. Reading from %s",
49
+ yml_config_path,
50
+ )
51
+ else:
52
+ yml_config_path = Path(os.path.realpath(yml_config_path.readlink()))
53
+ log.info(
54
+ "Reading from %s",
55
+ yml_config_path,
56
+ )
57
+
58
+ if not yml_config_path.exists():
59
+ log.warning(f"{yml_config_path} does not exist!")
60
+ yml_config_path = Path("/etc") / APP_NAME.replace("-dev", "") / "classification.yml"
61
+ log.warning(f"Checking at {yml_config_path} instead.")
62
+ else:
63
+ yml_config_path = Path(yml_config)
64
+
65
+ log.debug("Loading classification definition from %s", yml_config_path)
66
+
67
+ classification_definition = None
68
+ # Load modifiers from the yaml config
69
+ if yml_config_path.exists():
70
+ with yml_config_path.open() as yml_fh:
71
+ yml_data = yaml.safe_load(yml_fh.read())
72
+ if yml_data:
73
+ classification_definition = yml_data
74
+
75
+ if classification_definition is None:
76
+ log.warning(
77
+ "Specified classification file does not exist or does not contain data!"
78
+ " Defaulting to default classification file."
79
+ )
80
+ default_file = Path(__file__).parent / "classification.yml"
81
+ if default_file.exists():
82
+ with default_file.open() as default_fh:
83
+ default_yml_data = yaml.safe_load(default_fh.read())
84
+ if default_yml_data:
85
+ classification_definition = default_yml_data
86
+ else:
87
+ log.critical("%s was not accessible!", default_file)
88
+
89
+ from howler.common.classification import Classification, InvalidDefinition
90
+
91
+ if not classification_definition:
92
+ raise InvalidDefinition("Could not find any classification definition to load.")
93
+
94
+ _classification = Classification(classification_definition)
95
+
96
+ if yml_config:
97
+ _CLASSIFICATIONS[yml_config] = _classification
98
+
99
+ return _classification
100
+
101
+
102
+ def get_lookups(lookup_folder: Optional[str] = None):
103
+ """Get lookups from the specified lookup folder"""
104
+ from howler.config import config
105
+
106
+ if not lookup_folder:
107
+ lookup_folder_path = Path("/etc/") / APP_NAME.replace("-dev", "") / "lookups"
108
+ else:
109
+ lookup_folder_path = Path(lookup_folder)
110
+
111
+ lookups = {}
112
+
113
+ if lookup_folder_path.exists():
114
+ for file in lookup_folder_path.iterdir():
115
+ with file.open("r") as f:
116
+ data = yaml.safe_load(f)
117
+ lookups[file.stem] = data
118
+ local_path = Path(__file__).parent.parent.parent / "static/mitre"
119
+ config_path = Path(config.ui.static_folder) if config.ui.static_folder else None
120
+ if local_path.exists():
121
+ mitre_path = local_path
122
+ elif config_path and (config_path / "mitre").exists():
123
+ mitre_path = config_path / "mitre"
124
+ else:
125
+ mitre_path = None
126
+
127
+ if mitre_path:
128
+ lookups["icons"] = sorted(set(f.stem for f in mitre_path.iterdir()))
129
+ else:
130
+ lookups["icons"] = []
131
+
132
+ lookups["roles"] = sorted(USER_TYPES)
133
+
134
+ return lookups
135
+
136
+
137
+ # Lazy load the datastore
138
+ _datastore = None
139
+
140
+
141
+ def datastore(_config=None, archive_access=True):
142
+ """Get a datastore connection"""
143
+ global _datastore
144
+
145
+ from howler.datastore.howler_store import HowlerDatastore
146
+ from howler.datastore.store import ESStore
147
+
148
+ if not _config:
149
+ _config = config
150
+
151
+ if _datastore is None:
152
+ _datastore = HowlerDatastore(ESStore(config=config, archive_access=archive_access))
153
+
154
+ return _datastore