Sublime route and service (#12)
* API endpoint to receive alert from Sublime Sublime invokes a webhook that is configured within the flask app to receive the message ID and store that in the `sublime_alerts` table * more modular and readable * sublime alerts logic to receive an sublime webhook call for when an alert is detected. stores the message_id in the `sublime_alerts` table retrieves all the `message_id`s from the table and invokes sublime API to get the details * precommit fixes
taylor_socfortress committed
Jul 12, 2023 at 13:56 UTC
18197dcb14b2a6d92b1d96032f9fec204e202343
13 files changed
+842
-87
backend/app/__init__.py
+2
@@ -40,6 +40,7 @@ from app.routes.dfir_iris import bp as dfir_iris_bp
40
from app.routes.graylog import bp as graylog_bp
41
from app.routes.rules import bp as rules_bp
42
from app.routes.shuffle import bp as shuffle_bp
43
+from app.routes.sublime import bp as sublime_bp
44
from app.routes.velociraptor import bp as velociraptor_bp
45
from app.routes.wazuhindexer import bp as wazuhindexer_bp
46
@@ -52,3 +53,4 @@ app.register_blueprint(wazuhindexer_bp) # Register the wazuhindexer blueprint
53
app.register_blueprint(shuffle_bp) # Register the shuffle blueprint
54
app.register_blueprint(velociraptor_bp) # Register the velociraptor blueprint
55
app.register_blueprint(dfir_iris_bp) # Register the dfir_iris blueprint
56
+app.register_blueprint(sublime_bp) # Register the sublime blueprint
backend/app/models/sublime_alerts.py
new
+58
@@ -0,0 +1,58 @@
1
+from datetime import datetime
2
+
3
+from sqlalchemy import Column
4
+from sqlalchemy import DateTime
5
+from sqlalchemy import Integer
6
+from sqlalchemy import String
7
+
8
+from app import db
9
+from app import ma
10
+
11
+
12
+# Path: backend\app\models.py
13
+class SublimeAlerts(db.Model):
14
+ """
15
+ Class for Sublime Alerts which stores the message ID, and timestamp.
16
+ This class inherits from SQLAlchemy's Model class.
17
+ """
18
+
19
+ id: Column[Integer] = db.Column(db.Integer, primary_key=True)
20
+ message_id: Column[String] = db.Column(db.String(1000))
21
+ timestamp: Column[DateTime] = db.Column(db.DateTime, default=datetime.utcnow)
22
+
23
+ def __init__(self, message_id: str):
24
+ """
25
+ Initialize a new instance of the Sublime Alerts class.
26
+
27
+ :param message_id: The Message ID of the alert.
28
+ """
29
+ self.message_id = message_id
30
+
31
+ def __repr__(self) -> str:
32
+ """
33
+ Returns a string representation of the Case instance.
34
+
35
+ :return: A string representation of the case ID.
36
+ """
37
+ return f"<Case {self.message_id}>"
38
+
39
+
40
+class SublimeAlertsSchema(ma.Schema):
41
+ """
42
+ Schema for serializing and deserializing instances of the Sublime Alerts class.
43
+ """
44
+
45
+ class Meta:
46
+ """
47
+ Meta class defines the fields to be serialized/deserialized.
48
+ """
49
+
50
+ fields: tuple = (
51
+ "id",
52
+ "message_id",
53
+ "timestamp",
54
+ )
55
+
56
+
57
+sublime_alert_schema: SublimeAlertsSchema = SublimeAlertsSchema()
58
+sublime_alerts_schema: SublimeAlertsSchema = SublimeAlertsSchema(many=True)
backend/app/routes/sublime.py
new
+48
@@ -0,0 +1,48 @@
1
+from typing import Any
2
+from typing import Dict
3
+
4
+from flask import Blueprint
5
+from flask import jsonify
6
+from flask import request
7
+from loguru import logger
8
+
9
+from app.services.Sublime.alerts import InvalidPayloadError
10
+from app.services.Sublime.alerts import SublimeAlertsService
11
+
12
+bp = Blueprint("sublime", __name__)
13
+
14
+
15
+@bp.route("/sublime/alert", methods=["POST"])
16
+def put_alert() -> jsonify:
17
+ """
18
+ Endpoint to store alert in the `sublime_alerts` table.
19
+ Invoked by the Sublime alert webhook which is configured in the Sublime UI.
20
+
21
+ Returns:
22
+ jsonify: A JSON response containing if the alert was stored successfully.
23
+ """
24
+ logger.info("Received request to store Sublime alert")
25
+ data: Dict[str, Any] = request.get_json()
26
+ service = SublimeAlertsService.from_connector_details("Sublime")
27
+
28
+ try:
29
+ message_id = service.validate_payload(data=data)
30
+ service.store_alert(message_id=message_id)
31
+ return jsonify({"message": "Successfully stored payload.", "success": True}), 200
32
+ except InvalidPayloadError:
33
+ logger.error("Received invalid payload.")
34
+ return jsonify({"message": "Invalid payload.", "success": False}), 400
35
+
36
+
37
+@bp.route("/sublime/alerts", methods=["GET"])
38
+def get_alerts() -> jsonify:
39
+ """
40
+ Endpoint to list all alerts from the `sublime_alerts` table.
41
+
42
+ Returns:
43
+ jsonify: A JSON response containing the list of all alerts from Sublime.
44
+ """
45
+ logger.info("Received request to get all Sublime alerts")
46
+ service = SublimeAlertsService.from_connector_details("Sublime")
47
+ alerts = service.collect_alerts()
48
+ return jsonify(alerts)
backend/app/services/Sublime/__init__.py
backend/app/services/Sublime/alerts.py
new
+245
@@ -0,0 +1,245 @@
1
+from typing import Dict
2
+from typing import List
3
+from typing import Union
4
+
5
+import requests
6
+from loguru import logger
7
+
8
+from app import db
9
+from app.models.sublime_alerts import SublimeAlerts
10
+from app.services.Sublime.universal import UniversalService
11
+
12
+
13
+class InvalidPayloadError(Exception):
14
+ """
15
+ Exception to be raised when the payload is invalid.
16
+ """
17
+
18
+ pass
19
+
20
+
21
+class SublimeSession:
22
+ """
23
+ Handles the session and connection to the Sublime server.
24
+
25
+ Attributes:
26
+ session (requests.Session): The session object for making HTTP requests.
27
+ connector_url (str): The base URL for the Sublime API.
28
+ """
29
+
30
+ def __init__(self, connector_url: str, connector_api_key: str):
31
+ """
32
+ The constructor for SublimeSession class.
33
+
34
+ Args:
35
+ connector_url (str): The base URL for the Sublime API.
36
+ connector_api_key (str): The API key for the Sublime API.
37
+ """
38
+ self.session = requests.Session()
39
+ self.session.headers.update(
40
+ {"Authorization": f"Bearer {connector_api_key}", "Content-Type": "application/json"},
41
+ )
42
+ self.connector_url = connector_url
43
+
44
+ def send_request(self, url: str) -> requests.Response:
45
+ """
46
+ Sends a GET request to a specific URL.
47
+
48
+ Args:
49
+ url (str): The URL to send the GET request to.
50
+
51
+ Returns:
52
+ requests.Response: The response object from the GET request.
53
+ """
54
+ return self.session.get(url, verify=False)
55
+
56
+
57
+class SublimeAlertsService:
58
+ """
59
+ Handles operations related to Sublime alerts.
60
+
61
+ Attributes:
62
+ session (SublimeSession): The session object for making HTTP requests.
63
+ connector_url (str): The base URL for the Sublime API.
64
+ connector_api_key (str): The API key for the Sublime API.
65
+ """
66
+
67
+ def __init__(self, session: SublimeSession, connector_url: str, connector_api_key: str):
68
+ """
69
+ The constructor for SublimeAlertsService class.
70
+
71
+ Args:
72
+ session (SublimeSession): The session object for making HTTP requests.
73
+ connector_url (str): The base URL for the Sublime API.
74
+ connector_api_key (str): The API key for the Sublime API.
75
+ """
76
+ self.session = session
77
+ self.connector_url = connector_url
78
+ self.connector_api_key = connector_api_key
79
+
80
+ @classmethod
81
+ def from_connector_details(cls, connector_name: str) -> "SublimeAlertsService":
82
+ """
83
+ Creates an instance of SublimeAlertsService using connector details.
84
+
85
+ Args:
86
+ connector_name (str): The name of the connector.
87
+
88
+ Returns:
89
+ SublimeAlertsService: An instance of the class.
90
+ """
91
+ connector_url, connector_api_key = UniversalService().collect_sublime_details(connector_name)
92
+ session = SublimeSession(connector_url, connector_api_key)
93
+ return cls(session, connector_url, connector_api_key)
94
+
95
+ def validate_payload(self, data: Dict[str, object]) -> str:
96
+ """
97
+ Validates the payload received from the Sublime alert webhook.
98
+
99
+ Args:
100
+ data (Dict[str, object]): The data received from the webhook.
101
+
102
+ Returns:
103
+ str: The message ID from the payload.
104
+
105
+ Raises:
106
+ InvalidPayloadError: If the payload is invalid.
107
+ """
108
+ try:
109
+ return data["data"]["message"]["id"]
110
+ except KeyError:
111
+ raise InvalidPayloadError("Invalid payload.")
112
+
113
+ def store_alert(self, message_id: str) -> None:
114
+ """
115
+ Stores a Sublime alert in the database.
116
+
117
+ Args:
118
+ message_id (str): The ID of the message to be stored.
119
+ """
120
+ sublime_alert = SublimeAlerts(message_id=message_id)
121
+ db.session.add(sublime_alert)
122
+ db.session.commit()
123
+ logger.info(f"Successfully stored payload with message ID {message_id}.")
124
+
125
+ def collect_alerts(self) -> Dict[str, Union[bool, str, List[Dict[str, str]]]]:
126
+ """
127
+ Collects alerts from Sublime and the database.
128
+
129
+ Returns:
130
+ Dict[str, Union[bool, str, List[Dict[str, str]]]]: A dictionary containing the success status,
131
+ a message, and potentially the message details.
132
+ """
133
+ if not self._are_sublime_details_collected():
134
+ return {
135
+ "message": "Failed to collect Sublime details",
136
+ "success": False,
137
+ }
138
+
139
+ alerts = self._collect_alerts_from_db()
140
+ if alerts["success"] is False:
141
+ return alerts
142
+
143
+ messages = self._collect_alerts_from_sublime(message_ids=alerts["message_ids"])
144
+ if messages["success"] is False:
145
+ return messages
146
+
147
+ return {
148
+ "message": "Successfully collected alerts",
149
+ "success": True,
150
+ "message_details": messages["message_details"],
151
+ }
152
+
153
+ def _are_sublime_details_collected(self) -> bool:
154
+ """
155
+ Checks whether the details for the Sublime connector were successfully collected.
156
+
157
+ Returns:
158
+ bool: True if all details were collected, False otherwise.
159
+ """
160
+ return all([self.connector_url, self.connector_api_key])
161
+
162
+ def _collect_alerts_from_db(self) -> Dict[str, Union[bool, str, List[str]]]:
163
+ """
164
+ Collects alerts from the database.
165
+
166
+ Returns:
167
+ Dict[str, Union[bool, str, List[str]]]: A dictionary containing the success status,
168
+ a message, and potentially the message IDs.
169
+ """
170
+ try:
171
+ message_ids = [alert.message_id for alert in SublimeAlerts.query.all()]
172
+ except Exception as err:
173
+ logger.error(f"Failed to collect message ids from database: {err}")
174
+ return {
175
+ "message": "Failed to collect message ids from database",
176
+ "success": False,
177
+ }
178
+
179
+ return {
180
+ "message": "Successfully collected message ids from database",
181
+ "success": True,
182
+ "message_ids": message_ids,
183
+ }
184
+
185
+ def _collect_alerts_from_sublime(self, message_ids: List[str]) -> Dict[str, Union[bool, str, List[Dict[str, str]]]]:
186
+ """
187
+ Collects alerts from Sublime.
188
+
189
+ Args:
190
+ message_ids (List[str]): A list of message IDs to collect.
191
+
192
+ Returns:
193
+ Dict[str, Union[bool, str, List[Dict[str, str]]]]: A dictionary containing the success status,
194
+ a message, and potentially the message details.
195
+ """
196
+ try:
197
+ message_details = []
198
+ for message_id in message_ids:
199
+ response = self.session.send_request(f"{self.connector_url}/v0/messages/{message_id}")
200
+ response.raise_for_status()
201
+ message_details.append(response.json())
202
+
203
+ return {
204
+ "message": "Successfully collected messages from Sublime",
205
+ "success": True,
206
+ "message_details": message_details,
207
+ }
208
+ except requests.exceptions.HTTPError as err:
209
+ return self._handle_request_error(err)
210
+
211
+ def _handle_request_error(self, err: Exception) -> Dict[str, Union[bool, str]]:
212
+ """
213
+ Handles a request error.
214
+
215
+ Args:
216
+ err (Exception): The exception that was raised.
217
+
218
+ Returns:
219
+ Dict[str, Union[bool, str]]: A dictionary containing the success status and a message.
220
+ """
221
+ logger.error(f"Failed to collect messages from Sublime: {err}")
222
+ return {
223
+ "message": "Failed to collect messages from Sublime",
224
+ "success": False,
225
+ }
226
+
227
+ def _collect_messages(self) -> Dict[str, Union[bool, str, Dict[str, str]]]:
228
+ """
229
+ Collects messages from Sublime.
230
+
231
+ Returns:
232
+ Dict[str, Union[bool, str, Dict[str, str]]]: A dictionary containing the success status,
233
+ a message, and potentially the messages.
234
+ """
235
+ try:
236
+ response = self.session.send_request(f"{self.connector_url}/v0/messages/groups")
237
+ response.raise_for_status()
238
+ except requests.exceptions.HTTPError as err:
239
+ return self._handle_request_error(err)
240
+
241
+ return {
242
+ "message": "Successfully collected messages from Sublime",
243
+ "success": True,
244
+ "messages": response.json(),
245
+ }
backend/app/services/Sublime/universal.py
new
+36
@@ -0,0 +1,36 @@
1
+from app.models.connectors import Connector
2
+from app.models.connectors import connector_factory
3
+
4
+
5
+class UniversalService:
6
+ """
7
+ A service class that encapsulates the logic for polling messages from Sublime.
8
+ """
9
+
10
+ def __init__(self) -> None:
11
+ self.collect_sublime_details("Sublime")
12
+ (
13
+ self.connector_url,
14
+ self.connector_api_key,
15
+ ) = self.collect_sublime_details("Sublime")
16
+
17
+ def collect_sublime_details(self, connector_name: str):
18
+ """
19
+ Collects the details of the Sublime connector.
20
+
21
+ Args:
22
+ connector_name (str): The name of the Sublime connector.
23
+
24
+ Returns:
25
+ tuple: A tuple containing the connection URL, and api key.
26
+ """
27
+ connector_instance = connector_factory.create(connector_name, connector_name)
28
+ connection_successful = connector_instance.verify_connection()
29
+ if connection_successful:
30
+ connection_details = Connector.get_connector_info_from_db(connector_name)
31
+ return (
32
+ connection_details.get("connector_url"),
33
+ connection_details.get("connector_api_key"),
34
+ )
35
+ else:
36
+ return None, None
backend/copilot.sqbpro
+1
-1
@@ -1 +1 @@
1
-<?xml version="1.0" encoding="UTF-8"?><sqlb_project><db path="C:/Users/walto/Desktop/GitHub/CoPilot/backend/copilot.db" readonly="0" foreign_keys="1" case_sensitive_like="0" temp_store="0" wal_autocheckpoint="1000" synchronous="2"/><attached/><window><main_tabs open="structure browser pragmas query" current="1"/></window><tab_structure><column_width id="0" width="300"/><column_width id="1" width="0"/><column_width id="2" width="100"/><column_width id="3" width="1981"/><column_width id="4" width="0"/><expanded_item id="0" parent="1"/><expanded_item id="1" parent="1"/><expanded_item id="2" parent="1"/><expanded_item id="3" parent="1"/></tab_structure><tab_browse><current_table name="4,10:mainconnectors"/><default_encoding codec=""/><browse_table_settings><table schema="main" name="agent_metadata" show_row_id="0" encoding="" plot_x_axis="" unlock_view_pk="_rowid_"><sort/><column_widths><column index="1" value="40"/><column index="2" value="164"/><column index="3" value="79"/><column index="4" value="40"/><column index="5" value="72"/><column index="6" value="95"/><column index="7" value="71"/></column_widths><filter_values/><conditional_formats/><row_id_formats/><display_formats/><hidden_columns/><plot_y_axes/><global_filter/></table><table schema="main" name="case" show_row_id="0" encoding="" plot_x_axis="" unlock_view_pk="_rowid_"><sort/><column_widths><column index="1" value="40"/><column index="2" value="57"/><column index="3" value="81"/><column index="4" value="53"/></column_widths><filter_values/><conditional_formats/><row_id_formats/><display_formats/><hidden_columns/><plot_y_axes/><global_filter/></table><table schema="main" name="connectors" show_row_id="0" encoding="" plot_x_axis="" unlock_view_pk="_rowid_"><sort/><column_widths><column index="1" value="40"/><column index="2" value="117"/><column index="3" value="112"/><column index="4" value="259"/><column index="5" value="196"/><column index="6" value="146"/><column index="7" value="145"/><column index="8" value="132"/></column_widths><filter_values/><conditional_formats/><row_id_formats/><display_formats/><hidden_columns/><plot_y_axes/><global_filter/></table><table schema="main" name="connectors_available" show_row_id="0" encoding="" plot_x_axis="" unlock_view_pk="_rowid_"><sort/><column_widths><column index="1" value="40"/><column index="2" value="117"/><column index="3" value="186"/><column index="4" value="140"/><column index="5" value="151"/><column index="6" value="131"/></column_widths><filter_values/><conditional_formats/><row_id_formats/><display_formats/><hidden_columns/><plot_y_axes/><global_filter/></table><table schema="main" name="disabled_rules" show_row_id="0" encoding="" plot_x_axis="" unlock_view_pk="_rowid_"><sort/><column_widths><column index="1" value="40"/><column index="2" value="52"/><column index="3" value="102"/><column index="4" value="73"/><column index="5" value="144"/><column index="6" value="175"/><column index="7" value="106"/></column_widths><filter_values/><conditional_formats/><row_id_formats/><display_formats/><hidden_columns/><plot_y_axes/><global_filter/></table></browse_table_settings></tab_browse><tab_sql><sql name="SQL 1"></sql><current_tab id="0"/></tab_sql></sqlb_project>
1
+<?xml version="1.0" encoding="UTF-8"?><sqlb_project><db path="C:/Users/walto/Desktop/GitHub/CoPilot/backend/copilot.db" readonly="0" foreign_keys="1" case_sensitive_like="0" temp_store="0" wal_autocheckpoint="1000" synchronous="2"/><attached/><window><main_tabs open="structure browser pragmas query" current="0"/></window><tab_structure><column_width id="0" width="300"/><column_width id="1" width="0"/><column_width id="2" width="100"/><column_width id="3" width="1981"/><column_width id="4" width="0"/><expanded_item id="0" parent="1"/><expanded_item id="1" parent="1"/><expanded_item id="2" parent="1"/><expanded_item id="3" parent="1"/></tab_structure><tab_browse><current_table name="4,14:mainagent_metadata"/><default_encoding codec=""/><browse_table_settings><table schema="main" name="agent_metadata" show_row_id="0" encoding="" plot_x_axis="" unlock_view_pk="_rowid_"><sort/><column_widths><column index="1" value="40"/><column index="2" value="65"/><column index="3" value="271"/><column index="4" value="300"/><column index="5" value="205"/><column index="6" value="95"/><column index="7" value="175"/></column_widths><filter_values/><conditional_formats/><row_id_formats/><display_formats/><hidden_columns/><plot_y_axes/><global_filter/></table><table schema="main" name="sublime_alerts" show_row_id="0" encoding="" plot_x_axis="" unlock_view_pk="_rowid_"><sort><column index="3" mode="0"/></sort><column_widths><column index="1" value="40"/><column index="2" value="84"/><column index="3" value="76"/></column_widths><filter_values/><conditional_formats/><row_id_formats/><display_formats/><hidden_columns/><plot_y_axes/><global_filter/></table></browse_table_settings></tab_browse><tab_sql><sql name="SQL 1"></sql><current_tab id="0"/></tab_sql></sqlb_project>
backend/docs/shuffle.md
+69
@@ -2,11 +2,80 @@
2
3
### <span style="color:green">Shuffle Routes</span>
4
5
+## shuffle.py
6
+
7
+This is a Python file that is responsible for creating Flask routes to interact with Shuffle workflows. It is part of a larger application that presumably interacts with the Shuffle system. The Shuffle system is a security automation platform that helps automate security processes.
8
+
9
+Here is a detailed breakdown of what the file does:
10
+
11
+### Imports
12
+
13
+The file imports various modules and functions that are required:
14
+
15
+- `Blueprint` from `flask`: This is a Flask object that allows you to create modular routes.
16
+- `jsonify` from `flask`: This function is used to convert Python data structures to JSON.
17
+- `logger` from `loguru`: This is a logging utility that provides an easy way to add log statements to your code.
18
+- `WorkflowsService` from `app.services.Shuffle.workflows`: This is a custom service that interacts with Shuffle workflows.
19
+
20
+### Blueprint
21
+
22
+A `Blueprint` named `bp` is created. This blueprint is used to create routes that are associated with Shuffle workflows.
23
+
24
+### Routes
25
+
26
+Four routes are created:
27
+
28
+1. `/shuffle/workflows` (GET): This route returns a JSON response containing a list of all configured Workflows in Shuffle.
29
+2. `/shuffle/workflows/executions` (GET): This route returns a JSON response containing the list of all configured workflows and their last execution status in Shuffle.
30
+3. `/shuffle/workflows/executions/<workflow_id>` (GET): This route takes a `workflow_id` as an argument and returns a JSON response containing the last execution status of the specified workflow in Shuffle.
31
+
32
+Each route creates an instance of `WorkflowsService` to interact with the Shuffle workflows.
33
+
34
+### WorkflowsService
35
+
36
+This is a service class that contains methods for interacting with Shuffle workflows. The methods of this class are used to collect workflows and their execution status.
37
+
38
+In summary, the `shuffle.py` file is responsible for creating routes that provide an interface for interacting with Shuffle workflows. It uses a service class to interact with the Shuffle system and return the required data as a JSON response.
39
+
40
::: app.routes.shuffle
41
<br>
42
43
### <span style="color:red">Workflows Services</span>
44
45
+## workflows.py
46
+
47
+The `workflows.py` script is part of a larger system designed to interact with the Shuffle API, specifically to collect and manipulate information about workflows.
48
+
49
+### Classes
50
+
51
+#### WorkflowsService
52
+
53
+The `WorkflowsService` class encapsulates the logic for retrieving workflow information from Shuffle. It includes methods to collect workflow details, check if details were successfully collected, send requests to the Shuffle API, and handle any exceptions that occur during these processes.
54
+
55
+### Methods
56
+
57
+- `_collect_shuffle_details`: The `_collect_shuffle_details` method collects the details of the Shuffle connector from a universal service which pulls connector details from a database.
58
+
59
+- `_are_details_collected`: The `_are_details_collected` method checks whether the details for the Shuffle connector were successfully collected.
60
+
61
+- `_send_request`: The `_send_request` method sends a GET request to a provided URL.
62
+
63
+- `collect_workflows`: The `collect_workflows` method collects the workflows from Shuffle. If the details for the Shuffle connector were not successfully collected, the method returns a message indicating this. If the details were successfully collected, the method attempts to collect workflows from Shuffle and returns a dictionary containing the success status, a message, and potentially the workflows.
64
+
65
+- `_handle_request_error`: The `_handle_request_error` method handles any errors that occur during a request. It logs the error message and returns a dictionary containing the success status and an error message.
66
+
67
+- `_collect_workflows`: The `_collect_workflows` method attempts to collect workflows from Shuffle by sending a GET request to the appropriate Shuffle API endpoint. If the request is successful, the method returns a dictionary containing the success status, a message, and the workflows. If the request fails, the method calls the `_handle_request_error` method to handle the error.
68
+
69
+- `collect_workflow_details`: The `collect_workflow_details` method collects the workflow ID and workflow name from Shuffle. If the details for the Shuffle connector were not successfully collected, the method returns a message indicating this. If the details were successfully collected, the method attempts to collect workflow details from Shuffle and returns a dictionary containing the success status, a message, and potentially the workflow IDs.
70
+
71
+- `_collect_workflow_details`: The `_collect_workflow_details` method attempts to collect the workflow ID and workflow name from Shuffle by sending a GET request to the appropriate Shuffle API endpoint. If the request is successful, the method returns a dictionary containing the success status, a message, and the workflow IDs. If the request fails, the method calls the `_handle_request_error` method to handle the error.
72
+
73
+- `collect_workflow_executions_status`: The `collect_workflow_executions_status` method collects the execution status of a Shuffle Workflow by its ID. If the details for the Shuffle connector were not successfully collected, the method returns a message indicating this. If the details were successfully collected, the method attempts to collect the execution status of the workflow and returns a dictionary containing the success status, a message, and potentially the execution status.
74
+
75
+- `_collect_workflow_executions_status`: The `_collect_workflow_executions_status` method attempts to collect the execution status of a Shuffle Workflow by its ID by sending a GET request to the appropriate Shuffle API endpoint. If the request is successful, the method returns a dictionary containing the success status, a message, and the execution status. If the request fails, the method calls the `_handle_request_error` method to handle the error.
76
+
77
+In summary, `workflows.py` is a script that interacts with the Shuffle API to collect and handle information about workflows. It includes robust error handling to deal with any issues that might occur during the process of interacting with the API.
78
+
79
::: app.services.Shuffle.workflows
80
<br>
81
backend/docs/sublime.md
new
+115
@@ -0,0 +1,115 @@
1
+## Sublime Overview
2
+
3
+### <span style="color:green">Sublime Models</span>
4
+
5
+File: sublime_alerts.py
6
+
7
+This Python module defines a database model and corresponding schema for handling Sublime Alerts. It consists of two primary classes: `SublimeAlerts` and `SublimeAlertsSchema`.
8
+
9
+## Class: SublimeAlerts
10
+
11
+`SublimeAlerts` is a SQLAlchemy model class that defines a table in the database to store Sublime Alerts. Each instance of this class represents a single row in the `sublime_alerts` table.
12
+
13
+The table has the following columns:
14
+
15
+- `id`: This is the primary key, an integer which is unique for each alert.
16
+- `message_id`: This is a string column, which stores the ID of the message associated with the alert.
17
+- `timestamp`: This column stores the datetime when the alert was created. The default value is the current time in UTC.
18
+
19
+### Methods
20
+
21
+This class has the following methods:
22
+
23
+- `__init__(self, message_id: str)`: This is the constructor method. It is used to initialize a new instance of the `SublimeAlerts` class with a given `message_id`.
24
+- `__repr__(self) -> str`: This method returns a string representation of an instance of the `SublimeAlerts` class. This can be useful for debugging and logging.
25
+
26
+## Class: SublimeAlertsSchema
27
+
28
+`SublimeAlertsSchema` is a Marshmallow Schema class used for serializing and deserializing instances of the `SublimeAlerts` class.
29
+
30
+### Inner Class: Meta
31
+
32
+This class defines the fields to be serialized/deserialized. The fields are `id`, `message_id`, and `timestamp`.
33
+
34
+### Variables: sublime_alert_schema and sublime_alerts_schema
35
+
36
+These are instances of `SublimeAlertsSchema`. The `sublime_alert_schema` is used for single alert serialization/deserialization, while `sublime_alerts_schema` is used for multiple alerts (it has `many=True` to indicate it handles multiple objects).
37
+
38
+::: app.models.sublime_alerts
39
+<br>
40
+
41
+### <span style="color:green">Sublime Routes</span>
42
+
43
+## sublime.py - Flask Blueprint for Sublime Services
44
+
45
+This Python file, `sublime.py`, creates a Flask Blueprint for Sublime services. It defines two HTTP endpoints related to Sublime alerts: one for storing alerts and another for retrieving alerts.
46
+
47
+The Python classes and methods from `app.services.Sublime.alerts` and `app.services.Sublime.messages` are used to perform the core logic related to Sublime alerts.
48
+
49
+### Blueprint Definition
50
+
51
+The Flask Blueprint `sublime` is created at the beginning of the file, which is used to group the related endpoints.
52
+
53
+### `/sublime/alert` Endpoint (POST)
54
+
55
+This endpoint is used to store Sublime alerts into the `sublime_alerts` table. It's invoked by the Sublime alert webhook, which is configured in the Sublime UI.
56
+
57
+Upon receiving a POST request, it uses the `SublimeAlertsService` to validate the payload and store the alert. If the payload is valid and is successfully stored, it returns a successful JSON response. If the payload is invalid, it logs an error and returns a failure response.
58
+
59
+### `/sublime/alerts` Endpoint (GET)
60
+
61
+This endpoint is used to retrieve all alerts from the `sublime_alerts` table.
62
+
63
+Upon receiving a GET request, it uses the `SublimeAlertsService` to collect the alerts and returns them in a JSON response.
64
+
65
+### Exception Handling
66
+
67
+The file also defines an exception `InvalidPayloadError` which is raised when the payload received from the Sublime alert webhook is invalid. It's handled in the `put_alert()` function.
68
+
69
+::: app.routes.sublime
70
+<br>
71
+
72
+### <span style="color:red">Sublime Services Alerts</span>
73
+
74
+## alerts.py
75
+
76
+This Python module is part of a larger application that interacts with the Sublime API and a database to handle operations related to alerts. Specifically, it receives and validates payloads from a Sublime alert webhook, stores alerts into a database, and fetches alerts from Sublime and the database.
77
+
78
+### Classes
79
+
80
+#### `InvalidPayloadError`
81
+
82
+This is a custom exception class that is used to indicate that a payload received from a webhook is invalid.
83
+
84
+#### `SublimeSession`
85
+
86
+This class manages a session and connection to the Sublime server. It uses the requests library to create a session, updates the session headers with an authorization token and content type, and provides a method to send GET requests to a specific URL.
87
+
88
+#### `SublimeAlertsService`
89
+
90
+This class provides services for handling Sublime alerts. It uses an instance of the `SublimeSession` class to make HTTP requests and provides methods to perform the following operations:
91
+
92
+- Create an instance of `SublimeAlertsService` using connector details.
93
+- Validate a payload received from a Sublime alert webhook.
94
+- Store an alert in the database.
95
+- Collect alerts from Sublime and the database.
96
+- Check whether the details for the Sublime connector were successfully collected.
97
+- Collect alerts from the database.
98
+- Collect alerts from Sublime.
99
+- Handle a request error.
100
+- Collect messages from Sublime.
101
+
102
+The methods that start with an underscore (\_) are considered private methods and are intended for internal use within the class.
103
+
104
+Please note that this module also interacts with other parts of the larger application, such as the `db` object for interacting with the database, the `SublimeAlerts` model for the structure of the alerts, and the `UniversalService` for fetching Sublime details.
105
+
106
+This module also logs important information and errors using the `loguru` library.
107
+
108
+Please remember that the actual behavior of the code can depend on the rest of the application, the setup of the Sublime API, and the structure of the database.
109
+
110
+::: app.services.Sublime.alerts
111
+<br>
112
+
113
+### <span style="color:red">Sublime Services Universal</span>
114
+
115
+::: app.services.Sublime.universal
backend/docs/velociraptor.md
+85
@@ -2,14 +2,99 @@
2
3
### <span style="color:blue">Artifacts Model</span>
4
5
+## artifacts.py
6
+
7
+The `artifacts.py` file defines the structure and functionality related to 'Artifacts' in the context of this Python project.
8
+
9
+### Class Definitions
10
+
11
+The file defines two classes: `Artifact` and `ArtifactSchema`.
12
+
13
+#### Artifact Class
14
+
15
+`Artifact` is a SQLAlchemy Model class that represents an 'Artifact' in the application. Each 'Artifact' is characterized by:
16
+
17
+- `id` (Integer): A unique identifier.
18
+- `artifact_name` (String): The name of the artifact.
19
+- `artifact_results` (TEXT): The results of the artifact, stored as a JSON string.
20
+- `hostname` (String): The hostname where the artifact was collected.
21
+
22
+The `Artifact` class also defines an `__init__` method for initializing new instances of the class and an `__repr__` method for providing a string representation of each instance.
23
+
24
+#### ArtifactSchema Class
25
+
26
+`ArtifactSchema` is a Marshmallow Schema class that's used for serializing and deserializing instances of the `Artifact` class. The `Meta` inner class within `ArtifactSchema` defines the fields to be serialized/deserialized, which are the same as the attributes of the `Artifact` class.
27
+
28
+The file also defines two instances of `ArtifactSchema`: `artifact_schema` and `artifacts_schema`. The `artifacts_schema` is used for operations involving multiple `Artifact` instances (indicated by `many=True`), while `artifact_schema` is used for operations involving a single `Artifact` instance.
29
+
30
+### Summary
31
+
32
+Overall, `artifacts.py` is used to define how 'Artifacts' are structured and manipulated in this Python project. 'Artifacts' seem to represent some kind of collected data or results within the context of the project, although the exact nature of these 'Artifacts' would depend on the broader project context.
33
+
34
::: app.models.artifacts
35
<br>
36
37
### <span style="color:green">Artifacts Routes</span>
38
39
+## velociraptor.py
40
+
41
+This Python module named `velociraptor.py` is part of a web application and is dedicated to handling HTTP requests that interact with the Velociraptor system. Velociraptor is a tool for collecting host-based state information using Velociraptor artifacts.
42
+
43
+The module provides several HTTP endpoints under the base route "/velociraptor", each performing a different operation related to Velociraptor artifacts.
44
+
45
+### Endpoint: GET /velociraptor/artifacts
46
+
47
+This endpoint is used to retrieve all available artifacts from Velociraptor. An artifact is an item of information that is collected from a system. The endpoint processes each artifact to verify the connection and returns the results in a JSON response. The `ArtifactsService` is used to perform this operation.
48
+
49
+### Endpoint: GET /velociraptor/artifacts/linux
50
+
51
+This endpoint is similar to the above but is specifically for retrieving all available Linux artifacts. It processes each artifact to verify the connection and returns the results where the artifact's name begins with 'Linux'.
52
+
53
+### Endpoint: GET /velociraptor/artifacts/windows
54
+
55
+This endpoint is for retrieving all available Windows artifacts. It processes each artifact to verify the connection and returns the results where the artifact's name begins with 'Windows'.
56
+
57
+### Endpoint: GET /velociraptor/artifacts/mac
58
+
59
+This endpoint is for retrieving all available MacOS artifacts. It processes each artifact to verify the connection and returns the results where the artifact's name begins with 'MacOS'.
60
+
61
+### Endpoint: POST /velociraptor/artifacts/collection
62
+
63
+This endpoint is used to collect an artifact from a specific client. It collects the artifact name and client name from the request body and returns the results of the artifact collection operation. The `UniversalService` is used to get the client ID, and the `ArtifactsService` is used to run the artifact collection.
64
+
65
+In the event that the client ID cannot be obtained (for example, if the client has not been seen in the last 30 seconds and may not be online with the Velociraptor server), the endpoint returns a 500 status code and a message indicating the error.
66
+
67
+Note: This overview is based on the code provided, and actual behavior may vary based on the complete application context and setup.
68
+
69
::: app.routes.velociraptor
70
<br>
71
72
### <span style="color:red">Artifacts Services</span>
73
74
+## ArtifactsService Class
75
+
76
+The `ArtifactsService` class is part of a service in an application that works with Velociraptor, a tool often used for endpoint visibility and digital forensics. This class is specifically responsible for managing and interacting with "artifacts" in Velociraptor. Artifacts in Velociraptor represent data of interest on endpoints (machines) that can be collected for analysis.
77
+
78
+### Methods
79
+
80
+- `__init__`: This method initializes the `ArtifactsService` class. It also creates an instance of `UniversalService`, which is likely used to interact with Velociraptor's API.
81
+
82
+- `_create_query`: This method is used to create a query string, which is presumably used to communicate with Velociraptor's API.
83
+
84
+- `_get_artifact_key`: This method is used to construct the artifact key using the client ID and artifact name. The artifact key is likely a unique identifier for each artifact within the scope of a particular client.
85
+
86
+- `collect_artifacts`: This method is used to collect all the artifacts from Velociraptor. It does this by creating a query and then using the `UniversalService` to execute that query.
87
+
88
+- `collect_artifacts_prefixed`: This method is used to collect artifacts from Velociraptor that have a name beginning with a specific prefix.
89
+
90
+- `collect_artifacts_linux`, `collect_artifacts_windows`, `collect_artifacts_macos`: These methods are used to collect artifacts from Velociraptor that have names beginning with `Linux.`, `Windows.`, and `MacOS.` respectively. These methods are essentially filters for specific operating system-related artifacts.
91
+
92
+- `run_artifact_collection`: This method is used to run an artifact collection on a specific client. It creates a query to collect the client's artifact, watches the completion of the flow (which is likely the process of collecting the artifact), and reads the collection results. If there is an error during the process, it returns a failure message.
93
+
94
+Overall, the `ArtifactsService` class provides a way to interact with Velociraptor's artifacts, from collecting them based on certain criteria to running an artifact collection for a specific client.
95
+
96
::: app.services.Velociraptor.artifacts
97
+
98
+### <span style="color:red">Universal Services</span>
99
+
100
+::: app.services.Velociraptor.universal
backend/migrations/versions/0381c0088cbe_initial_migration.py
+107
-86
@@ -7,6 +7,7 @@ Create Date: 2023-07-11 13:24:04.087035
7
"""
8
import sqlalchemy as sa
9
from alembic import op
10
+from sqlalchemy.engine.reflection import Inspector
11
12
# revision identifiers, used by Alembic.
13
revision = "0381c0088cbe"
@@ -17,92 +18,112 @@ depends_on = None
18
19
def upgrade():
20
# ### commands auto generated by Alembic - please adjust! ###
20
- op.create_table(
21
- "agent_metadata",
22
- sa.Column("id", sa.Integer(), nullable=False),
23
- sa.Column("agent_id", sa.String(length=100), nullable=True),
24
- sa.Column("ip_address", sa.String(length=100), nullable=True),
25
- sa.Column("os", sa.String(length=100), nullable=True),
26
- sa.Column("hostname", sa.String(length=100), nullable=True),
27
- sa.Column("critical_asset", sa.Boolean(), nullable=True),
28
- sa.Column("last_seen", sa.DateTime(), nullable=True),
29
- sa.PrimaryKeyConstraint("id"),
30
- )
31
- op.create_table(
32
- "artifact",
33
- sa.Column("id", sa.Integer(), nullable=False),
34
- sa.Column("artifact_name", sa.String(length=100), nullable=True),
35
- sa.Column("artifact_results", sa.TEXT(), nullable=True),
36
- sa.Column("hostname", sa.String(length=100), nullable=True),
37
- sa.PrimaryKeyConstraint("id"),
38
- )
39
- op.create_table(
40
- "case",
41
- sa.Column("id", sa.Integer(), nullable=False),
42
- sa.Column("case_id", sa.Integer(), nullable=True),
43
- sa.Column("case_name", sa.String(length=100), nullable=True),
44
- sa.Column("agents", sa.String(length=1000), nullable=True),
45
- sa.PrimaryKeyConstraint("id"),
46
- )
47
- op.create_table(
48
- "connectors",
49
- sa.Column("id", sa.Integer(), nullable=False),
50
- sa.Column("connector_name", sa.String(length=100), nullable=True),
51
- sa.Column("connector_type", sa.String(length=100), nullable=True),
52
- sa.Column("connector_url", sa.String(length=100), nullable=True),
53
- sa.Column("connector_last_updated", sa.DateTime(), nullable=True),
54
- sa.Column("connector_username", sa.String(length=100), nullable=True),
55
- sa.Column("connector_password", sa.String(length=100), nullable=True),
56
- sa.Column("connector_api_key", sa.String(length=100), nullable=True),
57
- sa.PrimaryKeyConstraint("id"),
58
- sa.UniqueConstraint("connector_name"),
59
- )
60
- op.create_table(
61
- "connectors_available",
62
- sa.Column("id", sa.Integer(), nullable=False),
63
- sa.Column("connector_name", sa.String(length=100), nullable=True),
64
- sa.Column("connector_description", sa.String(length=100), nullable=True),
65
- sa.Column("connector_supports", sa.String(length=100), nullable=True),
66
- sa.Column("connector_configured", sa.Boolean(), nullable=True),
67
- sa.Column("connector_verified", sa.Boolean(), nullable=True),
68
- sa.PrimaryKeyConstraint("id"),
69
- sa.UniqueConstraint("connector_name"),
70
- )
71
- op.create_table(
72
- "disabled_rules",
73
- sa.Column("id", sa.Integer(), nullable=False),
74
- sa.Column("rule_id", sa.String(length=100), nullable=True),
75
- sa.Column("previous_level", sa.String(length=1000), nullable=True),
76
- sa.Column("new_level", sa.String(length=1000), nullable=True),
77
- sa.Column("reason_for_disabling", sa.String(length=100), nullable=True),
78
- sa.Column("date_disabled", sa.DateTime(), nullable=True),
79
- sa.Column("length_of_time", sa.Integer(), nullable=True),
80
- sa.PrimaryKeyConstraint("id"),
81
- )
82
- op.create_table(
83
- "graylog_metrics_allocation",
84
- sa.Column("id", sa.Integer(), nullable=False),
85
- sa.Column("input_usage", sa.Float(), nullable=True),
86
- sa.Column("output_usage", sa.Float(), nullable=True),
87
- sa.Column("processor_usage", sa.Float(), nullable=True),
88
- sa.Column("input_1_sec_rate", sa.Float(), nullable=True),
89
- sa.Column("output_1_sec_rate", sa.Float(), nullable=True),
90
- sa.Column("total_input", sa.Float(), nullable=True),
91
- sa.Column("total_output", sa.Float(), nullable=True),
92
- sa.Column("timestamp", sa.DateTime(), nullable=True),
93
- sa.PrimaryKeyConstraint("id"),
94
- )
95
- op.create_table(
96
- "wazuh_indexer_allocation",
97
- sa.Column("id", sa.Integer(), nullable=False),
98
- sa.Column("node", sa.String(length=100), nullable=True),
99
- sa.Column("disk_used", sa.Float(), nullable=True),
100
- sa.Column("disk_available", sa.Float(), nullable=True),
101
- sa.Column("disk_total", sa.Float(), nullable=True),
102
- sa.Column("disk_percent", sa.Float(), nullable=True),
103
- sa.Column("timestamp", sa.DateTime(), nullable=True),
104
- sa.PrimaryKeyConstraint("id"),
105
- )
21
+ conn = op.get_bind()
22
+ inspector = Inspector.from_engine(conn)
23
+
24
+ if "agent_metadata" not in inspector.get_table_names():
25
+ op.create_table(
26
+ "agent_metadata",
27
+ sa.Column("id", sa.Integer(), nullable=False),
28
+ sa.Column("agent_id", sa.String(length=100), nullable=True),
29
+ sa.Column("ip_address", sa.String(length=100), nullable=True),
30
+ sa.Column("os", sa.String(length=100), nullable=True),
31
+ sa.Column("hostname", sa.String(length=100), nullable=True),
32
+ sa.Column("critical_asset", sa.Boolean(), nullable=True),
33
+ sa.Column("last_seen", sa.DateTime(), nullable=True),
34
+ sa.PrimaryKeyConstraint("id"),
35
+ )
36
+ if "artifact" not in inspector.get_table_names():
37
+ op.create_table(
38
+ "artifact",
39
+ sa.Column("id", sa.Integer(), nullable=False),
40
+ sa.Column("artifact_name", sa.String(length=100), nullable=True),
41
+ sa.Column("artifact_results", sa.TEXT(), nullable=True),
42
+ sa.Column("hostname", sa.String(length=100), nullable=True),
43
+ sa.PrimaryKeyConstraint("id"),
44
+ )
45
+ if "case" not in inspector.get_table_names():
46
+ op.create_table(
47
+ "case",
48
+ sa.Column("id", sa.Integer(), nullable=False),
49
+ sa.Column("case_id", sa.Integer(), nullable=True),
50
+ sa.Column("case_name", sa.String(length=100), nullable=True),
51
+ sa.Column("agents", sa.String(length=1000), nullable=True),
52
+ sa.PrimaryKeyConstraint("id"),
53
+ )
54
+ if "connectors" not in inspector.get_table_names():
55
+ op.create_table(
56
+ "connectors",
57
+ sa.Column("id", sa.Integer(), nullable=False),
58
+ sa.Column("connector_name", sa.String(length=100), nullable=True),
59
+ sa.Column("connector_type", sa.String(length=100), nullable=True),
60
+ sa.Column("connector_url", sa.String(length=100), nullable=True),
61
+ sa.Column("connector_last_updated", sa.DateTime(), nullable=True),
62
+ sa.Column("connector_username", sa.String(length=100), nullable=True),
63
+ sa.Column("connector_password", sa.String(length=100), nullable=True),
64
+ sa.Column("connector_api_key", sa.String(length=100), nullable=True),
65
+ sa.PrimaryKeyConstraint("id"),
66
+ sa.UniqueConstraint("connector_name"),
67
+ )
68
+ if "connectors_available" not in inspector.get_table_names():
69
+ op.create_table(
70
+ "connectors_available",
71
+ sa.Column("id", sa.Integer(), nullable=False),
72
+ sa.Column("connector_name", sa.String(length=100), nullable=True),
73
+ sa.Column("connector_description", sa.String(length=100), nullable=True),
74
+ sa.Column("connector_supports", sa.String(length=100), nullable=True),
75
+ sa.Column("connector_configured", sa.Boolean(), nullable=True),
76
+ sa.Column("connector_verified", sa.Boolean(), nullable=True),
77
+ sa.PrimaryKeyConstraint("id"),
78
+ sa.UniqueConstraint("connector_name"),
79
+ )
80
+ if "disabled_rules" not in inspector.get_table_names():
81
+ op.create_table(
82
+ "disabled_rules",
83
+ sa.Column("id", sa.Integer(), nullable=False),
84
+ sa.Column("rule_id", sa.String(length=100), nullable=True),
85
+ sa.Column("previous_level", sa.String(length=1000), nullable=True),
86
+ sa.Column("new_level", sa.String(length=1000), nullable=True),
87
+ sa.Column("reason_for_disabling", sa.String(length=100), nullable=True),
88
+ sa.Column("date_disabled", sa.DateTime(), nullable=True),
89
+ sa.Column("length_of_time", sa.Integer(), nullable=True),
90
+ sa.PrimaryKeyConstraint("id"),
91
+ )
92
+ if "graylog_metrics_allocation" not in inspector.get_table_names():
93
+ op.create_table(
94
+ "graylog_metrics_allocation",
95
+ sa.Column("id", sa.Integer(), nullable=False),
96
+ sa.Column("input_usage", sa.Float(), nullable=True),
97
+ sa.Column("output_usage", sa.Float(), nullable=True),
98
+ sa.Column("processor_usage", sa.Float(), nullable=True),
99
+ sa.Column("input_1_sec_rate", sa.Float(), nullable=True),
100
+ sa.Column("output_1_sec_rate", sa.Float(), nullable=True),
101
+ sa.Column("total_input", sa.Float(), nullable=True),
102
+ sa.Column("total_output", sa.Float(), nullable=True),
103
+ sa.Column("timestamp", sa.DateTime(), nullable=True),
104
+ sa.PrimaryKeyConstraint("id"),
105
+ )
106
+ if "wazuh_indexer_allocation" not in inspector.get_table_names():
107
+ op.create_table(
108
+ "wazuh_indexer_allocation",
109
+ sa.Column("id", sa.Integer(), nullable=False),
110
+ sa.Column("node", sa.String(length=100), nullable=True),
111
+ sa.Column("disk_used", sa.Float(), nullable=True),
112
+ sa.Column("disk_available", sa.Float(), nullable=True),
113
+ sa.Column("disk_total", sa.Float(), nullable=True),
114
+ sa.Column("disk_percent", sa.Float(), nullable=True),
115
+ sa.Column("timestamp", sa.DateTime(), nullable=True),
116
+ sa.PrimaryKeyConstraint("id"),
117
+ )
118
+ if "sublime_alerts" not in inspector.get_table_names():
119
+ op.create_table(
120
+ "sublime_alerts",
121
+ sa.Column("id", sa.Integer(), nullable=False),
122
+ sa.Column("message_id", sa.String(length=1000), nullable=True),
123
+ sa.Column("timestamp", sa.DateTime(), nullable=True),
124
+ sa.PrimaryKeyConstraint("id"),
125
+ )
126
+
127
# ### end Alembic commands ###
128
129
backend/migrations/versions/e13d34bd3d1f_add_sublime_alerts_model.py
new
+75
@@ -0,0 +1,75 @@
1
+"""Add sublime alerts model.
2
+
3
+Revision ID: e13d34bd3d1f
4
+Revises: 0381c0088cbe
5
+Create Date: 2023-07-12 11:47:15.118862
6
+
7
+"""
8
+import sqlalchemy as sa
9
+from alembic import op
10
+
11
+# revision identifiers, used by Alembic.
12
+revision = "e13d34bd3d1f"
13
+down_revision = "0381c0088cbe"
14
+branch_labels = None
15
+depends_on = None
16
+
17
+
18
+def upgrade():
19
+ # ### commands auto generated by Alembic - please adjust! ###
20
+ op.drop_table("case")
21
+ op.drop_table("graylog_metrics_allocation")
22
+ op.drop_table("artifact")
23
+ op.drop_table("wazuh_indexer_allocation")
24
+ with op.batch_alter_table("sublime_alerts", schema=None) as batch_op:
25
+ batch_op.add_column(sa.Column("timestamp", sa.DateTime(), nullable=True))
26
+
27
+ # ### end Alembic commands ###
28
+
29
+
30
+def downgrade():
31
+ # ### commands auto generated by Alembic - please adjust! ###
32
+ with op.batch_alter_table("sublime_alerts", schema=None) as batch_op:
33
+ batch_op.drop_column("timestamp")
34
+
35
+ op.create_table(
36
+ "wazuh_indexer_allocation",
37
+ sa.Column("id", sa.INTEGER(), nullable=False),
38
+ sa.Column("node", sa.VARCHAR(length=100), nullable=True),
39
+ sa.Column("disk_used", sa.FLOAT(), nullable=True),
40
+ sa.Column("disk_available", sa.FLOAT(), nullable=True),
41
+ sa.Column("disk_total", sa.FLOAT(), nullable=True),
42
+ sa.Column("disk_percent", sa.FLOAT(), nullable=True),
43
+ sa.Column("timestamp", sa.DATETIME(), nullable=True),
44
+ sa.PrimaryKeyConstraint("id"),
45
+ )
46
+ op.create_table(
47
+ "artifact",
48
+ sa.Column("id", sa.INTEGER(), nullable=False),
49
+ sa.Column("artifact_name", sa.VARCHAR(length=100), nullable=True),
50
+ sa.Column("artifact_results", sa.TEXT(), nullable=True),
51
+ sa.Column("hostname", sa.VARCHAR(length=100), nullable=True),
52
+ sa.PrimaryKeyConstraint("id"),
53
+ )
54
+ op.create_table(
55
+ "graylog_metrics_allocation",
56
+ sa.Column("id", sa.INTEGER(), nullable=False),
57
+ sa.Column("input_usage", sa.FLOAT(), nullable=True),
58
+ sa.Column("output_usage", sa.FLOAT(), nullable=True),
59
+ sa.Column("processor_usage", sa.FLOAT(), nullable=True),
60
+ sa.Column("input_1_sec_rate", sa.FLOAT(), nullable=True),
61
+ sa.Column("output_1_sec_rate", sa.FLOAT(), nullable=True),
62
+ sa.Column("total_input", sa.FLOAT(), nullable=True),
63
+ sa.Column("total_output", sa.FLOAT(), nullable=True),
64
+ sa.Column("timestamp", sa.DATETIME(), nullable=True),
65
+ sa.PrimaryKeyConstraint("id"),
66
+ )
67
+ op.create_table(
68
+ "case",
69
+ sa.Column("id", sa.INTEGER(), nullable=False),
70
+ sa.Column("case_id", sa.INTEGER(), nullable=True),
71
+ sa.Column("case_name", sa.VARCHAR(length=100), nullable=True),
72
+ sa.Column("agents", sa.VARCHAR(length=1000), nullable=True),
73
+ sa.PrimaryKeyConstraint("id"),
74
+ )
75
+ # ### end Alembic commands ###
backend/mkdocs.yml
+1
@@ -41,6 +41,7 @@ nav:
41
- Wazuh-Manager: wazuhmanager.md
42
- Velociraptor: velociraptor.md
43
- Shuffle: shuffle.md
44
+ - Sublime: sublime.md
45
46
markdown_extensions:
47
- pymdownx.highlight: