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.
777 lines
27 KiB
777 lines
27 KiB
#!/usr/bin/env python
|
|
# coding=utf-8
|
|
|
|
__author__ = "TrackMe Limited"
|
|
__copyright__ = "Copyright 2022-2026, TrackMe Limited, U.K."
|
|
__credits__ = ["Guilhem Marchand"]
|
|
__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 time
|
|
import json
|
|
import random
|
|
import re
|
|
import logging
|
|
from logging.handlers import RotatingFileHandler
|
|
|
|
# Third-party library imports
|
|
import urllib3
|
|
import requests
|
|
from requests.structures import CaseInsensitiveDict
|
|
import urllib.parse
|
|
|
|
# Disable warnings for insecure requests
|
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
|
|
# set splunkhome
|
|
splunkhome = os.environ["SPLUNK_HOME"]
|
|
|
|
# set logging
|
|
filehandler = RotatingFileHandler(
|
|
"%s/var/log/splunk/trackme_splunkremotesearch.log" % splunkhome,
|
|
mode="a",
|
|
maxBytes=10000000,
|
|
backupCount=1,
|
|
)
|
|
formatter = logging.Formatter(
|
|
"%(asctime)s %(levelname)s %(filename)s %(funcName)s %(lineno)d %(message)s"
|
|
)
|
|
logging.Formatter.converter = time.gmtime
|
|
filehandler.setFormatter(formatter)
|
|
log = logging.getLogger() # root logger - Good to get it only once.
|
|
for hdlr in log.handlers[:]: # remove the existing file handlers
|
|
if isinstance(hdlr, logging.FileHandler):
|
|
log.removeHandler(hdlr)
|
|
log.addHandler(filehandler) # set the new handler
|
|
# set the log level to INFO, DEBUG as the default is ERROR
|
|
log.setLevel(logging.INFO)
|
|
|
|
# append current directory
|
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
# import libs
|
|
import import_declare_test
|
|
|
|
# import Splunk libs
|
|
from splunklib.searchcommands import (
|
|
dispatch,
|
|
GeneratingCommand,
|
|
Configuration,
|
|
Option,
|
|
validators,
|
|
)
|
|
import splunklib.client as client
|
|
|
|
# import trackme libs
|
|
from trackme_libs import (
|
|
trackme_reqinfo,
|
|
trackme_register_tenant_object_summary_gen_non_persistent,
|
|
trackme_register_tenant_object_summary_gen_persistent,
|
|
run_splunk_search,
|
|
)
|
|
|
|
# import trackme licensing libs
|
|
from trackme_libs_licensing import trackme_check_license
|
|
|
|
|
|
@Configuration(distributed=False)
|
|
class SplunkRemoteSearch(GeneratingCommand):
|
|
account = Option(
|
|
doc="""
|
|
**Syntax:** **account=****
|
|
**Description:** Splunk remote deployment account to be used for the query.""",
|
|
require=True,
|
|
default=None,
|
|
)
|
|
|
|
search = Option(
|
|
doc="""
|
|
**Syntax:** **search=****
|
|
**Description:** The Splunk query to be executed.""",
|
|
require=True,
|
|
default=None,
|
|
)
|
|
|
|
earliest = Option(
|
|
doc="""
|
|
**Syntax:** **earliest=****
|
|
**Description:** The earliest time for the search.""",
|
|
require=False,
|
|
default=None,
|
|
)
|
|
|
|
latest = Option(
|
|
doc="""
|
|
**Syntax:** **latest=****
|
|
**Description:** The latest time for the search.""",
|
|
require=False,
|
|
default=None,
|
|
)
|
|
|
|
register_component = Option(
|
|
doc="""
|
|
**Syntax:** **register_component=****
|
|
**Description:** If the search is invoked by a tracker, register_component can be called to capture and regoster any execution exception.""",
|
|
require=False,
|
|
default=False,
|
|
)
|
|
|
|
component = Option(
|
|
doc="""
|
|
**Syntax:** **component=****
|
|
**Description:** If register_component is set, a value for component is required.""",
|
|
require=False,
|
|
default=None,
|
|
validate=validators.Match("component", r"^.*$"),
|
|
)
|
|
|
|
report = Option(
|
|
doc="""
|
|
**Syntax:** **report=****
|
|
**Description:** If register_component is set, a value for report is required.""",
|
|
require=False,
|
|
default=None,
|
|
validate=validators.Match("report", r"^.*$"),
|
|
)
|
|
|
|
tenant_id = Option(
|
|
doc="""
|
|
**Syntax:** **tenant_id=****
|
|
**Description:** If register_component is set, a value for tenant_id is required.""",
|
|
require=False,
|
|
default=None,
|
|
validate=validators.Match("tenant_id", r"^.*$"),
|
|
)
|
|
|
|
run_against_each_member = Option(
|
|
doc="""
|
|
**Syntax:** **run_against_each_member=****
|
|
**Description:** If set to true, the search will be run against each member of the account.""",
|
|
require=False,
|
|
default=False,
|
|
validate=validators.Boolean(),
|
|
)
|
|
|
|
report_runtime = Option(
|
|
doc="""
|
|
**Syntax:** **report_runtime=****
|
|
**Description:** If set to true, the runtime of the search will be reported.""",
|
|
require=False,
|
|
default=False,
|
|
validate=validators.Boolean(),
|
|
)
|
|
|
|
sample_ratio = Option(
|
|
doc="""
|
|
**Syntax:** **sample_ratio=****
|
|
**Description:** If set to a numeric value (e.g., 100), enables sampling with 1 sample for N events.""",
|
|
require=False,
|
|
default=None,
|
|
validate=validators.Match("sample_ratio", r"^\d+$"),
|
|
)
|
|
|
|
# get current user and roles membership
|
|
def get_user_roles(self):
|
|
"""
|
|
Retrieve current user and his roles.
|
|
"""
|
|
|
|
# get current user
|
|
username = self._metadata.searchinfo.username
|
|
|
|
# get user info
|
|
users = self.service.users
|
|
|
|
# Get roles for the current user
|
|
username_roles = []
|
|
for user in users:
|
|
if user.name == username:
|
|
username_roles = user.roles
|
|
logging.debug(f'username="{username}", roles="{username_roles}"')
|
|
|
|
# return current user roles as a list
|
|
return username_roles
|
|
|
|
def get_effective_roles(self, 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
|
|
|
|
# get account creds with least privilege approach
|
|
def get_account(self, session_key, splunkd_uri, account):
|
|
"""
|
|
Retrieve account creds.
|
|
"""
|
|
|
|
# Ensure splunkd_uri starts with "https://"
|
|
if not splunkd_uri.startswith("https://"):
|
|
splunkd_uri = f"https://{splunkd_uri}"
|
|
|
|
# Build header and target URL
|
|
headers = CaseInsensitiveDict()
|
|
headers["Authorization"] = f"Splunk {session_key}"
|
|
headers["Content-Type"] = "application/json"
|
|
target_url = (
|
|
f"{splunkd_uri}/services/trackme/v2/configuration/get_remote_account"
|
|
)
|
|
|
|
# Create a requests session for better performance
|
|
session = requests.Session()
|
|
session.headers.update(headers)
|
|
|
|
try:
|
|
# Use a context manager to handle the request
|
|
with session.post(
|
|
target_url, data=json.dumps({"account": account}), verify=False
|
|
) as response:
|
|
if response.ok:
|
|
response_json = response.json()
|
|
return response_json
|
|
else:
|
|
error_message = f'Failed to retrieve account, status_code={response.status_code}, response_text="{response.text}"'
|
|
logging.error(error_message)
|
|
raise Exception(error_message)
|
|
|
|
except Exception as e:
|
|
error_message = f'Failed to retrieve account, exception="{str(e)}"'
|
|
logging.error(error_message)
|
|
raise Exception(error_message)
|
|
|
|
# get the list of all accounts with least privileges approach
|
|
def list_accounts(self, session_key, splunkd_uri):
|
|
"""
|
|
List all accounts.
|
|
"""
|
|
|
|
# Ensure splunkd_uri starts with "https://"
|
|
if not splunkd_uri.startswith("https://"):
|
|
splunkd_uri = f"https://{splunkd_uri}"
|
|
|
|
# Build header and target URL
|
|
headers = CaseInsensitiveDict()
|
|
headers["Authorization"] = f"Splunk {session_key}"
|
|
target_url = f"{splunkd_uri}/services/trackme/v2/configuration/list_accounts"
|
|
|
|
# Create a requests session for better performance
|
|
session = requests.Session()
|
|
session.headers.update(headers)
|
|
|
|
try:
|
|
# Use a context manager to handle the request
|
|
with session.get(target_url, verify=False) as response:
|
|
if response.ok:
|
|
logging.debug(
|
|
f'Success retrieving list of accounts, data="{response.json()}", response_text="{response.text}"'
|
|
)
|
|
response_json = response.json()
|
|
return response_json
|
|
else:
|
|
error_message = f'Failed to retrieve accounts, status_code={response.status_code}, response_text="{response.text}"'
|
|
logging.error(error_message)
|
|
raise Exception(error_message)
|
|
|
|
except Exception as e:
|
|
error_message = f'Failed to retrieve account, exception="{str(e)}"'
|
|
logging.error(error_message)
|
|
raise Exception(error_message)
|
|
|
|
def is_reachable(self, session, url, timeout):
|
|
try:
|
|
session.get(url, timeout=timeout, verify=False)
|
|
return True, None
|
|
except Exception as e:
|
|
return False, str(e)
|
|
|
|
def select_url(self, session, splunk_url, timeout=15):
|
|
splunk_urls = splunk_url.split(",")
|
|
unreachable_errors = []
|
|
|
|
reachable_urls = []
|
|
for url in splunk_urls:
|
|
reachable, error = self.is_reachable(session, url, timeout)
|
|
if reachable:
|
|
reachable_urls.append(url)
|
|
else:
|
|
unreachable_errors.append((url, error))
|
|
|
|
selected_url = random.choice(reachable_urls) if reachable_urls else False
|
|
return selected_url, unreachable_errors
|
|
|
|
def log_and_register_failure(self, error_msg, session_key, start, earliest, latest):
|
|
logging.error(error_msg)
|
|
|
|
if (
|
|
self.register_component
|
|
and self.tenant_id
|
|
and self.component
|
|
and self.report
|
|
):
|
|
try:
|
|
trackme_register_tenant_object_summary_gen_persistent(
|
|
session_key,
|
|
self._metadata.searchinfo.splunkd_uri,
|
|
self.tenant_id,
|
|
self.component,
|
|
self.report,
|
|
"failure",
|
|
time.time(),
|
|
str(time.time() - start),
|
|
error_msg,
|
|
earliest,
|
|
latest,
|
|
)
|
|
except Exception as e:
|
|
logging.error(
|
|
f'tenant_id="{self.tenant_id}", component="{self.component}", Failed to call trackme_register_tenant_object_summary_gen_persistent with exception="{str(e)}"'
|
|
)
|
|
elif self.register_component:
|
|
logging.error(
|
|
"If register_component is set, then tenant_id, report, and component must be set too."
|
|
)
|
|
|
|
raise Exception(error_msg)
|
|
|
|
def register_success(self, session_key, start, earliest, latest):
|
|
|
|
if (
|
|
self.register_component
|
|
and self.tenant_id
|
|
and self.component
|
|
and self.report
|
|
):
|
|
try:
|
|
trackme_register_tenant_object_summary_gen_non_persistent(
|
|
session_key,
|
|
self._metadata.searchinfo.splunkd_uri,
|
|
self.tenant_id,
|
|
self.component,
|
|
self.report,
|
|
"success",
|
|
time.time(),
|
|
str(time.time() - start),
|
|
"splunkremotesearch success",
|
|
earliest,
|
|
latest,
|
|
)
|
|
except Exception as e:
|
|
logging.error(
|
|
f'tenant_id="{self.tenant_id}", component="{self.component}", Failed to call trackme_register_tenant_object_summary_gen_non_persistent with exception="{str(e)}"'
|
|
)
|
|
elif self.register_component:
|
|
logging.error(
|
|
"If register_component is set, then tenant_id, report, and component must be set too."
|
|
)
|
|
|
|
return True
|
|
|
|
def establish_remote_service(
|
|
self,
|
|
parsed_url,
|
|
bearer_token,
|
|
app_namespace,
|
|
session_key,
|
|
start,
|
|
earliest,
|
|
latest,
|
|
timeout=600,
|
|
):
|
|
try:
|
|
service = client.connect(
|
|
host=parsed_url.hostname,
|
|
splunkToken=str(bearer_token),
|
|
owner="nobody",
|
|
app=app_namespace,
|
|
port=parsed_url.port,
|
|
autologin=True,
|
|
timeout=timeout,
|
|
)
|
|
|
|
remote_apps = [app.label for app in service.apps]
|
|
if remote_apps:
|
|
logging.info(
|
|
f'Remote search connectivity check to host="{parsed_url.hostname}" on port="{parsed_url.port}" was successful'
|
|
)
|
|
return service
|
|
|
|
except Exception as e:
|
|
error_msg = f'Remote search for account="{self.account}" has failed at connectivity check, host="{parsed_url.hostname}" on port="{parsed_url.port}" with exception="{str(e)}"'
|
|
self.log_and_register_failure(
|
|
error_msg, session_key, start, earliest, latest
|
|
)
|
|
|
|
return None
|
|
|
|
def run_remote_search(
|
|
self,
|
|
service,
|
|
searchStr,
|
|
session_key,
|
|
start,
|
|
earliest,
|
|
latest,
|
|
report_runtime=False,
|
|
sample_ratio=None,
|
|
):
|
|
result_count = 0
|
|
records = []
|
|
search_start = time.time()
|
|
|
|
try:
|
|
kwargs_oneshot = {
|
|
"earliest_time": earliest,
|
|
"latest_time": latest,
|
|
"search_mode": "normal",
|
|
"preview": False,
|
|
"time_format": "%s",
|
|
"count": 0,
|
|
"output_mode": "json",
|
|
}
|
|
|
|
# Add sample_ratio to kwargs_oneshot if provided
|
|
if sample_ratio is not None:
|
|
kwargs_oneshot["sample_ratio"] = sample_ratio
|
|
|
|
# If the search is a raw search but doesn't start with the search keyword, fix this automatically
|
|
if not re.search(r"^search\s", searchStr) and not re.search(
|
|
r"^\s{0,}\|", searchStr
|
|
):
|
|
searchStr = f"search {searchStr}"
|
|
|
|
reader = run_splunk_search(service, searchStr, kwargs_oneshot, 24, 5)
|
|
|
|
# Loop through the reader results
|
|
for item in reader:
|
|
if isinstance(item, dict):
|
|
search_results = item
|
|
epochtime = str(search_results.get("_time", time.time()))
|
|
yield_record = {"_time": epochtime}
|
|
|
|
for k in search_results:
|
|
if not k.startswith("_"):
|
|
yield_record[k] = search_results[k]
|
|
|
|
yield_record["_raw"] = search_results.get("_raw", search_results)
|
|
records.append(yield_record)
|
|
result_count += 1
|
|
|
|
search_runtime = round(time.time() - search_start, 3)
|
|
|
|
if report_runtime:
|
|
return {
|
|
"status": "success",
|
|
"exception": "",
|
|
"result_count": result_count,
|
|
"runtime_seconds": search_runtime,
|
|
"records": records,
|
|
}
|
|
else:
|
|
return {
|
|
"status": "success",
|
|
"records": records,
|
|
"result_count": result_count,
|
|
"runtime_seconds": search_runtime,
|
|
}
|
|
|
|
except Exception as e:
|
|
search_runtime = round(time.time() - search_start, 3)
|
|
error_msg = f"Remote search failed with exception: {str(e)}"
|
|
|
|
if report_runtime:
|
|
return {
|
|
"status": "failed",
|
|
"exception": str(e),
|
|
"result_count": 0,
|
|
"runtime_seconds": search_runtime,
|
|
"records": [],
|
|
}
|
|
else:
|
|
self.log_and_register_failure(
|
|
error_msg, session_key, start, earliest, latest
|
|
)
|
|
raise Exception(error_msg)
|
|
|
|
def generate_fields(self, records):
|
|
# this function ensures that records have the same list of fields to allow Splunk to automatically extract these fields
|
|
# if a given result does not have a given field, it will be added to the record as an empty value
|
|
all_keys = set()
|
|
for record in records:
|
|
all_keys.update(record.keys())
|
|
|
|
for record in records:
|
|
for key in all_keys:
|
|
if key not in record:
|
|
record[key] = ""
|
|
yield record
|
|
|
|
def generate(self, **kwargs):
|
|
# start perf duration counter
|
|
start = time.time()
|
|
|
|
# Get request info and set logging level
|
|
reqinfo = trackme_reqinfo(
|
|
self._metadata.searchinfo.session_key, self._metadata.searchinfo.splunkd_uri
|
|
)
|
|
log.setLevel(reqinfo["logging_level"])
|
|
|
|
# Get the session key
|
|
session_key = self._metadata.searchinfo.session_key
|
|
|
|
# set earliest and latest
|
|
if not self.earliest:
|
|
earliest = self._metadata.searchinfo.earliest_time
|
|
else:
|
|
earliest = self.earliest
|
|
|
|
if not self.latest:
|
|
latest = self._metadata.searchinfo.latest_time
|
|
else:
|
|
latest = self.latest
|
|
|
|
# list of all accounts
|
|
accounts_list = self.list_accounts(session_key, reqinfo["server_rest_uri"])
|
|
accounts = accounts_list["accounts"]
|
|
|
|
# remove local from accounts list
|
|
accounts = [account for account in accounts if account != "local"]
|
|
|
|
# check requested account
|
|
if not self.account in accounts:
|
|
error_msg = f'The account="{self.account}" has not been configured on this instance, cannot proceed!'
|
|
self.log_and_register_failure(
|
|
error_msg, session_key, start, earliest, latest
|
|
)
|
|
|
|
# check license state
|
|
try:
|
|
check_license = trackme_check_license(
|
|
reqinfo["server_rest_uri"], session_key
|
|
)
|
|
license_is_valid = check_license.get("license_is_valid")
|
|
license_subscription_class = check_license.get("license_subscription_class")
|
|
logging.debug(
|
|
f'function check_license called, response="{json.dumps(check_license, indent=2)}"'
|
|
)
|
|
|
|
except Exception as e:
|
|
license_is_valid = 0
|
|
license_subscription_class = "free"
|
|
logging.error(f'function check_license exception="{str(e)}"')
|
|
|
|
# try and return
|
|
if (
|
|
(license_is_valid != 1 or license_subscription_class == "free_extended")
|
|
and len(accounts) >= 2
|
|
and accounts[0] != self.account
|
|
):
|
|
raise Exception(
|
|
f"This TrackMe deployment is running in Free limited edition and you have reached the maximum number of 1 remote deployment, only the first remote account ({accounts[0]}) can be used"
|
|
)
|
|
|
|
# get account
|
|
account_dict = self.get_account(
|
|
session_key, reqinfo["server_rest_uri"], self.account
|
|
)
|
|
splunk_url = account_dict["splunk_url"]
|
|
app_namespace = account_dict["app_namespace"]
|
|
bearer_token = account_dict["token"]
|
|
rbac_roles = set(account_dict["rbac_roles"])
|
|
|
|
# timeouts
|
|
timeout_connect_check = account_dict.get("timeout_connect_check", 15)
|
|
|
|
# ensures this is an integer
|
|
try:
|
|
timeout_connect_check = int(timeout_connect_check)
|
|
except Exception as e:
|
|
logging.error(
|
|
f"timeout_connect_check is not an integer, received value: {timeout_connect_check}, setting to 15"
|
|
)
|
|
|
|
timeout_search_check = account_dict.get("timeout_search_check", 300)
|
|
|
|
# ensures this is an integer
|
|
try:
|
|
timeout_search_check = int(timeout_search_check)
|
|
except Exception as e:
|
|
logging.error(
|
|
f"timeout_search_check is not an integer, received value: {timeout_search_check}, setting to 300"
|
|
)
|
|
|
|
# Get user's direct roles
|
|
user_roles = self.get_user_roles()
|
|
|
|
# Get roles dictionary
|
|
roles = self.service.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 (direct roles + inherited roles)
|
|
effective_roles = self.get_effective_roles(user_roles, roles_dict)
|
|
|
|
# Check RBAC using effective roles
|
|
rbac_granted = bool(effective_roles & rbac_roles)
|
|
|
|
# Grant the system user
|
|
if self._metadata.searchinfo.username in ("splunk-system-user", "admin"):
|
|
rbac_granted = True
|
|
|
|
if not rbac_granted:
|
|
logging.debug(
|
|
f'RBAC access not granted to this account, user_roles="{user_roles}", effective_roles="{effective_roles}", account_roles="{rbac_roles}", username="{self._metadata.searchinfo.username}"'
|
|
)
|
|
raise Exception(
|
|
"Access to this Remote account has been refused, please contact your TrackMe administrator to grant access to this Remote account"
|
|
)
|
|
else:
|
|
logging.debug(
|
|
f'RBAC access granted to this account, user_roles="{user_roles}", effective_roles="{effective_roles}", account_roles="{rbac_roles}"'
|
|
)
|
|
|
|
# Create a session within the generate function
|
|
session = requests.Session()
|
|
|
|
# Get the search string
|
|
searchStr = self.search
|
|
|
|
# Process
|
|
if self.run_against_each_member:
|
|
splunk_urls = splunk_url.split(",")
|
|
for member_url in splunk_urls:
|
|
member_url = f"https://{member_url.replace('https://', '').rstrip('/')}"
|
|
parsed_url = urllib.parse.urlparse(member_url)
|
|
|
|
logging.info(f"Processing member URL: {member_url}")
|
|
|
|
try:
|
|
remoteservice = self.establish_remote_service(
|
|
parsed_url,
|
|
bearer_token,
|
|
app_namespace,
|
|
session_key,
|
|
start,
|
|
earliest,
|
|
latest,
|
|
timeout=timeout_search_check,
|
|
)
|
|
|
|
if not remoteservice:
|
|
raise Exception("Could not establish remote service")
|
|
|
|
result = self.run_remote_search(
|
|
remoteservice,
|
|
searchStr,
|
|
session_key,
|
|
start,
|
|
earliest,
|
|
latest,
|
|
report_runtime=self.report_runtime,
|
|
sample_ratio=self.sample_ratio,
|
|
)
|
|
|
|
result["member"] = member_url
|
|
|
|
if self.report_runtime:
|
|
result["_raw"] = json.dumps(result)
|
|
result["_time"] = time.time()
|
|
yield result
|
|
else:
|
|
for yield_record in self.generate_fields(result["records"]):
|
|
yield yield_record
|
|
|
|
except Exception as e:
|
|
logging.error(
|
|
f"Search failed for member={member_url}, exception={str(e)}"
|
|
)
|
|
|
|
if self.report_runtime:
|
|
result = {
|
|
"member": member_url,
|
|
"status": "failed",
|
|
"exception": str(e),
|
|
"result_count": 0,
|
|
"runtime_seconds": 0,
|
|
}
|
|
result["_raw"] = json.dumps(result)
|
|
result["_time"] = time.time()
|
|
yield result
|
|
|
|
else:
|
|
# original behavior: select one working URL
|
|
selected_url, errors = self.select_url(
|
|
session, splunk_url, timeout_connect_check
|
|
)
|
|
|
|
if not selected_url:
|
|
error_msg = f"None of the endpoints provided in the account URLs could be reached successfully, verify your network connectivity! (timeout_connect_check={timeout_connect_check})"
|
|
error_msg += (
|
|
f"Errors: {' '.join([f'{url}: {error}' for url, error in errors])}"
|
|
)
|
|
logging.error(error_msg)
|
|
self.log_and_register_failure(
|
|
error_msg, session_key, start, earliest, latest
|
|
)
|
|
|
|
else:
|
|
selected_url = (
|
|
f"https://{selected_url.replace('https://', '').rstrip('/')}"
|
|
)
|
|
parsed_url = urllib.parse.urlparse(selected_url)
|
|
|
|
remoteservice = self.establish_remote_service(
|
|
parsed_url,
|
|
bearer_token,
|
|
app_namespace,
|
|
session_key,
|
|
start,
|
|
earliest,
|
|
latest,
|
|
timeout=timeout_search_check,
|
|
)
|
|
|
|
if not remoteservice:
|
|
raise Exception("Failed to establish remote service connection")
|
|
|
|
result = self.run_remote_search(
|
|
remoteservice,
|
|
searchStr,
|
|
session_key,
|
|
start,
|
|
earliest,
|
|
latest,
|
|
report_runtime=self.report_runtime,
|
|
sample_ratio=self.sample_ratio,
|
|
)
|
|
|
|
if self.report_runtime:
|
|
result["member"] = selected_url
|
|
result["_raw"] = json.dumps(result)
|
|
result["_time"] = time.time()
|
|
yield result
|
|
else:
|
|
for yield_record in self.generate_fields(result["records"]):
|
|
yield yield_record
|
|
|
|
|
|
dispatch(SplunkRemoteSearch, sys.argv, sys.stdin, sys.stdout, __name__)
|