#!/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, }