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.
517 lines
22 KiB
517 lines
22 KiB
#!/usr/bin/env python
|
|
# coding=utf-8
|
|
|
|
__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"
|
|
|
|
# Standard library imports
|
|
import os
|
|
import sys
|
|
import json
|
|
import logging
|
|
|
|
# Networking and URL handling imports
|
|
import requests
|
|
from urllib.parse import urlencode
|
|
import urllib3
|
|
|
|
# Disable insecure request warnings for urllib3
|
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
|
|
# splunk home
|
|
splunkhome = os.environ["SPLUNK_HOME"]
|
|
|
|
# append lib
|
|
sys.path.append(os.path.join(splunkhome, "etc", "apps", "trackme", "lib"))
|
|
|
|
# logging:
|
|
# To avoid overriding logging destination of callers, the libs will not set on purpose any logging definition
|
|
# and rely on callers themselves
|
|
|
|
|
|
def get_suffix(s):
|
|
parts = s.split("-")
|
|
return parts[-1]
|
|
|
|
|
|
def getfieldvalue(jsonData, fieldName):
|
|
value = jsonData.get(fieldName, "null")
|
|
if isinstance(value, bool):
|
|
# Preserve numerical boolean values
|
|
return int(value)
|
|
return value
|
|
|
|
|
|
def has_user_access(effective_roles, record):
|
|
tenant_roles_admin = (
|
|
set(record["tenant_roles_admin"])
|
|
if isinstance(record["tenant_roles_admin"], list)
|
|
else set(record["tenant_roles_admin"].split(","))
|
|
)
|
|
tenant_roles_power = (
|
|
set(record["tenant_roles_power"])
|
|
if isinstance(record["tenant_roles_power"], list)
|
|
else set(record["tenant_roles_power"].split(","))
|
|
)
|
|
tenant_roles_user = (
|
|
set(record["tenant_roles_user"])
|
|
if isinstance(record["tenant_roles_user"], list)
|
|
else set(record["tenant_roles_user"].split(","))
|
|
)
|
|
allowed_roles = (
|
|
tenant_roles_admin
|
|
| tenant_roles_user
|
|
| tenant_roles_power
|
|
| {"admin", "trackme_admin", "sc_admin"}
|
|
)
|
|
|
|
return bool(set(effective_roles) & allowed_roles)
|
|
|
|
|
|
def get_effective_roles(user_roles, roles_dict):
|
|
effective_roles = set(user_roles) # start with user's direct roles
|
|
to_check = list(user_roles) # roles to be checked for inherited roles
|
|
|
|
while to_check:
|
|
current_role = to_check.pop()
|
|
inherited_roles = roles_dict.get(current_role, [])
|
|
for inherited_role in inherited_roles:
|
|
if inherited_role not in effective_roles:
|
|
effective_roles.add(inherited_role)
|
|
to_check.append(inherited_role)
|
|
|
|
return effective_roles
|
|
|
|
|
|
def process_exec_summary(exec_summary_json):
|
|
try:
|
|
summary_data = json.loads(exec_summary_json)
|
|
except json.JSONDecodeError:
|
|
raise ValueError("Invalid JSON input")
|
|
|
|
components_data = {}
|
|
for item in summary_data.values():
|
|
component = item["component"]
|
|
|
|
if component not in components_data:
|
|
components_data[component] = {"last_exec": 0.0, "status": 0}
|
|
|
|
# get last_exec
|
|
try:
|
|
last_exec = float(item["last_exec"])
|
|
except Exception as e:
|
|
last_exec = 0.0
|
|
|
|
if last_exec > components_data[component]["last_exec"]:
|
|
components_data[component]["last_exec"] = last_exec
|
|
|
|
if item["last_status"] == "failure":
|
|
components_data[component]["status"] = 1
|
|
|
|
return components_data
|
|
|
|
|
|
def get_vtenants_accounts(session_key, splunkd_uri):
|
|
# Define an header for requests authenticated communications with splunkd
|
|
header = {
|
|
"Authorization": "Splunk %s" % session_key,
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
# Add the vtenant account
|
|
url = "%s/services/trackme/v2/vtenants/vtenants_accounts" % (splunkd_uri)
|
|
|
|
# Proceed
|
|
try:
|
|
response = requests.post(url, headers=header, verify=False, timeout=600)
|
|
if response.status_code not in (200, 201, 204):
|
|
msg = f'get vtenant account has failed, response.status_code="{response.status_code}", response.text="{response.text}"'
|
|
raise Exception(msg)
|
|
else:
|
|
vtenants_account = response.json()
|
|
logging.debug(
|
|
f'get vtenant account was operated successfully, response.status_code="{response.status_code}"'
|
|
)
|
|
logging.debug(f"vtenants_account={json.dumps(vtenants_account, indent=2)}")
|
|
except Exception as e:
|
|
msg = f'get vtenant account has failed, exception="{str(e)}"'
|
|
logging.error(msg)
|
|
raise Exception(msg)
|
|
|
|
return vtenants_account
|
|
|
|
|
|
def trackmeload(session_key, splunkd_uri, service, users, roles, username, mode):
|
|
# Get roles for the current user
|
|
username_roles = []
|
|
for user in users:
|
|
if user.name == username:
|
|
username_roles = user.roles
|
|
logging.info(f'username="{username}", roles="{username_roles}"')
|
|
|
|
# get roles
|
|
roles_dict = {}
|
|
|
|
for role in roles:
|
|
imported_roles_value = role.content.get("imported_roles", [])
|
|
if imported_roles_value: # Check if it has a non-empty value
|
|
roles_dict[role.name] = imported_roles_value
|
|
|
|
logging.debug(f"roles_dict={json.dumps(roles_dict, indent=2)}")
|
|
|
|
# get effective roles, which takes into account both direct membership and inheritance
|
|
effective_roles = get_effective_roles(username_roles, roles_dict)
|
|
|
|
# Data collection
|
|
collection_name = "kv_trackme_virtual_tenants"
|
|
collection = service.kvstore[collection_name]
|
|
|
|
# Summary state collection
|
|
summary_state_collection_name = "kv_trackme_virtual_tenants_entities_summary"
|
|
summary_state_collection = service.kvstore[summary_state_collection_name]
|
|
|
|
# get vtenants_account
|
|
try:
|
|
vtenants_account = get_vtenants_accounts(
|
|
session_key,
|
|
splunkd_uri,
|
|
)
|
|
except Exception as e:
|
|
raise Exception(f'get_vtenants_accounts has failed with exception="{str(e)}"')
|
|
|
|
# final yield record
|
|
yield_record = []
|
|
|
|
# Get the records
|
|
filtered_records = []
|
|
try:
|
|
records = collection.data.query()
|
|
|
|
# loop through records, for each record get the alias value from vtenants_account
|
|
# and add to the record
|
|
for record in records:
|
|
# get the tenant_id
|
|
tenant_id = record["tenant_id"]
|
|
|
|
# get the alias
|
|
alias = vtenants_account[tenant_id].get("alias", tenant_id)
|
|
|
|
# add alias to record
|
|
record["tenant_alias"] = alias
|
|
|
|
sorted_records = sorted(records, key=lambda x: x["tenant_alias"])
|
|
|
|
# Filter records based on user access
|
|
filtered_records = [
|
|
record
|
|
for record in sorted_records
|
|
if has_user_access(effective_roles, record)
|
|
or username in ("splunk-system-user")
|
|
]
|
|
|
|
# render
|
|
for filtered_record in filtered_records:
|
|
try:
|
|
# log debug
|
|
logging.info(
|
|
f'Inspecting record="{json.dumps(filtered_record, indent=2)}"'
|
|
)
|
|
|
|
# get the tenant_id
|
|
tenant_id = filtered_record["tenant_id"]
|
|
|
|
# lookup the summary state
|
|
try:
|
|
query_string = {
|
|
"tenant_id": tenant_id,
|
|
}
|
|
summary_state_record = summary_state_collection.data.query(
|
|
query=json.dumps(query_string)
|
|
)[0]
|
|
logging.debug(
|
|
f'tenant_id="{tenant_id}", summary state found, record="{json.dumps(summary_state_record)}"'
|
|
)
|
|
|
|
# add summary_state_record fields to filtered_record
|
|
for k, v in summary_state_record.items():
|
|
if k != "tenant_id" and not k.startswith("_"):
|
|
filtered_record[k] = v
|
|
|
|
except Exception as e:
|
|
logging.debug(
|
|
f'tenant_id="{tenant_id}", no summary state is available, exception="{str(e)}"'
|
|
)
|
|
|
|
# Process tenant_objects_exec_summary field
|
|
if "tenant_objects_exec_summary" in filtered_record:
|
|
try:
|
|
exec_summary_data = process_exec_summary(
|
|
filtered_record["tenant_objects_exec_summary"]
|
|
)
|
|
for component, data in exec_summary_data.items():
|
|
# get suffix
|
|
short_component = get_suffix(component)
|
|
|
|
filtered_record[f"{short_component}_status"] = data[
|
|
"status"
|
|
]
|
|
filtered_record[f"{short_component}_last_exec"] = data[
|
|
"last_exec"
|
|
]
|
|
except Exception as e:
|
|
logging.error(
|
|
f'tenant_id="{tenant_id}", failed to process exec summary, exception="{str(e)}"'
|
|
)
|
|
|
|
# yield needs to be explicit and gen field names and values explicitly
|
|
try:
|
|
tenant_id = getfieldvalue(filtered_record, "tenant_id")
|
|
description = vtenants_account[tenant_id].get("description", "")
|
|
alias = vtenants_account[tenant_id].get("alias", tenant_id)
|
|
|
|
new_record = {
|
|
"tenant_id": tenant_id,
|
|
"tenant_alias": alias,
|
|
"tenant_status": getfieldvalue(
|
|
filtered_record, "tenant_status"
|
|
),
|
|
"tenant_desc": description,
|
|
"tenant_owner": getfieldvalue(filtered_record, "tenant_owner"),
|
|
"tenant_roles_admin": getfieldvalue(
|
|
filtered_record, "tenant_roles_admin"
|
|
),
|
|
"tenant_roles_user": getfieldvalue(
|
|
filtered_record, "tenant_roles_user"
|
|
),
|
|
"tenant_dsm_enabled": getfieldvalue(
|
|
filtered_record, "tenant_dsm_enabled"
|
|
),
|
|
"tenant_cim_enabled": getfieldvalue(
|
|
filtered_record, "tenant_cim_enabled"
|
|
),
|
|
"tenant_flx_enabled": getfieldvalue(
|
|
filtered_record, "tenant_flx_enabled"
|
|
),
|
|
"tenant_fqm_enabled": getfieldvalue(
|
|
filtered_record, "tenant_fqm_enabled"
|
|
),
|
|
"tenant_dhm_enabled": getfieldvalue(
|
|
filtered_record, "tenant_dhm_enabled"
|
|
),
|
|
"tenant_mhm_enabled": getfieldvalue(
|
|
filtered_record, "tenant_mhm_enabled"
|
|
),
|
|
"tenant_wlk_enabled": getfieldvalue(
|
|
filtered_record, "tenant_wlk_enabled"
|
|
),
|
|
"tenant_dhm_root_constraint": getfieldvalue(
|
|
filtered_record, "tenant_dhm_root_constraint"
|
|
),
|
|
"tenant_mhm_root_constraint": getfieldvalue(
|
|
filtered_record, "tenant_mhm_root_constraint"
|
|
),
|
|
"tenant_cim_objects": getfieldvalue(
|
|
filtered_record, "tenant_cim_objects"
|
|
),
|
|
"tenant_alert_objects": getfieldvalue(
|
|
filtered_record, "tenant_alert_objects"
|
|
),
|
|
"tenant_dsm_hybrid_objects": getfieldvalue(
|
|
filtered_record, "tenant_dsm_hybrid_objects"
|
|
),
|
|
"tenant_objects_exec_summary": getfieldvalue(
|
|
filtered_record, "tenant_objects_exec_summary"
|
|
),
|
|
"tenant_idx_settings": getfieldvalue(
|
|
filtered_record, "tenant_idx_settings"
|
|
),
|
|
"tenant_replica": getfieldvalue(
|
|
filtered_record, "tenant_replica"
|
|
),
|
|
"key": getfieldvalue(filtered_record, "_key"),
|
|
"report_entities_count": getfieldvalue(
|
|
filtered_record, "report_entities_count"
|
|
),
|
|
"dhm_entities": getfieldvalue(filtered_record, "dhm_entities"),
|
|
"dhm_critical_red_priority": getfieldvalue(
|
|
filtered_record, "dhm_critical_red_priority"
|
|
),
|
|
"dhm_high_red_priority": getfieldvalue(
|
|
filtered_record, "dhm_high_red_priority"
|
|
),
|
|
"dhm_last_exec": getfieldvalue(
|
|
filtered_record, "dhm_last_exec"
|
|
),
|
|
"dhm_low_red_priority": getfieldvalue(
|
|
filtered_record, "dhm_low_red_priority"
|
|
),
|
|
"dhm_medium_red_priority": getfieldvalue(
|
|
filtered_record, "dhm_medium_red_priority"
|
|
),
|
|
"dsm_entities": getfieldvalue(filtered_record, "dsm_entities"),
|
|
"dsm_critical_red_priority": getfieldvalue(
|
|
filtered_record, "dsm_critical_red_priority"
|
|
),
|
|
"dsm_high_red_priority": getfieldvalue(
|
|
filtered_record, "dsm_high_red_priority"
|
|
),
|
|
"dsm_last_exec": getfieldvalue(
|
|
filtered_record, "dsm_last_exec"
|
|
),
|
|
"dsm_low_red_priority": getfieldvalue(
|
|
filtered_record, "dsm_low_red_priority"
|
|
),
|
|
"dsm_medium_red_priority": getfieldvalue(
|
|
filtered_record, "dsm_medium_red_priority"
|
|
),
|
|
"mhm_entities": getfieldvalue(filtered_record, "mhm_entities"),
|
|
"mhm_critical_red_priority": getfieldvalue(
|
|
filtered_record, "mhm_critical_red_priority"
|
|
),
|
|
"mhm_high_red_priority": getfieldvalue(
|
|
filtered_record, "mhm_high_red_priority"
|
|
),
|
|
"mhm_last_exec": getfieldvalue(
|
|
filtered_record, "mhm_last_exec"
|
|
),
|
|
"mhm_low_red_priority": getfieldvalue(
|
|
filtered_record, "mhm_low_red_priority"
|
|
),
|
|
"mhm_medium_red_priority": getfieldvalue(
|
|
filtered_record, "mhm_medium_red_priority"
|
|
),
|
|
"cim_entities": getfieldvalue(filtered_record, "cim_entities"),
|
|
"cim_critical_red_priority": getfieldvalue(
|
|
filtered_record, "cim_critical_red_priority"
|
|
),
|
|
"cim_high_red_priority": getfieldvalue(
|
|
filtered_record, "cim_high_red_priority"
|
|
),
|
|
"cim_last_exec": getfieldvalue(
|
|
filtered_record, "cim_last_exec"
|
|
),
|
|
"cim_low_red_priority": getfieldvalue(
|
|
filtered_record, "cim_low_red_priority"
|
|
),
|
|
"cim_medium_red_priority": getfieldvalue(
|
|
filtered_record, "cim_medium_red_priority"
|
|
),
|
|
"flx_entities": getfieldvalue(filtered_record, "flx_entities"),
|
|
"flx_critical_red_priority": getfieldvalue(
|
|
filtered_record, "flx_critical_red_priority"
|
|
),
|
|
"flx_high_red_priority": getfieldvalue(
|
|
filtered_record, "flx_high_red_priority"
|
|
),
|
|
"flx_last_exec": getfieldvalue(
|
|
filtered_record, "flx_last_exec"
|
|
),
|
|
"flx_low_red_priority": getfieldvalue(
|
|
filtered_record, "flx_low_red_priority"
|
|
),
|
|
"flx_medium_red_priority": getfieldvalue(
|
|
filtered_record, "flx_medium_red_priority"
|
|
),
|
|
"fqm_entities": getfieldvalue(filtered_record, "fqm_entities"),
|
|
"fqm_critical_red_priority": getfieldvalue(
|
|
filtered_record, "fqm_critical_red_priority"
|
|
),
|
|
"fqm_high_red_priority": getfieldvalue(
|
|
filtered_record, "fqm_high_red_priority"
|
|
),
|
|
"fqm_last_exec": getfieldvalue(
|
|
filtered_record, "fqm_last_exec"
|
|
),
|
|
"fqm_low_red_priority": getfieldvalue(
|
|
filtered_record, "fqm_low_red_priority"
|
|
),
|
|
"fqm_medium_red_priority": getfieldvalue(
|
|
filtered_record, "fqm_medium_red_priority"
|
|
),
|
|
"wlk_entities": getfieldvalue(filtered_record, "wlk_entities"),
|
|
"wlk_critical_red_priority": getfieldvalue(
|
|
filtered_record, "wlk_critical_red_priority"
|
|
),
|
|
"wlk_high_red_priority": getfieldvalue(
|
|
filtered_record, "wlk_high_red_priority"
|
|
),
|
|
"wlk_last_exec": getfieldvalue(
|
|
filtered_record, "wlk_last_exec"
|
|
),
|
|
"wlk_low_red_priority": getfieldvalue(
|
|
filtered_record, "wlk_low_red_priority"
|
|
),
|
|
"wlk_medium_red_priority": getfieldvalue(
|
|
filtered_record, "wlk_medium_red_priority"
|
|
),
|
|
"all_status": getfieldvalue(filtered_record, "all_status"),
|
|
"dhm_status": getfieldvalue(filtered_record, "dhm_status"),
|
|
"dsm_status": getfieldvalue(filtered_record, "dsm_status"),
|
|
"mhm_status": getfieldvalue(filtered_record, "mhm_status"),
|
|
"cim_status": getfieldvalue(filtered_record, "cim_status"),
|
|
"flx_status": getfieldvalue(filtered_record, "flx_status"),
|
|
"fqm_status": getfieldvalue(filtered_record, "fqm_status"),
|
|
"wlk_status": getfieldvalue(filtered_record, "wlk_status"),
|
|
"all_last_exec": getfieldvalue(
|
|
filtered_record, "all_last_exec"
|
|
),
|
|
"dhm_last_exec": getfieldvalue(
|
|
filtered_record, "dhm_last_exec"
|
|
),
|
|
"dsm_last_exec": getfieldvalue(
|
|
filtered_record, "dsm_last_exec"
|
|
),
|
|
"mhm_last_exec": getfieldvalue(
|
|
filtered_record, "mhm_last_exec"
|
|
),
|
|
"cim_last_exec": getfieldvalue(
|
|
filtered_record, "cim_last_exec"
|
|
),
|
|
"flx_last_exec": getfieldvalue(
|
|
filtered_record, "flx_last_exec"
|
|
),
|
|
"fqm_last_exec": getfieldvalue(
|
|
filtered_record, "fqm_last_exec"
|
|
),
|
|
"wlk_last_exec": getfieldvalue(
|
|
filtered_record, "wlk_last_exec"
|
|
),
|
|
}
|
|
|
|
yield_record.append(new_record)
|
|
except Exception as e:
|
|
logging.error(
|
|
f'Failed to process tenant "{tenant_id}", skipping record, this likely indicates a corrupted Virtual Tenant, run a POST call against /services/trackme/v2/vtenants/admin/del_tenant to purge the faulty tenant, exception: {str(e)}'
|
|
)
|
|
continue
|
|
except Exception as e:
|
|
logging.error(
|
|
f"Failed to process tenant record, skipping. Exception: {str(e)}"
|
|
)
|
|
continue
|
|
|
|
except Exception as e:
|
|
raise Exception(
|
|
f'Failed to retrieve tenants, this likely indicates a corrupted Virtual Tenant, run a POST call against /services/trackme/v2/vtenants/admin/del_tenant to purge the faulty tenant, exception="{str(e)}"'
|
|
)
|
|
|
|
# return
|
|
if mode == "full":
|
|
return {
|
|
"tenants": yield_record,
|
|
}
|
|
|
|
elif mode == "expanded":
|
|
yield_response = []
|
|
for tenant_record in yield_record:
|
|
yield_response.append(tenant_record)
|
|
return {
|
|
"tenants": yield_response,
|
|
}
|