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.

331 lines
15 KiB

# Copyright (C) 2005-2024 Splunk Inc. All Rights Reserved.
import json
import sys
import uuid
from splunk.clilib.bundle_paths import make_splunkhome_path
sys.path.append(make_splunkhome_path(['etc', 'apps', 'SA-ITOA', 'lib']))
import itsi_path
from urllib.parse import quote_plus
from SA_ITOA_app_common.splunklib.searchcommands import Configuration, Option, GeneratingCommand, dispatch, validators
from ITOA.setup_logging import InstrumentCall, logger
from splunk import rest, RESTException, ResourceNotFound
from itsi.itsi_utils import ItsiMacroReader
@Configuration()
class ItsiInstallCommand(GeneratingCommand):
is_noah_enabled = Option(
doc='''
**Syntax:** **is_noah_enabled=***<Boolean>*
**Description:** Is the deployment on Noah?''',
require=False,
default=False,
validate=validators.Boolean(),
)
def make_request(self, session_key, path, method='GET', params=None):
"""
make GET/POST to a path
:param path: the path of an endpoint
:type path: str
:param method: one of ['GET', 'POST']
:type method: str
:param params: params that send out with the request
:type params: dict
:return: content from the returned request
:rtype: json
"""
args = {'output_mode': 'json'}
args.update(params if params else {})
response = None
content = None
try:
if method == 'GET':
response, content = rest.simpleRequest(
path,
method=method,
getargs=args,
sessionKey=session_key,
raiseAllErrors=False
)
elif method == 'POST':
response, content = rest.simpleRequest(
path,
method=method,
postargs=args,
sessionKey=session_key,
raiseAllErrors=False
)
except Exception as e:
raise RESTException(500, '%s' % e)
if response.status != 200:
raise RESTException(response.status, 'Received unexpected results from dispatcher: {}'.format(content))
return json.loads(content)
def get_conf_stanza(self, session_key, conf_name, stanza_name, app='SA-ITOA'):
"""
Create conf stanza by calling splunk conf endpoints
@type session_key: string
@param session_key: session_key
@type conf_name: string
@param conf_name: conf file name
@type stanza_name: dict
@param stanza_name: dict of data to post
@type app: string
@param app: itsi_module name
@rtype: dict
@return: response dictionary
"""
getargs = {'output_mode': 'json'}
rest_path = self.get_conf_rest_path(conf_name, app) + '/' + quote_plus(stanza_name)
response, content = rest.simpleRequest(
rest_path,
method='GET',
getargs=getargs,
sessionKey=session_key,
raiseAllErrors=True
)
return {'response': response, 'content': json.loads(content)['entry'][0]['content']}
def get_conf_rest_path(self, conf_name, app='SA-ITOA', host_base_uri=''):
"""
builds the configuration restpath
@type conf_name: String
@param: conf_name: configuration file name
@type app: string
@param app: itsi_module name
@type host_base_uri: string
@param app: base uri
@rtype: String
@return: return response
"""
if host_base_uri:
return host_base_uri + '/servicesNS/nobody/' + app + '/configs/conf-' + conf_name
else:
return rest.makeSplunkdUri() + 'servicesNS/nobody/' + app + '/configs/conf-' + conf_name
def create_conf_property(self, session_key, conf_name, conf_stanza, data_to_post, app='SA-ITOA'):
"""
Create conf stanza by calling splunk conf endpoints
@type session_key: string
@param session_key: session_key
@type conf_name: string
@param conf_name: conf file name
@type conf_stanza: dict
@param conf_stanza: dict of data to post
@type data_to_post: string
@param data_to_post: {stanza_name: {param_name: param_value}}
@type app: string
@param app: itsi_module name
@rtype: tuple
@return: response and content or raise an exception
"""
postargs = data_to_post[conf_stanza]
postargs['output_mode'] = 'json'
try:
self.get_conf_stanza(session_key, conf_name, conf_stanza, app)['content'] # detect if stanza already exists
rest_path = self.get_conf_rest_path(conf_name, app) + '/' + quote_plus(conf_stanza)
logger.info('Making API call to add conf flag with %s, postargs: %s, method: POST' % (
rest_path, postargs))
response, content = rest.simpleRequest(
rest_path,
method='POST',
postargs=postargs,
sessionKey=session_key,
raiseAllErrors=True
)
except ResourceNotFound:
rest_path = self.get_conf_rest_path(conf_name, app)
postargs['name'] = conf_stanza
logger.info('Making API call to add conf flag with %s, postargs: %s, method: POST' % (
rest_path, postargs))
response, content = rest.simpleRequest(
rest_path,
method='POST',
postargs=postargs,
sessionKey=session_key,
raiseAllErrors=True
)
return {'response': response, 'content': content}
def execute_add_action_config(self, conf_name, stanza_name, app_name, param_name, param_value, session_key):
"""
executes add action of the configuration
:param conf_name: configuration file name
:param stanza_name: stanza name to be updated
:param app_name: app name
:param param_name: new parameter name
:param param_value: new parameter value
:param session_key: the splunkd session key for the request
:type session_key: string
:return: None
"""
logger.info('Starting component config add, configuration:%s.%s.%s.%s' % (app_name,
conf_name,
stanza_name,
param_name))
try:
# call the create property api with new parameter value
logger.info(
'Executing component config add, adding property:%s.%s.%s.%s' % (app_name,
conf_name,
stanza_name,
param_name))
data_to_post = {stanza_name: {param_name: param_value}}
logger.info(
"Executing component config add, the data_to_post:%s" % (data_to_post))
self.create_conf_property(session_key, conf_name, stanza_name, data_to_post, app_name)
logger.info(
'Finished component config add, configuration:%s.%s.%s.%s' % (app_name,
conf_name,
stanza_name,
param_name))
except RESTException as e:
if e.statusCode == 409:
logger.info(
'Finished component config add, config stanza %s.%s.%s already exists' % (app_name,
conf_name,
stanza_name))
else:
logger.error(
"Failed component config add, failed to add stanza configuration:%s" % (e))
raise e
def reload_inputs(self, app, session_key):
"""
Reload inputs for an app
:param app: the app for which to refresh inputs
:type app: string
:param session_key: the splunkd session key for the request
:type session_key: string
:return: None
"""
path = "%sservicesNS/nobody/%s/data/inputs" % (rest.makeSplunkdUri(), app)
logger.info('Reloading inputs, retrieving inputs for app %s by calling %s' % (app, path))
content = self.make_request(session_key, path)
for entry in content['entry']:
input_name = entry['name']
if "_reload" not in entry['links'] or \
input_name == 'tcp' or input_name.startswith('tcp://'): # Reloading tcp inputs will return 404.
logger.info("Reloading inputs, input name %s for app %s does not support reload, skipped." % (
input_name, app))
continue
logger.info('Reloading inputs, reloading input %s in app %s' % (input_name, app))
self.make_request(session_key,
"%s%s" % (rest.makeSplunkdUri(), entry['links']['_reload']),
method='POST')
def generate(self):
logger.info("Running search command 'itsiinstall'")
try:
itsi_notable_archive_macro = ItsiMacroReader(self.service.token, 'itsi_notable_archive_index')
itsi_notable_audit_macro = ItsiMacroReader(self.service.token, 'itsi_notable_audit_index')
itsi_grouped_alerts_macro = ItsiMacroReader(self.service.token, 'itsi_grouped_alerts_index')
itsi_tracked_alerts_macro = ItsiMacroReader(self.service.token, 'itsi_tracked_alerts_index')
itsi_import_objects_macro = ItsiMacroReader(self.service.token, 'get_itsi_import_objects_index')
MISCELLANEOUS = {
'splunk_httpinput': {
'disabled': {
'http': '0'
}
},
'SA-ITOA': {
'disabled': {
'itsi_notable_event_hec_init://default_hec_initializer': '1',
'itsi_hec_init://bulk_import_hec_initializer': '1',
'http://Auto Generated ITSI Event Management Token': '0',
'http://itsi_group_comments_token': '0',
'http://itsi_bulk_import_token': '0'
},
'index': {
'http://Auto Generated ITSI Notable Event Retention Policy Token': itsi_notable_archive_macro.index,
'http://Auto Generated ITSI Notable Index Audit Token': itsi_notable_audit_macro.index,
'http://itsi_group_alerts_token': itsi_grouped_alerts_macro.index,
'http://itsi_group_alerts_sync_token': itsi_grouped_alerts_macro.index,
'http://Auto Generated ITSI Event Management Token': itsi_tracked_alerts_macro.index,
'http://itsi_group_comments_token': itsi_grouped_alerts_macro.index,
'http://itsi_bulk_import_token': itsi_import_objects_macro.index
},
'indexes': {
'http://Auto Generated ITSI Notable Event Retention Policy Token': itsi_notable_archive_macro.index,
'http://Auto Generated ITSI Notable Index Audit Token': itsi_notable_audit_macro.index,
'http://itsi_group_alerts_token': itsi_grouped_alerts_macro.index,
'http://itsi_group_alerts_sync_token': itsi_grouped_alerts_macro.index,
'http://Auto Generated ITSI Event Management Token': itsi_tracked_alerts_macro.index,
'http://itsi_group_comments_token': itsi_grouped_alerts_macro.index,
'http://itsi_bulk_import_token': itsi_import_objects_macro.index
},
'source': {
'http://Auto Generated ITSI Notable Index Audit Token': 'Notable Event Audit',
'http://itsi_group_alerts_token': 'itsi_group_alerts',
'http://itsi_group_alerts_sync_token': 'itsi_group_alerts',
'http://itsi_group_comments_token': 'Notable Event Comment',
'http://itsi_bulk_import_token': 'itsi bulk import'
},
'sourcetype': {
'http://Auto Generated ITSI Notable Event Retention Policy Token': 'itsi_notable:archive',
'http://Auto Generated ITSI Notable Index Audit Token': 'itsi_notable:audit',
'http://itsi_group_alerts_token': 'itsi_notable:group',
'http://itsi_group_alerts_sync_token': 'itsi_notable:group',
'http://Auto Generated ITSI Event Management Token': 'itsi_notable:event',
'http://itsi_group_comments_token': 'itsi_notable:comment',
'http://itsi_bulk_import_token': 'itsi_import_objects:csv'
},
'token': {
'http://Auto Generated ITSI Notable Event Retention Policy Token': str(uuid.uuid4()),
'http://Auto Generated ITSI Notable Index Audit Token': str(uuid.uuid4()),
'http://itsi_group_alerts_token': str(uuid.uuid4()),
'http://itsi_group_alerts_sync_token': str(uuid.uuid4()),
'http://Auto Generated ITSI Event Management Token': str(uuid.uuid4()),
'http://itsi_group_comments_token': str(uuid.uuid4()),
'http://itsi_bulk_import_token': str(uuid.uuid4())
}
}
}
if self.is_noah_enabled:
logger.info("Noah is enabled, proceeding with 'itsiinstall'")
for app, modinputs in MISCELLANEOUS.items(): # set miscellaneous configs in local inputs.conf for mod inputs
for key, stanzas in modinputs.items():
for stanza_name, value in stanzas.items():
self.execute_add_action_config('inputs', stanza_name, app, key, value, self.service.token)
self.reload_inputs('SA-ITOA', self.service.token)
else:
logger.info("Noah is not enabled, exiting 'itsiinstall'")
logger.info("Completed running 'itsiinstall'")
yield {}
except Exception as e:
# Explicitly specify Exception message due to missing Python3 support in error_exit()
self.error_exit(e, message=str(e))
dispatch(ItsiInstallCommand, sys.argv, sys.stdin, sys.stdout, __name__)