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.
316 lines
11 KiB
316 lines
11 KiB
# Copyright (C) 2005-2024 Splunk Inc. All Rights Reserved.
|
|
|
|
"""
|
|
Ping is an example of an action that can be taken programmatically on one or
|
|
more Notable Events in ITSI.
|
|
|
|
It is implemented as a Splunk Modular Alert Action.
|
|
|
|
Chunk of the logic lies in the method `execute()` where we work on one event
|
|
at a time.
|
|
|
|
Using this as an example, you could implement other actions like telnet,
|
|
work on external ticket and then update your ITSI Event worklog, update status,
|
|
severity, owner etc...
|
|
"""
|
|
|
|
import sys
|
|
import json
|
|
import platform
|
|
import subprocess
|
|
|
|
from splunk.clilib.bundle_paths import make_splunkhome_path
|
|
|
|
sys.path.append(make_splunkhome_path(['etc', 'apps', 'SA-ITOA', 'lib']))
|
|
sys.path.append(make_splunkhome_path(['etc', 'apps', 'SA-ITOA', 'lib', 'SA_ITOA_app_common']))
|
|
import itsi_path
|
|
import itsi_py3
|
|
|
|
from ITOA.setup_logging import getLogger
|
|
from ITOA.event_management.notable_event_utils import Audit
|
|
|
|
from itsi.event_management.sdk.grouping import EventGroup
|
|
from itsi.event_management.sdk.custom_group_action_base import CustomGroupActionBase
|
|
|
|
|
|
class Ping(CustomGroupActionBase):
|
|
"""
|
|
Ping is an example of an action that can be taken, programmatically on an ITSI
|
|
group. It is implemented as a Splunk Modular Alert Action.
|
|
|
|
Usage::
|
|
>>> if __name__ == "__main__":
|
|
>>> if len(sys.argv) > 1 and sys.argv[1] == '--execute':
|
|
>>> input_params = sys.stdin.read()
|
|
>>> ping = Ping(input_params)
|
|
>>> ping.execute()
|
|
"""
|
|
|
|
DEFAULT_HOST_KEY_IN_CONFIG = 'host_to_ping'
|
|
DEFAULT_COUNT_VALUE = '10' # packets
|
|
DEFAULT_TIMEOUT_VALUE = '11' # seconds
|
|
|
|
def __init__(self, settings, count_value=None, timeout_value=None, audit_token_name='Auto Generated ITSI Notable Index Audit Token'):
|
|
"""
|
|
Initialize the object
|
|
@type settings: dict/basestring
|
|
@param settings: incoming settings for this alert action that splunkd
|
|
passes via stdin.
|
|
|
|
@type count_value: basestring
|
|
@param count_value: a string indicating the number of ICMP packets to
|
|
send to destination.
|
|
|
|
@type timeout_value: basestring
|
|
@param timeout_value: (seconds) a string indicating the number of
|
|
seconds to wait, prior to giving up in case of an ICMP timeout.
|
|
|
|
@returns Nothing
|
|
"""
|
|
self.logger = getLogger(logger_name="itsi.event_action.ping")
|
|
|
|
super(Ping, self).__init__(settings, self.logger)
|
|
|
|
self.executable = 'ping'
|
|
self.count_flag = None
|
|
self.count_value = None
|
|
self.timeout_flag = None
|
|
self.timeout_value = None
|
|
self.platform_type = None
|
|
self.audit = Audit(self.get_session_key(), audit_token_name)
|
|
|
|
self._set_flags(count_value, timeout_value)
|
|
|
|
def _set_flags(self, count_value, timeout_value):
|
|
"""
|
|
Set some flags for count, timeout etc...
|
|
We will consider the platform type for some of them.
|
|
|
|
@type count_value: basestring
|
|
@param count_value: a string indicating the number of ICMP packets to
|
|
send to destination.
|
|
|
|
@type timeout_value: basestring
|
|
@param timeout_value: (seconds) a string indicating the number of
|
|
seconds to wait, prior to giving up in case of an ICMP timeout.
|
|
|
|
@returns Nothing
|
|
"""
|
|
try:
|
|
self.count_value = int(count_value)
|
|
except (ValueError, TypeError):
|
|
self.count_value = self.DEFAULT_COUNT_VALUE
|
|
|
|
try:
|
|
self.timeout_value = int(timeout_value)
|
|
except (ValueError, TypeError):
|
|
self.timeout_value = self.DEFAULT_TIMEOUT_VALUE
|
|
|
|
# get platform specific flags
|
|
if platform.system() == 'Windows':
|
|
self.platform_type = 'windows'
|
|
self.count_flag = '-n'
|
|
self.timeout_flag = '-w'
|
|
else:
|
|
self.platform_type = '*nix'
|
|
self.count_flag = '-c'
|
|
self.timeout_flag = '-W'
|
|
|
|
self.logger.debug('Environment/Platform=`%s`. Flags=`%s %s %s %s`',
|
|
self.platform_type, self.count_flag, self.count_value,
|
|
self.timeout_flag, self.timeout_value)
|
|
|
|
def _get_exec_arg(self, host):
|
|
"""
|
|
Return a list compatible with Popen which can be exec'ed
|
|
|
|
@type host: basestring
|
|
@param host: target host.
|
|
|
|
@rtype: list of str
|
|
@returns: a param consumed by Popen
|
|
"""
|
|
|
|
self.logger.debug('Exec string=`%s %s %s %s %s %s`', self.executable,
|
|
self.count_flag, self.count_value, self.timeout_flag,
|
|
self.timeout_value, host)
|
|
|
|
return [self.executable, self.count_flag, str(self.count_value), self.timeout_flag,
|
|
str(self.timeout_value), host]
|
|
|
|
def ping(self, host, group_id, policy_id):
|
|
"""
|
|
given a host, ping it.
|
|
|
|
@type host: basestring
|
|
@param host: host to ping.
|
|
|
|
@type group_id: basestring
|
|
@param group_id: group id to ping
|
|
|
|
@type policy_id: basestring
|
|
@param policy_id: policy id of the group
|
|
|
|
@rtype: tuple (basestring, basestring)
|
|
@return: stdout, stderr
|
|
"""
|
|
if any([not host, not isinstance(host, itsi_py3.string_type),
|
|
isinstance(host, itsi_py3.string_type) and not host.strip()
|
|
]):
|
|
message = 'Invalid host to ping. Received="%s". Type="%s"' % (host, type(host).__name__)
|
|
self.audit.send_activity_to_audit({
|
|
'event_id': group_id,
|
|
'itsi_policy_id': policy_id
|
|
}, message, 'Host Ping Failed')
|
|
raise Exception(message)
|
|
|
|
p = subprocess.Popen(
|
|
self._get_exec_arg(host),
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
universal_newlines=True
|
|
)
|
|
|
|
out, err = p.communicate()
|
|
|
|
self.logger.debug('Ping complete. host=`%s` stdout=`%s` stderr=`%s`', host, out, err)
|
|
return itsi_py3.decode(out), itsi_py3.decode(err)
|
|
|
|
def get_host_to_ping(self, data, host_key=None):
|
|
"""
|
|
Return the string indicating the host to ping.
|
|
Work on the config that came with the alert action execution
|
|
if group specific config is provided.
|
|
|
|
@rtype: basestring
|
|
@return: the host to ping
|
|
"""
|
|
host_key = self.DEFAULT_HOST_KEY_IN_CONFIG if not host_key else host_key
|
|
config = self.get_config()
|
|
host_info = config.get(host_key, '')
|
|
|
|
host = None
|
|
|
|
if host_info.startswith('%') and host_info.endswith('%'):
|
|
# if host_info starts and ends with `%` it refers to
|
|
# another key in the group data, whose value we care about.
|
|
# We will retrieve its value and set it as the host.
|
|
# {
|
|
# 'host_to_ping': '%orig_host%', # references another field below
|
|
# ...
|
|
# 'orig_host': 'foobar.orig', # we care about this field
|
|
# ...
|
|
# }
|
|
host_info = host_info.strip('%')
|
|
host = data.get(host_info)
|
|
else:
|
|
# if host_info does not start/end with `%s`, its value can be taken as
|
|
# the value of the host to ping.
|
|
host = host_info
|
|
|
|
if host is None or (isinstance(host, itsi_py3.string_type) and not host.strip()):
|
|
message = 'No host to ping.'
|
|
self.logger.error(message)
|
|
self.audit.send_activity_to_audit({'event_id': data.get('itsi_group_id')}, 'Failed to ping null host.', 'Host ping failed')
|
|
raise Exception(message)
|
|
|
|
# if host contains any port #s, strip it away
|
|
# localhost:8000 or git.splunk.com:80
|
|
host = host.split(':')[0]
|
|
self.logger.debug('Host to ping=`%s`', host)
|
|
return host
|
|
|
|
def ping_and_update_notable_group(self, host, group_id, policy_id):
|
|
"""
|
|
Do the act of pinging the host and then go on to update the comment for
|
|
the notable event group.
|
|
@type host: basestring
|
|
@param host: host to ping
|
|
|
|
@type group_id: basestring
|
|
@param group_id: group id to ping
|
|
|
|
@type policy_id: basestring
|
|
@param policy_id: policy id of the group
|
|
|
|
@rtype: basestring | None
|
|
@return: error, if error occurred
|
|
"""
|
|
out, err = self.ping(host, group_id, policy_id)
|
|
error_to_return = None
|
|
if err.strip():
|
|
self.logger.error('Errors while running ping=`%s`', err)
|
|
comment = 'Errors while running ping: {}'.format(err)
|
|
error_to_return = comment
|
|
else:
|
|
self.logger.debug('Ping output=`%s`', out)
|
|
comment = 'No errors while running ping.'
|
|
|
|
self.logger.info('Updating tags/comments for group_id: %s', group_id)
|
|
event = EventGroup(self.get_session_key(), self.logger)
|
|
event.create_comment(group_id, comment, policy_id)
|
|
if error_to_return is None:
|
|
event.create_comment(group_id, out, policy_id)
|
|
event.create_tag(group_id, 'ping', policy_id)
|
|
self.audit.send_activity_to_audit({
|
|
'event_id': group_id,
|
|
'itsi_policy_id': policy_id,
|
|
}, 'The host="%s" was pinged successfully.' % (host), 'Host pinged')
|
|
else:
|
|
self.audit.send_activity_to_audit({
|
|
'event_id': group_id,
|
|
'itsi_policy_id': policy_id
|
|
}, 'Failed to ping host="%s".' % (host), 'Host ping failed')
|
|
return error_to_return
|
|
|
|
def execute(self):
|
|
"""
|
|
Execute en bulk. For each event in the results file:
|
|
1. extract the host to ping
|
|
2. ping host
|
|
3. add comment
|
|
|
|
Apart from the above, this method does nothing else.
|
|
The rest is left to your implementation and imagination.
|
|
"""
|
|
self.logger.debug('Received settings from splunkd=`%s`', json.dumps(self.settings))
|
|
|
|
count = 0
|
|
try:
|
|
ping_failed = False
|
|
for data in self.get_group():
|
|
if isinstance(data, Exception):
|
|
# Generator can yield an Exception
|
|
# We cannot print the call stack here reliably, because
|
|
# of how this code handles it, we may have generated an exception elsewhere
|
|
# Better to present this as an error
|
|
self.logger.error(data)
|
|
raise data
|
|
|
|
if not data.get('itsi_group_id'):
|
|
self.logger.warning('Event does not have an `itsi_group_id`. No-op.')
|
|
continue
|
|
|
|
group_id = data.get('itsi_group_id')
|
|
policy_id = data.get('itsi_policy_id')
|
|
host = self.get_host_to_ping(data)
|
|
ping_error = self.ping_and_update_notable_group(host, group_id, policy_id)
|
|
if ping_error is not None:
|
|
ping_failed = True
|
|
count += 1
|
|
if ping_failed is True:
|
|
raise Exception('Failed to execute one or more ping actions.')
|
|
except Exception as e:
|
|
self.logger.error('Failed to execute ping.')
|
|
self.logger.exception(e)
|
|
sys.exit(1)
|
|
|
|
self.logger.info('Executed action. Processed events count=`%s`.', count)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) > 1 and sys.argv[1] == '--execute':
|
|
input_params = sys.stdin.read()
|
|
ping = Ping(input_params)
|
|
ping.execute()
|