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.
SH-Deployer/apps/SA-ITOA/bin/itsi_event_action_webhook.py

359 lines
13 KiB

# Copyright (C) 2005-2024 Splunk Inc. All Rights Reserved.
import sys
import json
import requests
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']))
from ITOA.itoa_common import get_conf
from ITOA.setup_logging import getLogger
from ITOA.event_management.notable_event_utils import Audit
from itsi.event_management.sdk.custom_group_action_base import CustomGroupActionBase
import splunk.rest as splunk_rest
from splunk.util import safeURLQuote
WEBHOOK_AUTH_TYPES = {
'BASIC_AUTH': 'Basic authentication',
'BEARER_TOKEN': 'Bearer token',
'NO_AUTH': 'No authentication'
}
class Webhook(CustomGroupActionBase):
"""
Class that performs Webhook action on notable events group.
"""
def __init__(self, settings, app='SA-ITOA'):
"""
Initialize the object
@type settings: dict/basestring
@param settings: incoming settings for this alert action that splunkd
passes via stdin.
@returns Nothing
"""
self.logger = getLogger(logger_name="itsi.event_action.webhook")
super(Webhook, self).__init__(settings, self.logger)
self.app = app
self.owner = 'nobody'
self.session_key = self.get_session_key()
self.webhook_name = None
self.webhook_url = None
self.conf_file_name = 'webhooks'
self.webhook_header = ''
def get_clear_password(self, username):
"""
Get the actual password and token
@type: str
@param username: username/webhook name
@rtype: str
@return: decoded password/token
"""
try:
realm_user = self.webhook_name + ':' + username
uri_string = ('/servicesNS/{0}/{1}/storage/passwords/{2}').format(self.owner, self.app, realm_user)
uri = safeURLQuote(uri_string)
res, content = splunk_rest.simpleRequest(uri, getargs={'output_mode': 'json'},
sessionKey=self.session_key)
if res.status == 200:
self.logger.info(
'Password is fetched successfully for webhook=%s', self.webhook_name)
else:
self.logger.error(
'Error in getting password from passwords.conf. response=%s content=%s', res, content)
return None
if not content:
self.logger.error('content was not returned.')
return None
parsed_content = json.loads(content)
password = parsed_content.get('entry', [])[0].get('content', {}).get('clear_password', {})
return password
except Exception as e:
self.logger.exception('An error occurred while fetching the password. Exception: %s', e)
return None
def send_post_request(self, data):
"""
Send POST request with data to url
@type: dict
@param data: payload to send via url
@return: Nothing
"""
try:
headers = {"Content-Type": "application/json"}
if self.webhook_header is not None:
try:
headers = json.loads(self.webhook_header)
except Exception as e:
self.logger.error(
'Unable to complete webhook action because the header is invalid. Update the header and try again. Webhook={0} header={1} Exception: {2}.'.format(
self.webhook_name, self.webhook_header, e
)
)
sys.exit(1)
response = requests.post(self.webhook_url, data=data, headers=headers)
# Check if the request was successful (status code 2xx)
if response.status_code >= 200 and response.status_code < 300:
self.logger.info(
'Webhook action for webhook {0} executed successfully. Response: {1}'.format(
self.webhook_name, response
)
)
else:
self.logger.error(
'Failed to execute Webhook action for webhook={0}. status={1}.'.format(
self.webhook_name, response.status_code
)
)
sys.exit(1)
except Exception as e:
self.logger.exception(
'An error occurred while executing the webhook action. Webhook={0} Exception: {1}'.format(
self.webhook_name, e
)
)
sys.exit(1)
def send_post_request_with_auth(self, data, username):
"""
Send POST request with data to url using username and password
@type: dict
@param data: payload to send via url
@type: str
@param username: username for authentication
@return: Nothing
"""
try:
decrypted_password = self.get_clear_password(username)
headers = {'Content-Type': 'application/json'}
if self.webhook_header is not None:
try:
headers = json.loads(self.webhook_header)
except Exception as e:
self.logger.error(
'Unable to complete webhook action because the header is invalid. Update the header and try again. Webhook={0} header={1} Exception: {2}.'.format(
self.webhook_name, self.webhook_header, e
)
)
sys.exit(1)
if decrypted_password is None:
self.logger.error(
'Password not found for provided Webhook=%s', self.webhook_name
)
sys.exit(1)
response = requests.post(
self.webhook_url,
headers=headers,
data=data,
auth=(username, decrypted_password),
verify=self.is_ssl_certificate_validation_disabled,
)
# Check if the request was successful (status code 2xx)
if response.status_code >= 200 and response.status_code < 300:
self.logger.info(
'Webhook action for webhook {0} executed successfully. Response: {1}'.format(
self.webhook_name, response
)
)
else:
self.logger.error(
'Failed to execute Webhook action for webhook={0}. status={1}.'.format(
self.webhook_name, response.status_code
)
)
sys.exit(1)
except Exception as e:
self.logger.exception(
'An error occurred while executing the webhook action. Webhook={0} Exception: {1}'.format(
self.webhook_name, e
)
)
sys.exit(1)
def send_post_request_with_token(self, data):
"""
Send POST request with data to url using Token
@type: dict
@param data: payload to send via url
@return: Nothing
"""
try:
decrypted_token = self.get_clear_password(self.webhook_name)
if decrypted_token is None:
self.logger.error(
'Token not found for provied Webhook=%s', self.webhook_name
)
sys.exit(1)
headers = {
"Authorization": f"Bearer {decrypted_token}",
"Content-Type": "application/json",
}
if self.webhook_header is not None:
try:
self.webhook_header = json.loads(self.webhook_header)
if "Authorization" not in self.webhook_header:
self.webhook_header.update(
{"Authorization": f"Bearer {decrypted_token}"}
)
headers = self.webhook_header
except Exception as e:
self.logger.error(
'Unable to complete webhook action because the header is invalid. Update the header and try again. Webhook={0} header={1} Exception: {2}.'.format(
self.webhook_name, self.webhook_header, e
)
)
sys.exit(1)
response = requests.post(
self.webhook_url,
data=data,
headers=headers,
verify=self.is_ssl_certificate_validation_disabled,
)
# Check if the request was successful (status code 2xx)
if response.status_code >= 200 and response.status_code < 300:
self.logger.info(
'Webhook action for webhook {0} executed successfully. Response: {1}'.format(
self.webhook_name, response
)
)
else:
self.logger.error(
'Failed to execute Webhook action for webhook={0}. status={1}.'.format(
self.webhook_name, response.status_code
)
)
sys.exit(1)
except Exception as e:
self.logger.exception(
'An error occurred while executing the webhook action. Webhook={0} Exception: {1}'.format(
self.webhook_name, e
)
)
sys.exit(1)
def get_configuration(self, conf_file_name=None, app=None):
"""
Get configurations for webhooks
@type: str
@param conf_file_name: configuration file name
@type: str
@param app: app name
@rtype: dict
@return: config fields with value for provided configuration file
"""
conf_file_name = conf_file_name if conf_file_name else self.conf_file_name
app = app if app else self.app
rval = get_conf(self.session_key, conf_file_name, search='disabled=0', count=-1, app=app)
response = rval.get('response')
if response.status != 200:
self.logger.error(
'Failed to fetch configuration file=`%s`, rval=`%s`', self.conf_file_name, rval
)
raise Exception('Failed to fetch data for config="%s".', self.conf_file_name)
content = rval.get('content')
content = json.loads(content)
configuration = {}
for entry in content.get('entry', []):
configuration[entry.get('name')] = entry.get('content', {})
return configuration
def execute_action(self, payload):
"""
Performs the POST request for the provided webhook
@type: dict
@param payload: payload of event
"""
# Reading configuration file
configuration = self.get_configuration(self.conf_file_name)
# Getting required fields from conf file
webhook_obj = configuration.get(self.webhook_name, None)
webhook_auth_type = webhook_obj.get('auth_type', None)
self.webhook_header = webhook_obj.get('header', None)
self.is_ssl_certificate_validation_disabled = webhook_obj.get('should_ssl_verified', False)
if self.is_ssl_certificate_validation_disabled:
self.is_ssl_certificate_validation_disabled = bool(int(self.is_ssl_certificate_validation_disabled))
# Execute POST call based on the Auth Type
if webhook_auth_type == WEBHOOK_AUTH_TYPES['BASIC_AUTH']: # using username password
webhook_username = webhook_obj.get('username', None)
if webhook_username is None:
self.logger.error('Username must be defined for Webhook with Auth Type Basic')
sys.exit(1)
self.send_post_request_with_auth(payload, webhook_username)
elif webhook_auth_type == WEBHOOK_AUTH_TYPES['BEARER_TOKEN']: # using token
self.send_post_request_with_token(payload)
elif webhook_auth_type == WEBHOOK_AUTH_TYPES['NO_AUTH']: # direct POST call
self.send_post_request(payload)
else:
raise Exception('Correct Auth Type is not provided for webhook %s', self.webhook_name)
def execute(self):
"""
Performs webhook action.
executes the execute_action for the webhook with payload
"""
self.logger.debug('Received settings from splunkd=`%s`', json.dumps(self.settings))
payload = self.settings.get('result', None)
config = self.settings.get('configuration', None)
self.webhook_name = config.get('webhook_name', None)
self.webhook_url = config.get('webhook_uri', None)
try:
if self.webhook_name is None:
self.logger.error('Webhook Name must be defined for webhook action')
sys.exit(1)
if self.webhook_url is None:
self.logger.error('Webhook URL must be defined for webhook action')
sys.exit(1)
self.execute_action(json.dumps(payload))
except Exception as e:
self.logger.error('Failed to execute webhook action.')
self.logger.exception(e)
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == '--execute':
input_params = sys.stdin.read()
webhook = Webhook(input_params)
webhook.execute()