#!/usr/bin/env python # coding=utf-8 __name__ = "trackme_rest_handler_audit.py" __author__ = "TrackMe Limited" __copyright__ = "Copyright 2022-2026, TrackMe Limited, U.K." __credits__ = "TrackMe Limited, U.K." __license__ = "TrackMe Limited, all rights reserved" __version__ = "0.1.0" __maintainer__ = "TrackMe Limited, U.K." __email__ = "support@trackme-solutions.com" __status__ = "PRODUCTION" # Built-in libraries import json import os import sys import time import datetime import hashlib # splunk home splunkhome = os.environ["SPLUNK_HOME"] # append current directory sys.path.append(os.path.dirname(os.path.abspath(__file__))) # import libs import import_declare_test # set logging from trackme_libs_logging import setup_logger logger = setup_logger("trackme.rest.audit", "trackme_rest_api_audit.log") # Redirect global logging to use the same handler import logging logging.getLogger().handlers = logger.handlers logging.getLogger().setLevel(logger.level) # import rest handler import trackme_rest_handler # import trackme libs from trackme_libs import trackme_getloglevel, trackme_reqinfo, trackme_idx_for_tenant # import trackme libs audit from trackme_libs_audit import trackme_audit_gen, trackme_handler_events_gen # import Splunk libs import splunklib.client as client class TrackMeHandlerAudit_v2(trackme_rest_handler.RESTHandler): def __init__(self, command_line, command_arg): super(TrackMeHandlerAudit_v2, self).__init__(command_line, command_arg, logger) def get_resource_group_desc_audit(self, request_info, **kwargs): response = { "resource_group_name": "audit", "resource_group_desc": "These endpoints provide functionality to generate audit events in TrackMe tenants. They are used internally and can also be used to generate additional audit events in TrackMe sub-systems", } return {"payload": response, "status": 200} # Register multiple audit events at once def post_audit_events_v2(self, request_info, **kwargs): describe = False # Retrieve from data try: resp_dict = json.loads(str(request_info.raw_args["payload"])) except Exception as e: resp_dict = None if resp_dict is not None: try: describe = resp_dict["describe"] if describe in ("true", "True"): describe = True except Exception as e: describe = False if not describe: # tenant_id is required try: tenant_id = resp_dict["tenant_id"] except Exception as e: return { "payload": { "action": "failure", "response": "tenant_id is required", }, "status": 500, } # audit_events is required, as a list try: audit_events = resp_dict["audit_events"] except Exception as e: return { "payload": { "action": "failure", "response": "audit_events is required", }, "status": 500, } if not isinstance(audit_events, list): try: audit_events = json.loads(audit_events) except Exception as e: return { "payload": { "action": "failure", "response": f"audit_events should be a list, received type: {type(audit_events)}, content={audit_events}, we have tried to load it as a list but we failed with exception={str(e)}", }, "status": 500, } else: # body is required in this endpoint, if not submitted describe the usage describe = True if describe: response = { "describe": "This endpoint allows registering TrackMe audit events.", "resource_desc": "Register new audit events", "resource_spl_example": '| trackme url="/services/trackme/v2/audit/audit_events_v2" mode="post", body="{\'tenant_id\': ' + "'mytenant', 'audit_events': '[]'" + "'object_attrs': '', 'result': ''}\"", "options": [ { "tenant_id": "The tenant identifier", "audit_events": "A list of audit events to be registered (see below)", "audit_events_fields": [ { "tenant_id": "The tenant identifier", "object": "The object name related to the audit event", "object_category": "The category related to the object", "object_state": "The object state", "object_previous_state": "The object previous state", "latest_flip_time": "The latest flip time", "latest_flip_state": "The latest flip state", "anomaly_reason": "The reason for the anomaly, defaults to 'none' if not specified", "result": "The result of the change operation, either a human readable message or the server technical answer", "comment": 'A comment provided by the user or the automation system for that change, if not provided defaults to "No comment for update."', } ], } ], } return {"payload": response, "status": 200} # set loglevel loglevel = trackme_getloglevel( request_info.system_authtoken, request_info.server_rest_port ) logger.setLevel(loglevel) # Register the new audit event using libs # get TrackMe conf trackme_conf = trackme_reqinfo( request_info.system_authtoken, request_info.server_rest_uri ) trackme_audit_default_idx = trackme_conf["trackme_conf"]["index_settings"][ "trackme_audit_idx" ] # Get the target index for the tenant_id if tenant_id != "all": tenant_idx = trackme_idx_for_tenant( request_info.system_authtoken, request_info.server_rest_uri, tenant_id ) tenant_audit_idx = tenant_idx["trackme_audit_idx"] else: tenant_audit_idx = trackme_audit_default_idx # check audit_events and verify for the presence of the field tenant_id, if not present add it # fix Issue#873 for event in audit_events: if "tenant_id" not in event: event["tenant_id"] = tenant_id # call the function trackme_audit_gen try: trackme_audit_gen(tenant_audit_idx, audit_events) return { "payload": { "action": "success", "result": f"Audit events were processed successfully, {len(audit_events)} events were registered.", }, "status": 200, } except Exception as e: response = { "action": "failure", "response": "audit event has failed", "exception": str(e), } logger.error(json.dumps(response)) return {"payload": response, "status": 500} # Register multiple handler events at once def post_handler_events(self, request_info, **kwargs): describe = False # Retrieve from data try: resp_dict = json.loads(str(request_info.raw_args["payload"])) except Exception as e: resp_dict = None if resp_dict is not None: try: describe = resp_dict["describe"] if describe in ("true", "True"): describe = True except Exception as e: describe = False if not describe: # tenant_id is required try: tenant_id = resp_dict["tenant_id"] except Exception as e: return { "payload": { "action": "failure", "response": "tenant_id is required", }, "status": 500, } # source, optional and defaults to trackme:api if not submitted try: source = resp_dict["source"] except Exception as e: source = "trackme:api" # sourcetype, optional and defaults to trackme:state if not submitted try: sourcetype = resp_dict["sourcetype"] except Exception as e: sourcetype = "trackme:handler" # handler_events is required, as a list try: handler_events = resp_dict["handler_events"] except Exception as e: return { "payload": { "action": "failure", "response": "handler_events is required", }, "status": 500, } if not isinstance(handler_events, list): try: handler_events = json.loads(handler_events) except Exception as e: return { "payload": { "action": "failure", "response": f"handler_events should be a list, received type: {type(handler_events)}, content={handler_events}, we have tried to load it as a list but we failed with exception={str(e)}", }, "status": 500, } else: # body is required in this endpoint, if not submitted describe the usage describe = True if describe: response = { "describe": "This endpoint allows registering multiple handler events at once.", "resource_desc": "Index multiple handler events", "resource_spl_example": '| trackme url="/services/trackme/v2/audit/handler_events" mode="post", body="{\'tenant_id\': ' + "'mytenant', 'handler_events': '[]', 'source': 'trackme:api', 'sourcetype': 'trackme:handler'}\"", "options": [ { "tenant_id": "The tenant identifier", "handler_events": "A list of handler events to be registered", "source": "The source of the handler event, defaults to trackme:api if not specified", "sourcetype": "The sourcetype of the handler event, defaults to trackme:handler if not specified", } ], } return {"payload": response, "status": 200} # set loglevel loglevel = trackme_getloglevel( request_info.system_authtoken, request_info.server_rest_port ) logger.setLevel(loglevel) # Register the new audit event using libs # get TrackMe conf trackme_conf = trackme_reqinfo( request_info.system_authtoken, request_info.server_rest_uri ) trackme_summary_default_idx = trackme_conf["trackme_conf"]["index_settings"][ "trackme_summary_idx" ] # Get the target index for the tenant_id if tenant_id != "all": tenant_idx = trackme_idx_for_tenant( request_info.system_authtoken, request_info.server_rest_uri, tenant_id ) tenant_summary_idx = tenant_idx["trackme_summary_idx"] else: tenant_summary_idx = trackme_summary_default_idx # check audit_events and verify for the presence of the field tenant_id, if not present add it # fix Issue#873 for event in handler_events: if "tenant_id" not in event: event["tenant_id"] = tenant_id # call the function trackme_handler_events_gen try: trackme_handler_events_gen( tenant_summary_idx, handler_events, source, sourcetype ) return { "payload": { "action": "success", "result": f"Handler events were processed successfully, {len(handler_events)} events were registered.", }, "status": 200, } except Exception as e: response = { "action": "failure", "response": "handler event has failed", "exception": str(e), } logger.error(json.dumps(response)) return {"payload": response, "status": 500} # Register a flip event def post_flip_event(self, request_info, **kwargs): describe = False # Retrieve from data try: resp_dict = json.loads(str(request_info.raw_args["payload"])) except Exception as e: resp_dict = None if resp_dict is not None: try: describe = resp_dict["describe"] if describe in ("true", "True"): describe = True except Exception as e: describe = False if not describe: tenant_id = resp_dict["tenant_id"] object_name = resp_dict["object"] object_category = resp_dict["object_category"] object_state = resp_dict["object_state"] object_previous_state = resp_dict["object_previous_state"] latest_flip_time = resp_dict["latest_flip_time"] latest_flip_state = resp_dict["latest_flip_state"] anomaly_reason = resp_dict.get("anomaly_reason", "none") result = resp_dict["result"] else: # body is required in this endpoint, if not submitted describe the usage describe = True if describe: response = { "describe": "This endpoint allows registering a TrackMe flipping event.", "resource_desc": "Register a flipping event", "resource_spl_example": '| trackme url="/services/trackme/v2/vtenants/flip_event" mode="post", body="{\'tenant_id\': ' + "'mytenant', 'object': 'network:pan:traffic', 'object_category': 'splk-dsm', " + "'object_state': 'red', 'object_previous_state': 'green', 'latest_flip_time': '1689613080', 'latest_flip_state': 'red', 'result': 'my message'}\"", "options": [ { "tenant_id": "The tenant identifier", "object": "The object name related to the audit event", "object_category": "The category related to the object", "object_state": "The object state", "object_previous_state": "The object previous state", "latest_flip_time": "The latest flip time", "latest_flip_state": "The latest flip state", "anomaly_reason": "The reason for the anomaly, defaults to 'none' if not specified", "result": "The result of the change operation, either a human readable message or the server technical answer", } ], } return {"payload": response, "status": 200} # set loglevel loglevel = trackme_getloglevel( request_info.system_authtoken, request_info.server_rest_port ) logger.setLevel(loglevel) # TrackMe reqinfo reqinfo_trackme = trackme_reqinfo( request_info.system_authtoken, request_info.server_rest_uri ) trackmeconf = reqinfo_trackme["trackme_conf"]["index_settings"] # get service service = client.connect( owner="nobody", app="trackme", port=request_info.server_rest_port, token=request_info.system_authtoken, timeout=600, ) # Register the new audit event using libs try: # Define Meta splunk_sourcetype = "trackme:flip" splunk_source = "flip_state_change_tracking" splunk_host = request_info.server_servername audit_event = { "_time": str(time.time()), "timeStr": str(datetime.datetime.now()), "tenant_id": tenant_id, "object": object_name, "object_category": object_category, "object_state": object_state, "object_previous_state": object_previous_state, "latest_flip_time": latest_flip_time, "latest_flip_state": latest_flip_state, "anomaly_reason": anomaly_reason, "result": result, } # calculate the event_id as the sha-256 sum of the audit_event event_id = hashlib.sha256(json.dumps(audit_event).encode()).hexdigest() audit_event["event_id"] = event_id audit_event = json.dumps(audit_event) # get the target index tenant_indexes = trackme_idx_for_tenant( request_info.system_authtoken, request_info.server_rest_uri, tenant_id ) # index the audit record target = service.indexes[tenant_indexes["trackme_summary_idx"]] target.submit( event=str(audit_event), source=str(splunk_source), sourcetype=str(splunk_sourcetype), host=str(splunk_host), ) return { "payload": { "action": "success", "result": "The flipping record was registered successfully", }, "status": 200, } except Exception as e: response = { "action": "failure", "response": "flipping event has failed", "exception": str(e), } logger.error(json.dumps(response)) return {"payload": response, "status": 500} # Register a state event def post_state_event(self, request_info, **kwargs): describe = False # Retrieve from data try: resp_dict = json.loads(str(request_info.raw_args["payload"])) except Exception as e: resp_dict = None if resp_dict is not None: try: describe = resp_dict["describe"] if describe in ("true", "True"): describe = True except Exception as e: describe = False if not describe: tenant_id = resp_dict.get("tenant_id", None) if not tenant_id: return { "payload": { "action": "failure", "response": "tenant_id is required", }, "status": 500, } index = resp_dict.get("index", "trackme_summary") sourcetype = resp_dict.get("sourcetype", "trackme:state") source = resp_dict.get("source", "trackme:api") record = resp_dict.get("record", None) if not record: return { "payload": { "action": "failure", "response": "record is required", }, "status": 500, } else: # body is required in this endpoint, if not submitted describe the usage describe = True if describe: response = { "describe": "This endpoint allows registering a TrackMe state event.", "resource_desc": "Register a state event", "resource_spl_example": '| trackme url="/services/trackme/v2/vtenants/state_event" mode="post", body="{\'tenant_id\': ' + "'mytenant', 'source': 'mysource', 'record': 'myrecord'}\"", "options": [ { "tenant_id": "The tenant identifier", "index": "The target index. If not specified, defaults to trackme_summary", "sourcetype": "The value for the sourcetype Splunk Metadata. Defaults to trackme:state if not specified", "source": "The value for the source Splunk Metadata. Defaults to trackme:api if not specified", "record": "The record to be indexed, can be submitted as a string or a JSON object", } ], } return {"payload": response, "status": 200} # set loglevel loglevel = trackme_getloglevel( request_info.system_authtoken, request_info.server_rest_port ) logger.setLevel(loglevel) # get service service = client.connect( owner="nobody", app="trackme", port=request_info.server_rest_port, token=request_info.system_authtoken, timeout=600, ) # add event_id and convert if isinstance(record, dict): # calculate the event_id as the sha-256 sum of the audit_event event_id = hashlib.sha256(json.dumps(record).encode()).hexdigest() record["event_id"] = event_id record = json.dumps(record) # index the audit record target = service.indexes[index] try: target.submit( event=record, source=source, sourcetype=sourcetype, host=request_info.server_servername, ) return { "payload": { "action": "success", "result": "The state record was registered successfully", }, "status": 200, } except Exception as e: response = { "action": "failure", "response": "state event has failed", "exception": str(e), } logger.error(json.dumps(response, indent=2)) return { "payload": response, "status": 500, }