You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
604 lines
23 KiB
604 lines
23 KiB
#!/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': '[<events list>]'"
|
|
+ "'object_attrs': '<object attributes>', 'result': '<operation results>'}\"",
|
|
"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': '[<events list>]', '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,
|
|
}
|