Healthyagents by customer (#74)
* wazuh velo and full healthchecks done by customer determines the agents that belong to the given customer and endpoints to run wazuh, velo, and full healthchecks on the customer's agents * precommit fixes
taylor_socfortress committed
Jul 26, 2023 at 12:26 UTC
d492c8ec894c66a260372963b2480896a0946432
3 files changed
+249
-7
backend/app/routes/customers.py
+103
@@ -9,6 +9,7 @@ from app.models.agents import AgentMetadata
9
from app.models.agents import agent_metadatas_schema
10
from app.services.Customers.universal import UniversalCustomers
11
from app.services.Customers.universal import UniversalCustomersMeta
12
+from app.services.Healthchecks.agents import HealthcheckAgentsService
13
14
bp = Blueprint("customers", __name__)
15
@@ -309,3 +310,105 @@ def read_customer_agents(id: int):
310
200,
311
)
312
return jsonify({"message": "No agents found for this customer.", "success": False}), 404
313
+
314
+
315
+@bp.route("/customers/healthchecks/agents/<int:id>/wazuh", methods=["GET"])
316
+def read_customer_agents_healthchecks_wazuh(id: int):
317
+ """
318
+ Endpoint to fetch the `customerCode` from the `customers_meta` table for the given customer id.
319
+ Then, it uses the customer code to fetch all agents from the `agent_metadata` table where the `customerCode`
320
+ is a wildcard match on the `label` column within the `agent_metadata` table.
321
+
322
+ Returns:
323
+ Tuple[jsonify, int]: A Tuple where the first element is a JSON response
324
+ containing the customer code and the list of agents, and the second element is
325
+ the HTTP status code.
326
+ """
327
+ logger.info(f"Received request to get customer agents with id {id}")
328
+ customer_meta = UniversalCustomersMeta.read_by_id(id)
329
+ if customer_meta:
330
+ customer_code = customer_meta["customerCode"]
331
+ agents = AgentMetadata.query.filter(AgentMetadata.label.like(f"%{customer_code}%")).all()
332
+ if agents:
333
+ agents_list = agent_metadatas_schema.dump(agents)
334
+ agent_health = HealthcheckAgentsService().perform_healthcheck_wazuh(agents=agents_list)
335
+ return (
336
+ jsonify(
337
+ {
338
+ "customerCode": customer_code,
339
+ "agents": agent_health,
340
+ "message": "Customer Wazuh agents health fetched.",
341
+ "success": True,
342
+ },
343
+ ),
344
+ 200,
345
+ )
346
+ return jsonify({"message": "No wazuh agents found for this customer.", "success": False}), 404
347
+
348
+
349
+@bp.route("/customers/healthchecks/agents/<int:id>/velociraptor", methods=["GET"])
350
+def read_customer_agents_healthchecks_velociraptor(id: int):
351
+ """
352
+ Endpoint to fetch the `customerCode` from the `customers_meta` table for the given customer id.
353
+ Then, it uses the customer code to fetch all agents from the `agent_metadata` table where the `customerCode`
354
+ is a wildcard match on the `label` column within the `agent_metadata` table.
355
+
356
+ Returns:
357
+ Tuple[jsonify, int]: A Tuple where the first element is a JSON response
358
+ containing the customer code and the list of agents, and the second element is
359
+ the HTTP status code.
360
+ """
361
+ logger.info(f"Received request to get customer agents with id {id}")
362
+ customer_meta = UniversalCustomersMeta.read_by_id(id)
363
+ if customer_meta:
364
+ customer_code = customer_meta["customerCode"]
365
+ agents = AgentMetadata.query.filter(AgentMetadata.label.like(f"%{customer_code}%")).all()
366
+ if agents:
367
+ agents_list = agent_metadatas_schema.dump(agents)
368
+ agent_health = HealthcheckAgentsService().perform_healthcheck_velociraptor(agents=agents_list)
369
+ return (
370
+ jsonify(
371
+ {
372
+ "customerCode": customer_code,
373
+ "agents": agent_health,
374
+ "message": "Customer Velociraptor agents health fetched.",
375
+ "success": True,
376
+ },
377
+ ),
378
+ 200,
379
+ )
380
+ return jsonify({"message": "No velociraptor agents found for this customer.", "success": False}), 404
381
+
382
+
383
+@bp.route("/customers/healthchecks/agents/<int:id>/full", methods=["GET"])
384
+def read_customer_agents_healthchecks_full(id: int):
385
+ """
386
+ Endpoint to fetch the `customerCode` from the `customers_meta` table for the given customer id.
387
+ Then, it uses the customer code to fetch all agents from the `agent_metadata` table where the `customerCode`
388
+ is a wildcard match on the `label` column within the `agent_metadata` table.
389
+
390
+ Returns:
391
+ Tuple[jsonify, int]: A Tuple where the first element is a JSON response
392
+ containing the customer code and the list of agents, and the second element is
393
+ the HTTP status code.
394
+ """
395
+ logger.info(f"Received request to get customer agents with id {id}")
396
+ customer_meta = UniversalCustomersMeta.read_by_id(id)
397
+ if customer_meta:
398
+ customer_code = customer_meta["customerCode"]
399
+ agents = AgentMetadata.query.filter(AgentMetadata.label.like(f"%{customer_code}%")).all()
400
+ if agents:
401
+ agents_list = agent_metadatas_schema.dump(agents)
402
+ agent_health = HealthcheckAgentsService().perform_healthcheck_full(agents=agents_list, check_logs=True)
403
+ return (
404
+ jsonify(
405
+ {
406
+ "customerCode": customer_code,
407
+ "agents": agent_health,
408
+ "message": "Customer full agents health fetched.",
409
+ "success": True,
410
+ },
411
+ ),
412
+ 200,
413
+ )
414
+ return jsonify({"message": "No agents found for this customer.", "success": False}), 404
backend/app/services/Healthchecks/agents.py
+2
-7
@@ -92,13 +92,8 @@ class HealthcheckAgentsService:
92
continue
93
response = self.universal_service.run_query(query, index, size=1)
94
if response["query_results"]["hits"]["total"]["value"] > 0:
95
- return True
96
- # Check the next interval only if the previous interval didn't return results
97
- if interval == 1:
98
- break
99
- # Proceed to next agent if logs are found
100
- break
101
- return False
95
+ return True # Logs found, stop checking.
96
+ return False # No logs found after checking all intervals and indices.
97
98
@staticmethod
99
def _generate_recent_logs_query(agent_hostname: str, minutes: int) -> Dict:
backend/app/static/swagger.json
+144
@@ -880,6 +880,150 @@
880
}
881
}
882
},
883
+ "/customers/healthchecks/agents/{id}/wazuh": {
884
+ "get": {
885
+ "tags": ["Customers"],
886
+ "summary": "Get a customer",
887
+ "description": "Endpoint to retrieve a customer details from the `customers` and `customers_meta` tables.",
888
+ "parameters": [
889
+ {
890
+ "name": "id",
891
+ "in": "path",
892
+ "description": "ID of the customer to retrieve",
893
+ "required": true,
894
+ "type": "integer"
895
+ }
896
+ ],
897
+ "responses": {
898
+ "200": {
899
+ "description": "Successful operation",
900
+ "content": {
901
+ "application/json": {
902
+ "schema": {
903
+ "type": "object",
904
+ "properties": {
905
+ "message": { "type": "string", "example": "Customer retrieved successfully." },
906
+ "success": { "type": "boolean", "example": true },
907
+ "customer": { "$ref": "#/definitions/Customer" },
908
+ "customerMeta": { "$ref": "#/definitions/CustomerMeta" }
909
+ }
910
+ }
911
+ }
912
+ }
913
+ },
914
+ "404": {
915
+ "description": "Customer not found",
916
+ "content": {
917
+ "application/json": {
918
+ "schema": {
919
+ "type": "object",
920
+ "properties": {
921
+ "message": { "type": "string", "example": "Customer not found." },
922
+ "success": { "type": "boolean", "example": false }
923
+ }
924
+ }
925
+ }
926
+ }
927
+ }
928
+ }
929
+ }
930
+ },
931
+ "/customers/healthchecks/agents/{id}/velociraptor": {
932
+ "get": {
933
+ "tags": ["Customers"],
934
+ "summary": "Get a customer",
935
+ "description": "Endpoint to retrieve a customer details from the `customers` and `customers_meta` tables.",
936
+ "parameters": [
937
+ {
938
+ "name": "id",
939
+ "in": "path",
940
+ "description": "ID of the customer to retrieve",
941
+ "required": true,
942
+ "type": "integer"
943
+ }
944
+ ],
945
+ "responses": {
946
+ "200": {
947
+ "description": "Successful operation",
948
+ "content": {
949
+ "application/json": {
950
+ "schema": {
951
+ "type": "object",
952
+ "properties": {
953
+ "message": { "type": "string", "example": "Customer retrieved successfully." },
954
+ "success": { "type": "boolean", "example": true },
955
+ "customer": { "$ref": "#/definitions/Customer" },
956
+ "customerMeta": { "$ref": "#/definitions/CustomerMeta" }
957
+ }
958
+ }
959
+ }
960
+ }
961
+ },
962
+ "404": {
963
+ "description": "Customer not found",
964
+ "content": {
965
+ "application/json": {
966
+ "schema": {
967
+ "type": "object",
968
+ "properties": {
969
+ "message": { "type": "string", "example": "Customer not found." },
970
+ "success": { "type": "boolean", "example": false }
971
+ }
972
+ }
973
+ }
974
+ }
975
+ }
976
+ }
977
+ }
978
+ },
979
+ "/customers/healthchecks/agents/{id}/full": {
980
+ "get": {
981
+ "tags": ["Customers"],
982
+ "summary": "Get a customer",
983
+ "description": "Endpoint to retrieve a customer details from the `customers` and `customers_meta` tables.",
984
+ "parameters": [
985
+ {
986
+ "name": "id",
987
+ "in": "path",
988
+ "description": "ID of the customer to retrieve",
989
+ "required": true,
990
+ "type": "integer"
991
+ }
992
+ ],
993
+ "responses": {
994
+ "200": {
995
+ "description": "Successful operation",
996
+ "content": {
997
+ "application/json": {
998
+ "schema": {
999
+ "type": "object",
1000
+ "properties": {
1001
+ "message": { "type": "string", "example": "Customer retrieved successfully." },
1002
+ "success": { "type": "boolean", "example": true },
1003
+ "customer": { "$ref": "#/definitions/Customer" },
1004
+ "customerMeta": { "$ref": "#/definitions/CustomerMeta" }
1005
+ }
1006
+ }
1007
+ }
1008
+ }
1009
+ },
1010
+ "404": {
1011
+ "description": "Customer not found",
1012
+ "content": {
1013
+ "application/json": {
1014
+ "schema": {
1015
+ "type": "object",
1016
+ "properties": {
1017
+ "message": { "type": "string", "example": "Customer not found." },
1018
+ "success": { "type": "boolean", "example": false }
1019
+ }
1020
+ }
1021
+ }
1022
+ }
1023
+ }
1024
+ }
1025
+ }
1026
+ },
1027
"/agents": {
1028
"get": {
1029
"tags": ["Agents"],