diff --git a/apps/alert_webhook/README/alert_actions.conf.spec b/apps/alert_webhook/README/alert_actions.conf.spec new file mode 100755 index 00000000..db935a4b --- /dev/null +++ b/apps/alert_webhook/README/alert_actions.conf.spec @@ -0,0 +1,47 @@ +[webhook] + +param.user_agent = +* The value of the User-Agent HTTP header that the Splunk platform sends + to the webhook receiver. +* No default. + +enable_allowlist = +* Whether or not the Splunk platform alert webhook uses the webhook allowlist + when it performs a webhook query. +* The webhook allowlist defines the URLs for which webhook + alert actions can send HTTP POST requests. +* A value of "true" means that the webhook allowlist is turned on, and + that the Splunk platform lets the webhook action query against any endpoint. + See the CAUTION later in this description for details. +* A value of "false" means that the webhook allowlist is turned off. +* While this setting is valid within the alert-actions.conf file + within the alert_webhook app, it is also available in the + alert-actions.conf file in Splunk Enterprise. +* CAUTION: Be mindful when using this setting. If you give the setting + a value of "true", you must also configure the 'allowlist.' setting. + Failure to do so is a security risk, as the webhook alert action can then + query against any REST endpoint, including external endpoints that are not + in your control and could be malicious. +* Default (for Splunk Cloud Platform): true +* Default (for all other Splunk products including Splunk Enterprise): false + +allowlist. = +* A list of endpoints upon which the Splunk platform webhook action can query. +* Each allowlist entry must begin with the string "allowlist." and must be + on its own line. +* The component of an allowlist entry can be any string, but must be + unique for each entry. +* Values are regular expression strings which must match URLs which you allow + the webhook action to access. +* Following is an example allowlist: + * allowlist.endpoint1 = ^https:\/\/10\.201\..*\/ + * This allowlist entry lets the webhook action access URLs of endpoints that + begin with the string "https://10.201" and end with a forward slash (/). + * allowlist.endpoint2 = ^https:\/\/(.*\.|)company.com\/?.*\/ + * This allowlist entry lets the webhook action access URLs of endpoints that + begin with the string "https://", contain any machine within the domain + "company.com", and end with a forward slash (/). +* CAUTION: If you don't specify an allowlist after configuring the 'enable_allowlist' + setting with a value of "true", the Splunk platform lets the webhook + action query against any endpoint, which is a security risk. +* No default. diff --git a/apps/alert_webhook/README/savedsearches.conf.spec b/apps/alert_webhook/README/savedsearches.conf.spec new file mode 100755 index 00000000..a20b8e5a --- /dev/null +++ b/apps/alert_webhook/README/savedsearches.conf.spec @@ -0,0 +1,7 @@ +# Webook alert action settings + +action.webhook = [0|1] +* Enable webhook action + +action.webhook.param.url = +* URL to send the HTTP POST request to. Must be accessible from the Splunk server. \ No newline at end of file diff --git a/apps/alert_webhook/appserver/static/webhook.png b/apps/alert_webhook/appserver/static/webhook.png new file mode 100755 index 00000000..6f2dba2c Binary files /dev/null and b/apps/alert_webhook/appserver/static/webhook.png differ diff --git a/apps/alert_webhook/bin/webhook.py b/apps/alert_webhook/bin/webhook.py new file mode 100755 index 00000000..aaf5d820 --- /dev/null +++ b/apps/alert_webhook/bin/webhook.py @@ -0,0 +1,59 @@ +from __future__ import annotations +import sys +import json +import csv +import gzip +from collections import OrderedDict +from future.moves.urllib.request import urlopen, Request +from future.moves.urllib.error import HTTPError, URLError + +def send_webhook_request(url, body, user_agent=None) -> bool: + if url is None: + sys.stderr.write("ERROR No URL provided\n") + return False + sys.stderr.write("INFO Sending POST request to url=%s with size=%d bytes payload\n" % (url, len(body))) + sys.stderr.write("DEBUG Body: %s\n" % body) + try: + if sys.version_info >= (3, 0) and type(body) == str: + body = body.encode() + settings = {"Content-Type": "application/json"} + if user_agent is not None: + settings['User-Agent'] = user_agent + req = Request(url, body, settings) + res = urlopen(req) + if 200 <= res.code < 300: + sys.stderr.write("INFO Webhook receiver responded with HTTP status=%d\n" % res.code) + return True + else: + sys.stderr.write("ERROR Webhook receiver responded with HTTP status=%d\n" % res.code) + return False + except HTTPError as e: + sys.stderr.write("ERROR Error sending webhook request: %s\n" % e) + except URLError as e: + sys.stderr.write("ERROR Error sending webhook request: %s\n" % e) + except ValueError as e: + sys.stderr.write("ERROR Invalid URL: %s\n" % e) + return False + + +if __name__ == "__main__": + if len(sys.argv) < 2 or sys.argv[1] != "--execute": + sys.stderr.write("FATAL Unsupported execution mode (expected --execute flag)\n") + sys.exit(1) + try: + settings = json.loads(sys.stdin.read()) + url = settings['configuration'].get('url') + body = OrderedDict( + sid=settings.get('sid'), + search_name=settings.get('search_name'), + app=settings.get('app'), + owner=settings.get('owner'), + results_link=settings.get('results_link'), + result=settings.get('result') + ) + user_agent = settings['configuration'].get('user_agent', 'Splunk') + if not send_webhook_request(url, json.dumps(body), user_agent=user_agent): + sys.exit(2) + except Exception as e: + sys.stderr.write("ERROR Unexpected error: %s\n" % e) + sys.exit(3) diff --git a/apps/alert_webhook/default/alert_actions.conf b/apps/alert_webhook/default/alert_actions.conf new file mode 100755 index 00000000..a0722bfc --- /dev/null +++ b/apps/alert_webhook/default/alert_actions.conf @@ -0,0 +1,11 @@ +[webhook] +python.version = latest +is_custom = 1 +label = Webhook +description = Generic HTTP POST to a specified URL +icon_path = webhook.png +payload_format = json + +param.user_agent = Splunk/$server.guid$ + +enable_allowlist = false diff --git a/apps/alert_webhook/default/app.conf b/apps/alert_webhook/default/app.conf new file mode 100755 index 00000000..e1174fe5 --- /dev/null +++ b/apps/alert_webhook/default/app.conf @@ -0,0 +1,18 @@ +# Version 10.0.2 +# +# Splunk app configuration file +# + +[ui] +is_visible = 0 +label = Webhook Alert Action + +[launcher] +author = Splunk +description = Webhook Alert Action +version=10.0.2 + +[install] +state = enabled +is_configured = 1 +allows_disable = false diff --git a/apps/alert_webhook/default/data/ui/alerts/webhook.html b/apps/alert_webhook/default/data/ui/alerts/webhook.html new file mode 100755 index 00000000..4d6ef3d2 --- /dev/null +++ b/apps/alert_webhook/default/data/ui/alerts/webhook.html @@ -0,0 +1,21 @@ +
+
+ + +
+ +
+
+
+
+ + Specified URL to send JSON payload via HTTP POST + (ex., https://your.server.com/api/v1/webhook). +
+ Learn More + +
+
+
+
\ No newline at end of file diff --git a/apps/alert_webhook/default/restmap.conf b/apps/alert_webhook/default/restmap.conf new file mode 100755 index 00000000..caf77848 --- /dev/null +++ b/apps/alert_webhook/default/restmap.conf @@ -0,0 +1,4 @@ +[validation:savedsearch] +# Require url to be set if webhook action is enabled +action.webhook = case('action.webhook' != "1", null(), 'action.webhook.param.url' == "action.webhook.param.url" OR 'action.webhook.param.url' == "", "No Webhook URL specified", 1==1, null()) +action.webhook.param.url = validate( match('action.webhook.param.url', "^https?://[^\s]+$"), "Webhook URL is invalid") \ No newline at end of file diff --git a/apps/alert_webhook/local/alert_actions.conf b/apps/alert_webhook/local/alert_actions.conf new file mode 100755 index 00000000..285c8917 --- /dev/null +++ b/apps/alert_webhook/local/alert_actions.conf @@ -0,0 +1,2 @@ +[webhook] + diff --git a/apps/alert_webhook/metadata/default.meta b/apps/alert_webhook/metadata/default.meta new file mode 100755 index 00000000..ddb8b569 --- /dev/null +++ b/apps/alert_webhook/metadata/default.meta @@ -0,0 +1,13 @@ +# Application-level permissions + +[] +access = read : [ * ], write : [ admin, power ] + +[alert_actions] +export = system + +[alerts] +export = system + +[restmap] +export = system \ No newline at end of file