def test_parse(): try: loginsightwebhookdemo.parse({"abc": "123"}) except AttributeError: assert True else: assert False
def test_parse(): try: loginsightwebhookdemo.parse({"abc": "123"}) except AttributeError: assert True else: assert False
def pagerduty(SERVICEKEY=None, ALERTID=None): """ Create a new incident for the Pagerduty service identified by `SERVICEKEY` in the URL. Uses the https://v2.developer.pagerduty.com/v2/docs/trigger-events API directly. """ if not PAGERDUTYURL: return ("PAGERDUTYURL parameter must be set properly, please edit the shim!", 500, None) if not SERVICEKEY: return ("SERVICEKEY must be set in the URL (e.g. /endpoint/pagerduty/<SERVICEKEY>", 500, None) # Retrieve fields in notification a = parse(request) payload = { "service_key": SERVICEKEY, "event_type": "trigger", "description": a['AlertName'], "details": { "notes": a['info'], "events": str(a['Messages']), }, "contexts": [ { "type": "link", "href": a['url'], "text": "View search results" }, { "type": "link", "href": a['editurl'], "text": "View alert definition" }, ] } return callapi(PAGERDUTYURL, 'post', json.dumps(payload))
def jira(ALERTID=None, PROJECT=None, ISSUETYPE='Bug'): """ Create a new bug for every incoming webhook that does not already have an unresolved bug. If an bug is currently unresolved, add a new comment to the unresolved bug. If `ISSUETYPE` is passed, blindly attempt to open/check the specified `ISSUETYPE`. Requires JIRA* parameters to be defined. """ bauth = request.authorization if bauth is not None: global JIRAUSER global JIRAPASS JIRAUSER = bauth.username JIRAPASS = bauth.password if not JIRAURL or not JIRAUSER or not JIRAPASS or not PROJECT: return ("JIRA* parameters must be set, please edit the shim!", 500, None) headers = {'Content-type': 'application/json'} a = parse(request) # Get the list of unresolved incidents that contain the AlertName from the incoming webhook incident = callapi( JIRAURL + '/rest/api/2/search?jql=project=' + PROJECT + '+AND+resolution=unresolved+AND+summary~"' + a['AlertName'] + '"', 'get', None, headers, (JIRAUSER, JIRAPASS)) try: i = json.loads(incident) except: return incident try: # Determine if there is an unresolved incident already if i['issues'][0]['key'] is not None: # Option 1: Do nothing #logging.info('Nothing to do, exiting.') #return ("OK", 200, None) # Option 2: Add a new comment payload = {"body": a['moreinfo']} return callapi( JIRAURL + '/rest/api/2/issue/' + i['issues'][0]['key'] + '/comment', 'put', json.dumps(payload), headers, (JIRAUSER, JIRAPASS)) except: # If no open incident then open one payload = { "project": { "key": PROJECT }, "fields": { "summary": a['AlertName'], "description": a['moreinfo'], "issuetype": { "name": ISSUETYPE } } } return callapi(JIRAURL + '/rest/api/2/issue/', 'post', json.dumps(payload), headers, (JIRAUSER, JIRAPASS))
def manageengine(TOKEN=None, ALERTID=None): """ Create a new incident for the bigpanda service identified by `APIKEY` in the URL. Uses https://www.manageengine.com/products/service-desk/sdpod-v3-api/SDPOD-V3-API.html. """ if not MANAGEENGINEURL: return ("MANAGEENGINEURL parameter must be set properly, please edit the shim!", 500, None) if not TOKEN: return ("TOKEN must be set in the URL (e.g. /endpoint/manageengine/<TOKEN>/<APPKEY>", 500, None) HEADERS = {"Accept": "application/json", "Content-Type": "application/json", "TECHNICIAN_KEY": TOKEN} # Retrieve fields in notification a = parse(request) payload = { input_data = "request": { "subject": a['description'], "description": a['moreinfo'], "requester": { "name": "TICKET OPENER" }, "group": { "name": "ASSIGNMENT GROUP" }, "technician": { "name": "TECHNICIAN ASSIGNMENT" } } } return callapi(MANAGEENGINEURL, 'post', json.dumps(payload), HEADERS)
def bigpanda(TOKEN=None, APPKEY=None, ALERTID=None): """ Create a new incident for the bigpanda service identified by `APIKEY` in the URL. Uses https://docs.bigpanda.io/reference#alerts. """ if not BIGPANDAURL: return ( "BIGPANDAURL parameter must be set properly, please edit the shim!", 500, None) if not TOKEN: return ( "TOKEN must be set in the URL (e.g. /endpoint/bigpanda/<TOKEN>/<APPKEY>", 500, None) if not APPKEY: return ( "APPKEY must be set in the URL (e.g. /endpoint/bigpanda/<TOKEN>/<APPKEY>", 500, None) HEADERS = { "Accept": "application/json", "Content-Type": "application/json", "Authorization": "Bearer " + TOKEN } # Retrieve fields in notification a = parse(request) payload = { "app_key": APPKEY, "host": "Webhook Shim", "check": a['AlertName'], "description": a['moreinfo'] } return callapi(BIGPANDAURL, 'post', json.dumps(payload), HEADERS)
def pushbullet(ALERTID=None, TOKEN=None): """ Send a `link` notification to all devices on pushbullet with a link back to the alert's query. If `TOKEN` is not passed, requires `PUSHBULLETTOKEN` defined, see https://www.pushbullet.com/#settings/account """ #if not PUSHBULLETURL: #return ("PUSHBULLET parameter must be set, please edit the shim!", 500, None) #if (TOKEN is not None): #PUSHBULLETTOKEN = TOKEN #if not PUSHBULLETTOKEN: # return ("PUSHBULLETTOKEN parameter must be set, please edit the shim!", 500, None) if not PUSHBULLETTOKEN: return ("fail", 500, None) a = parse(request) payload = { "body": a['info'], "title": a['AlertName'], "type": "link", "url": a['url'], } headers = { 'Content-type': 'application/json', 'Accept': 'application/vnd.tosslab.jandi-v2+json' } return callapi(PUSHBULLETURL, 'post', json.dumps(payload), headers)
def opsgenie(APIKEY=None, TEAM=None, ALERTID=None): """ Create a new incident for the opsgenie service identified by `APIKEY` in the URL. Uses https://www.opsgenie.com/docs/web-api/alert-api#createAlertRequest. """ if not OPSGENIEURL: return ("OPSGENIEURL parameter must be set properly, please edit the shim!", 500, None) if not APIKEY: return ("APIKEY must be set in the URL (e.g. /endpoint/opsgenie/<APIKEY>", 500, None) # Retrieve fields in notification a = parse(request) payload = { "apiKey": APIKEY, "alias": a['AlertName'], "message": a['AlertName'], "details": { "notes": a['info'], "events": str(a['Messages']), }, "description": a['moreinfo'] } if TEAM is not None: payload.append({"teams": [TEAM]}) return callapi(OPSGENIEURL, 'post', json.dumps(payload))
def opsgenie(APIKEY=None, TEAM=None, ALERTID=None): """ Create a new incident for the opsgenie service identified by `APIKEY` in the URL. Uses https://www.opsgenie.com/docs/web-api/alert-api#createAlertRequest. """ if not OPSGENIEURL: return ( "OPSGENIEURL parameter must be set properly, please edit the shim!", 500, None) if not APIKEY: return ( "APIKEY must be set in the URL (e.g. /endpoint/opsgenie/<APIKEY>", 500, None) # Retrieve fields in notification a = parse(request) payload = { "apiKey": APIKEY, "alias": a['AlertName'], "message": a['AlertName'], "details": { "notes": a['info'], "events": str(a['Messages']), }, "description": a['moreinfo'] } if TEAM is not None: payload.append({"teams": [TEAM]}) return callapi(OPSGENIEURL, 'post', json.dumps(payload))
def opsgenie(APIKEY=None, ALERTID=None): """ Create a new incident for the opsgenie service identified by `APIKEY` in the URL. Uses https://docs.opsgenie.com/docs/alert-api. """ if not OPSGENIEURL: return ( "OPSGENIEURL parameter must be set properly, please edit the shim!", 500, None) if not APIKEY: return ( "APIKEY must be set in the URL (e.g. /endpoint/opsgenie/<APIKEY>", 500, None) HEADERS = { "Content-type": "application/json", "Authorization": "GenieKey " + APIKEY } # Retrieve fields in notification a = parse(request) payload = { "alias": a['AlertName'], "message": a['AlertName'], "description": a['moreinfo'] } return callapi(OPSGENIEURL, 'post', json.dumps(payload), HEADERS)
def servicenow(ALERTID=None): """ Create a new incident for every incoming webhook that does not already have an active incident. If an incident is already active, add a new comment to the active alert. Uniqueness is determined by the incoming webhook alert name. Incidents are opened by the user defined. Requires SERVICENOW* parameters to be defined. """ if not SERVICENOWURL or not SERVICENOWUSER or not SERVICENOWPASS: return ("SERVICENOW* parameters must be set, please edit the shim!", 500, None) headers = { 'Content-type': 'application/json', 'Accept': 'application/json' } a = parse(request) payload = {"comments": a['moreinfo']} # Get the sys_id for the user user = callapi( SERVICENOWURL + '/sys_user.do?JSONv2&sysparm_query=user_name=' + SERVICENOWUSER + '&sysparam_fields=sys_id', 'get', None, headers, (SERVICENOWUSER, SERVICENOWPASS)) try: u = json.loads(user) except: return user # Get the list of open incidents that contain the AlertName from the incoming webhook # Note the user may have been changed so do not query for the user incident = callapi( SERVICENOWURL + '/incident.do?JSONv2&sysparm_query=active=true^short_description=' + a['AlertName'], 'get', None, headers, (SERVICENOWUSER, SERVICENOWPASS)) try: i = json.loads(incident) except: return incident try: # Determine if there is an open incident already if i['records'][0]['active'] == 'true': # Option 1: Do nothing #logging.info('Nothing to do, exiting.') #return ("OK", 200, None) # Option 2: Add a new comment return callapi( SERVICENOWURL + '/api/now/v1/table/incident/' + i['records'][0]['sys_id'], 'put', json.dumps(payload), headers, (SERVICENOWUSER, SERVICENOWPASS)) except: # If no open incident then open one payload.update({ "short_description": a['AlertName'], "caller_id": u['records'][0]['sys_id'] }) return callapi(SERVICENOWURL + '/api/now/v1/table/incident', 'post', json.dumps(payload), headers, (SERVICENOWUSER, SERVICENOWPASS))
def zendesk(ALERTID=None, EMAIL=None, TOKEN=None): """ Create a new incident for every incoming webhook that does not already have an open incident. If an incident is already open, add a new comment to the open alert. Uniqueness is determined by the incoming webhook alert name. Requires ZENDESK* parameters to be defined. """ if (not ZENDESKURL or (not ZENDESKUSER and not EMAIL) or (not ZENDESKPASS and not ZENDESKTOKEN and not TOKEN)): return ("ZENDESK* parameters must be set, please edit the shim!", 500, None) if not ZENDESKUSER: USER = EMAIL else: USER = ZENDESKUSER # Prefer tokens over passwords if ZENDESKTOKEN or TOKEN: if ZENDESKTOKEN: USER = USER + '/token' PASS = ZENDESKTOKEN else: USER = USER + '/token' PASS = TOKEN else: PASS = ZENDESKPASS headers = {'Content-type': 'application/json', 'Accept': 'application/json'} a = parse(request) # Get the list of open incidents that contain the AlertName from the incoming webhook incident = callapi(ZENDESKURL + '/api/v2/search.json?query=type:ticket status:open subject:"' + a['AlertName'] + '"', 'get', None, headers, (USER, PASS)) i = json.loads(incident) try: # Determine if there is an open incident already if i['results'][0]['id'] is not None: # Option 1: Do nothing #logging.info('Nothing to do, exiting.') #return ("OK", 200, None) # Option 2: Add a new comment # Limited to 30 updates per 10 minutes (https://developer.zendesk.com/rest_api/docs/core/introduction) payload = { 'ticket': { 'comment': { 'body': a['moreinfo'] } } } return callapi(ZENDESKURL + '/api/v2/tickets/' + str(i['results'][0]['id']) + '.json', 'put', json.dumps(payload), headers, (USER, PASS)) except: # If no open incident then open one payload = { "ticket": { #"requester": { # "name": "Log Insight", # "email": USER #}, "subject": a['AlertName'], "comment": { "body": a['moreinfo'], }, "type": 'incident', "tags": ["loginsight"] } } return callapi(ZENDESKURL + '/api/v2/tickets.json', 'post', json.dumps(payload), headers, (USER, PASS))
def slack(NUMRESULTS=10, ALERTID=None, T=None, B=None, X=None): """ Consume messages, and send them to Slack as an Attachment object. Sends up to `NUMRESULTS` (default 10) events onward. If `T/B/X` is not passed, requires `SLACKURL` defined in the form `https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX` For more information, see https://api.slack.com/incoming-webhooks """ # Prefer URL parameters to SLACKURL if X is not None: URL = 'https://hooks.slack.com/services/' + T + '/' + B + '/' + X elif not SLACKURL or not 'https://hooks.slack.com/services' in SLACKURL: return ( "SLACKURL parameter must be set properly, please edit the shim!", 500, None) else: URL = SLACKURL a = parse(request) slack_attachments = [] if (a['color'] == 'red'): color = 'danger' elif (a['color'] == 'yellow'): color = 'warning' else: color = 'info' payload = { "icon_url": a['icon'], } try: if ('AlertName' in a): # remove "Alert Details" field in 'moreinfo' tmp = a['moreinfo'] tmp = tmp.strip().split('\n') tmp.remove('') [tmp.remove(t) for t in tmp if t.startswith('Alert Details')] tmp = "\n".join(tmp) # end removal slack_attachments.append({ "pretext": tmp, }) if ('Messages' in a): for message in a['Messages'][:NUMRESULTS]: slack_attachments.append(slack_fields(color, message)) if a['fields']: slack_attachments.append(slack_fields(color, a)) except: logging.exception( "Can't create new payload. Check code and try again.") raise payload.update({ "username": a['hookName'], "attachments": slack_attachments }) return callapi(URL, 'post', json.dumps(payload))
def moogsoft(ALERTID=None): """ Information about this shim. Requires moogsoft* parameters to be defined. """ bauth = request.authorization if bauth is not None: global moogsoftUSER global moogsoftPASS moogsoftUSER = bauth.username moogsoftPASS = bauth.password if (not moogsoftURL or (not moogsoftUSER ) or (not moogsoftPASS) or (not vropsURL) or (not vropsUser) or (not vropsPass)): return ("moogsoft* and vrops* parameters must be set, please edit the shim!", 500, None) a = parse(request) ######################################## #Map vROps crticiality to moog sev here ######################################## sevMap = { "ALERT_CRITICALITY_LEVEL_CRITICAL":5, "ALERT_CRITICALITY_LEVEL_IMMEDIATE":4, "ALERT_CRITICALITY_LEVEL_WARNING":3, "ALERT_CRITICALITY_LEVEL_INFO":2 } link = vropsURL+"/ui/index.action#/object/"+a['resourceId']+"/alertsAndSymptoms/alerts/"+ALERTID recommendation = recommendations(a['alertId']) resourceProperties = fetchResourceProperties(a['resourceId']) payload = { "signature":ALERTID, "source_id":a['resourceId'], "external_id":ALERTID, "manager":a['hookName'], "source":a['resourceName'] if (a['resourceName'] != "") else "undefined", "class":a['subType'], "agent":a['adapterKind'], "agent_location":"", "type":a['type']+"::"+a['subType'], "severity":sevMap[a['criticality']] if (a['status'] != 'CANCELED') else 0, "description":a['AlertName'], "agent_time":a['updateDate']/1000, "recommendation":recommendation, "resource_properties":resourceProperties, "link":link } # Defaults to Content-type: application/json # If changed you must specify the content-type manually headers = {'Content-type': 'application/json', 'Accept': 'application/json'} if not headers: headers = None return callapi(moogsoftURL, 'post', json.dumps(payload), headers, check=VERIFY)
def groove(ALERTID=None, TOKEN=None, FROM=None, TO=None): """ Create a new ticket for every incoming webhook. Groove does not support searching for open tickets by subject today. Requires GROOVE* parameters to be defined. """ if not GROOVEURL or (not GROOVEFROM and not FROM) or (not GROOVETO and not TO): return ("GROOVE* parameters must be set, please edit the shim!", 500, None) if TOKEN is None: return ("TOKEN must be passed in the URL, please edit the incoming webhook!", 500, None) if GROOVEFROM and not FROM: FROM = GROOVEFROM if GROOVETO and not TO: TO = GROOVETO a = parse(request) headers = { 'Content-type': 'application/json', 'Authorization': 'Bearer ' + TOKEN } # Unfortunately, the Groove API does not support searching for open tickets by subject today # https://www.groovehq.com/docs/tickets#finding-one-ticket # As a result, every incoming webhook will result in a new ticket # Commented sections has been wired for when the Groove API is updated to support the use case ## Get the list of open incidents that contain the AlertName from the incoming webhook # payload = { # "state": "notstarted,opened,pending", # "subject": a['AlertName'] # } # incident = callapi(GROOVEURL + '/tickets', 'get', payload, headers) # # try: # i = json.loads(incident) # except: # return incident # try: # Determine if there is an open incident already # if i['tickets'][0]['number'] is not None: # # Option 1: Do nothing # #logging.info('Nothing to do, exiting.') # #return ("OK", 200, None) # # Option 2: Add a new message # payload = { 'body': a['moreinfo'] } # return callapi(GROOVEURL + '/tickets/' + str(i['tickets'][0]['number']) + '/messages', 'put', json.dumps(payload), headers) # except: # If no open incident then open one payload = { "ticket": { "body": a['moreinfo'], "from": FROM, "to": TO, "subject": a['AlertName'], "tags": ["loginsight"] } } return callapi(GROOVEURL + '/tickets', 'post', json.dumps(payload), headers)
def msteams(NUMRESULTS=10, ALERTID=None): """ Posts a message to a Microsoft Teams channel. Set TEAMSURL to the incoming webhook URL provided by Teams. To create a Teams webhook see: http://aka.ms/o365-connectors """ if not TEAMSURL: return ("The TEAMSURL parameter must be set, please edit the shim!", 500, None) a = parse(request) teams_attachments = [ { "activityImage": a['icon'], "activityTitle": a['AlertName'] if 'AlertName' in a else "Alert details", "activitySubtitle": "[Link to Alert](" + a['url'] + ")" if a['url'] else "", } ] try: if ('AlertName' in a): if ('Messages' in a): for message in a['Messages'][:NUMRESULTS]: teams_attachments.append(teams_fields(message)) if (a['fields'] is not None): teams_attachments.append(teams_fields(a)) except: logging.exception("Can't create new payload. Check code and try again.") raise if (a['color'] == 'red'): color = 'CC0000' elif (a['color'] == 'yellow'): color = 'FFCC00' elif (a['color'] == 'green'): color = '009000' else: color = '0075FF' payload = { "@type": "MessageCard", "@context": "http://schema.org/extensions", "themeColor": color, "summary": "Message from " + a['hookName'], "sections": teams_attachments } # Defaults to Content-type: application/json # If changed you must specify the content-type manually headers = {'Content-type': 'application/json', 'Accept': 'application/json'} if not headers: headers = None return callapi(TEAMSURL, 'post', json.dumps(payload), headers)
def jira(ALERTID=None, PROJECT=None, ISSUETYPE='Bug'): """ Create a new bug for every incoming webhook that does not already have an unresolved bug. If an bug is currently unresolved, add a new comment to the unresolved bug. If `ISSUETYPE` is passed, blindly attempt to open/check the specified `ISSUETYPE`. Requires JIRA* parameters to be defined. """ bauth = request.authorization if bauth is not None: global JIRAUSER global JIRAPASS JIRAUSER = bauth.username JIRAPASS = bauth.password if not JIRAURL or not JIRAUSER or not JIRAPASS or not PROJECT: return ("JIRA* parameters must be set, please edit the shim!", 500, None) headers = {'Content-type': 'application/json'} a = parse(request) # Get the list of unresolved incidents that contain the AlertName from the incoming webhook incident = callapi(JIRAURL + '/rest/api/2/search?jql=project=' + PROJECT + '+AND+resolution=unresolved+AND+summary~"' + a['AlertName'] + '"', 'get', None, headers, (JIRAUSER, JIRAPASS)) try: i = json.loads(incident) except: return incident try: # Determine if there is an unresolved incident already if i['issues'][0]['key'] is not None: # Option 1: Do nothing #logging.info('Nothing to do, exiting.') #return ("OK", 200, None) # Option 2: Add a new comment payload = { "body": a['moreinfo'] } return callapi(JIRAURL + '/rest/api/2/issue/' + i['issues'][0]['key'] + '/comment', 'put', json.dumps(payload), headers, (JIRAUSER, JIRAPASS)) except: # If no open incident then open one payload = { "project": { "key": PROJECT }, "fields": { "summary": a['AlertName'], "description": a['moreinfo'], "issuetype": { "name": ISSUETYPE } } } return callapi(JIRAURL + '/rest/api/2/issue/', 'post', json.dumps(payload), headers, (JIRAUSER, JIRAPASS))
def pagerduty(SERVICEKEY=None, ALERTID=None): if not SERVICEKEY: return ( "SERVICEKEY must be set in the URL (e.g. /endpoint/pagerduty/<SERVICEKEY>", 500, None) # Retrieve fields in notification a = parse(request) payload = { "payload": { "summary": a['AlertName'], "source": a['hookName'], "severity": "info", "custom_details": { "startDate": a['startDate'], "criticality": a['criticality'], "resourceId": a['resourceId'], "alertId": a['alertId'], "status": a['status'], "resourceName": a['resourceName'], "updateDate": a['updateDate'], "info": a['info'], "moreinfo": a['moreinfo'], "fields": a['fields'] }, }, "routing_key": SERVICEKEY, "event_action": "trigger", "description": a['AlertName'], "details": { "notes": a['info'], "events": str(a['Messages']), }, "contexts": [ { "type": "link", "href": a['url'], "text": "View search results" }, { "type": "link", "href": a['editurl'], "text": "View alert definition" }, ] } return callapi(PAGERDUTYURL, 'post', json.dumps(payload))
def servicenow(ALERTID=None): """ Create a new incident for every incoming webhook that does not already have an active incident. If an incident is already active, add a new comment to the active alert. Uniqueness is determined by the incoming webhook alert name. Incidents are opened by the user defined. Requires SERVICENOW* parameters to be defined. """ bauth = request.authorization if bauth is not None: global SERVICENOWUSER global SERVICENOWPASS SERVICENOWUSER = bauth.username SERVICENOWPASS = bauth.password if not SERVICENOWURL or not SERVICENOWUSER or not SERVICENOWPASS: return ("SERVICENOW* parameters must be set, please edit the shim!", 500, None) headers = {'Content-type': 'application/json', 'Accept': 'application/json'} a = parse(request) payload = { "comments": a['moreinfo'] } # Get the sys_id for the user user = callapi(SERVICENOWURL + '/sys_user.do?JSONv2&sysparm_query=user_name=' + SERVICENOWUSER + '&sysparam_fields=sys_id', 'get', None, headers, (SERVICENOWUSER, SERVICENOWPASS)) try: u = json.loads(user) except: return user # Get the list of open incidents that contain the AlertName from the incoming webhook # Note the user may have been changed so do not query for the user incident = callapi(SERVICENOWURL + '/incident.do?JSONv2&sysparm_query=active=true^short_description=' + a['AlertName'], 'get', None, headers, (SERVICENOWUSER, SERVICENOWPASS)) try: i = json.loads(incident) except: return incident try: # Determine if there is an open incident already if i['records'][0]['active'] == 'true': # Option 1: Do nothing #logging.info('Nothing to do, exiting.') #return ("OK", 200, None) # Option 2: Add a new comment return callapi(SERVICENOWURL + '/api/now/v1/table/incident/' + i['records'][0]['sys_id'], 'put', json.dumps(payload), headers, (SERVICENOWUSER, SERVICENOWPASS)) except: # If no open incident then open one payload.update({ "short_description": a['AlertName'], "caller_id": u['records'][0]['sys_id'] }) return callapi(SERVICENOWURL + '/api/now/v1/table/incident', 'post', json.dumps(payload), headers, (SERVICENOWUSER, SERVICENOWPASS))
def pivotaltracker(ALERTID=None, TOKEN=None, PROJECT=None): """ Create a new bug for every incoming webhook that does not already have an existing one. If a bug already exists, do nothing. Uniqueness is determined by the incoming webhook alert name. Requires PIVOTALTRACKER* parameters to be defined. """ if (not PIVOTALTRACKERURL or (not PIVOTALTRACKERTOKEN and not TOKEN) or (not PIVOTALTRACKERPROJECT and not PROJECT)): return ( "PIVOTALTRACKER* parameters must be set, please edit the shim!", 500, None) if not PROJECT: TOKEN = PIVOTALTRACKERTOKEN PROJECT = PIVOTALTRACKERPROJECT a = parse(request) # Defaults to Content-type: application/json # If changed you must specify the content-type manually headers = {'Content-type': 'application/json', 'X-TrackerToken': TOKEN} # Get the list of open bugs that contain the AlertName from the incoming webhook bug = callapi( PIVOTALTRACKERURL + PROJECT + '/stories?filter=name:"' + a['AlertName'] + '"', 'get', None, headers, None) try: b = json.loads(bug) except: return bug try: # Determine if there is an open incident already if b[0] is not None: # Option 1: Do nothing logging.debug('Nothing to do, exiting.') return ("OK", 200, None) # Option 2: Add a new comment #payload = { 'ticket': { 'comment': { 'body': a['moreinfo'] } } } #return callapi(PIVOTALTRACKERURL + '/api/v2/tickets/' + str(i['results'][0]['id']) + '.json', 'put', json.dumps(payload), headers, (USER, PASS)) except: # If no open bug then open one payload = { "name": a['AlertName'], "description": a['moreinfo'], "story_type": "bug" } return callapi(PIVOTALTRACKERURL + PROJECT + '/stories', 'post', json.dumps(payload), headers, None)
def template(ALERTID=None, TOKEN=None, EMAIL=None): """ Information about this shim. Requires TEMPLATE* parameters to be defined. """ if (not TEMPLATEURL or (not TEMPLATEUSER and not EMAIL) or (not TEMPLATEPASS and not TEMPLATETOKEN and not TOKEN)): return ("TEMPLATE* parameters must be set, please edit the shim!", 500, None) if not TEMPLATEUSER: USER = EMAIL else: USER = TEMPLATEUSER # Prefer tokens over passwords if TEMPLATETOKEN or TOKEN: if TEMPLATETOKEN: USER = USER + '/token' PASS = TEMPLATETOKEN else: USER = USER + '/token' PASS = TOKEN else: PASS = TEMPLATEPASS a = parse(request) payload = { "body": a['info'], "title": a['AlertName'], "type": "link", "url": a['url'], } # Defaults to Content-type: application/json # If changed you must specify the content-type manually headers = { 'Content-type': 'application/json', 'Accept': 'application/json' } if not headers: headers = None ######################### # Fire and Forgot Systems ######################### return callapi(TEMPLATEURL, 'post', json.dumps(payload), headers)
def slack(NUMRESULTS=10, ALERTID=None, T=None, B=None, X=None): """ Consume messages, and send them to Slack as an Attachment object. Sends up to `NUMRESULTS` (default 10) events onward. If `T/B/X` is not passed, requires `SLACKURL` defined in the form `https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX` For more information, see https://api.slack.com/incoming-webhooks """ # Prefer URL parameters to SLACKURL if X is not None: URL = 'https://hooks.slack.com/services/' + T + '/' + B + '/' + X elif not SLACKURL or not 'https://hooks.slack.com/services' in SLACKURL: return ("SLACKURL parameter must be set properly, please edit the shim!", 500, None) else: URL = SLACKURL a = parse(request) slack_attachments = [] if (a['color'] == 'red'): color = 'danger' elif (a['color'] == 'yellow'): color = 'warning' else: color = 'info' payload = { "icon_url": a['icon'], } try: if ('AlertName' in a): slack_attachments.append({ "pretext": a['moreinfo'], }) if ('Messages' in a): for message in a['Messages'][:NUMRESULTS]: slack_attachments.append(slack_fields(color, message)) if a['fields']: slack_attachments.append(slack_fields(color, a)) except: logging.exception("Can't create new payload. Check code and try again.") raise payload.update({ "username": a['hookName'], "attachments": slack_attachments }) return callapi(URL, 'post', json.dumps(payload))
def template(ALERTID=None, TOKEN=None, EMAIL=None): """ Information about this shim. Requires TEMPLATE* parameters to be defined. """ if (not TEMPLATEURL or (not TEMPLATEUSER and not EMAIL) or (not TEMPLATEPASS and not TEMPLATETOKEN and not TOKEN)): return ("TEMPLATE* parameters must be set, please edit the shim!", 500, None) if not TEMPLATEUSER: USER = EMAIL else: USER = TEMPLATEUSER # Prefer tokens over passwords if TEMPLATETOKEN or TOKEN: if TEMPLATETOKEN: USER = USER + '/token' PASS = TEMPLATETOKEN else: USER = USER + '/token' PASS = TOKEN else: PASS = TEMPLATEPASS a = parse(request) payload = { "body": a['info'], "title": a['AlertName'], "type": "link", "url": a['url'], } # Defaults to Content-type: application/json # If changed you must specify the content-type manually headers = {'Content-type': 'application/json', 'Accept': 'application/json'} if not headers: headers = None ######################### # Fire and Forgot Systems ######################### return callapi(TEMPLATEURL, 'post', json.dumps(payload), headers)
def pivotaltracker(ALERTID=None, TOKEN=None, PROJECT=None): """ Create a new bug for every incoming webhook that does not already have an existing one. If a bug already exists, do nothing. Uniqueness is determined by the incoming webhook alert name. Requires PIVOTALTRACKER* parameters to be defined. """ if (not PIVOTALTRACKERURL or (not PIVOTALTRACKERTOKEN and not TOKEN) or (not PIVOTALTRACKERPROJECT and not PROJECT)): return ("PIVOTALTRACKER* parameters must be set, please edit the shim!", 500, None) if not PROJECT: TOKEN = PIVOTALTRACKERTOKEN PROJECT = PIVOTALTRACKERPROJECT a = parse(request) # Defaults to Content-type: application/json # If changed you must specify the content-type manually headers = {'Content-type': 'application/json', 'X-TrackerToken': TOKEN} # Get the list of open bugs that contain the AlertName from the incoming webhook bug = callapi(PIVOTALTRACKERURL + PROJECT + '/stories?filter=name:"' + a['AlertName'] + '"', 'get', None, headers, None) try: b = json.loads(bug) except: return bug try: # Determine if there is an open incident already if b[0] is not None: # Option 1: Do nothing logging.debug('Nothing to do, exiting.') return ("OK", 200, None) # Option 2: Add a new comment #payload = { 'ticket': { 'comment': { 'body': a['moreinfo'] } } } #return callapi(PIVOTALTRACKERURL + '/api/v2/tickets/' + str(i['results'][0]['id']) + '.json', 'put', json.dumps(payload), headers, (USER, PASS)) except: # If no open bug then open one payload = { "name": a['AlertName'], "description": a['moreinfo'], "story_type": "bug" } return callapi(PIVOTALTRACKERURL + PROJECT + '/stories', 'post', json.dumps(payload), headers, None)
def travisci(ALERTID=None, TOKEN=None, REPO=None, BRANCH=None): """ If called, run a Travis CI job. If `TOKEN`, `REPO`, and/or `BRANCH` are not passed then the must be defined For more information, see https://docs.travis-ci.com/user/triggering-builds """ if (not TRAVISCIURL or (not TRAVISCITOKEN and not TOKEN) or (not TRAVISCIREPO and not REPO) or (not TRAVISCIBRANCH and not BRANCH)): return ("TRAVISCI* parameters must be set, please edit the shim!", 500, None) # Prefer tokens over passwords if not TOKEN: TOKEN = TRAVISCITOKEN if not REPO: REPO = TRAVISCIREPO if not BRANCH: BRANCH = TRAVISCIBRANCH a = parse(request) payload = { "request": { "message": "Override the commit message: this is an api request", "branch": BRANCH, "token": TOKEN } } # Defaults to Content-type: application/json # If changed you must specify the content-type manually headers = { 'Content-type': 'application/json', 'Accept': 'application/json', 'Accept': 'application/vnd.travis-ci.2+json', 'Client': 'LogInsight/1.0', 'Authorization': 'token ' + TOKEN, 'Travis-API-Version': '3' } return callapi(TRAVISCIURL + REPO + '/requests', 'post', json.dumps(payload), headers)
def pagerduty(SERVICEKEY=None, ALERTID=None): """ Create a new incident for the Pagerduty service identified by `SERVICEKEY` in the URL. Uses the https://v2.developer.pagerduty.com/v2/docs/trigger-events API directly. """ if not PAGERDUTYURL: return ( "PAGERDUTYURL parameter must be set properly, please edit the shim!", 500, None) if not SERVICEKEY: return ( "SERVICEKEY must be set in the URL (e.g. /endpoint/pagerduty/<SERVICEKEY>", 500, None) # Retrieve fields in notification a = parse(request) payload = { "service_key": SERVICEKEY, "event_type": "trigger", "description": a['AlertName'], "details": { "notes": a['info'], "events": str(a['Messages']), }, "contexts": [ { "type": "link", "href": a['url'], "text": "View search results" }, { "type": "link", "href": a['editurl'], "text": "View alert definition" }, ] } return callapi(PAGERDUTYURL, 'post', json.dumps(payload))
def opsgenie(APIKEY=None, ALERTID=None): """ Create a new incident for the opsgenie service identified by `APIKEY` in the URL. Uses https://docs.opsgenie.com/docs/alert-api. """ if not OPSGENIEURL: return ("OPSGENIEURL parameter must be set properly, please edit the shim!", 500, None) if not APIKEY: return ("APIKEY must be set in the URL (e.g. /endpoint/opsgenie/<APIKEY>", 500, None) HEADERS = {"Content-type": "application/json", "Authorization": "GenieKey " + APIKEY} # Retrieve fields in notification a = parse(request) payload = { "alias": a['AlertName'], "message": a['AlertName'], "description": a['moreinfo'] } return callapi(OPSGENIEURL, 'post', json.dumps(payload), HEADERS)
def wxteams(HOOKID=None, RESOURCEID=None): """ Send messages to Spark/Webex Teams. If HOOKID is not present, requires WXTEAMS defined as 'https://api.ciscospark.com/v1/webooks/incoming/<HOOKID> """ # Prefer URL parameters to WXTEAMS if HOOKID is not None: URL = 'https://api.ciscospark.com/v1/webhooks/incoming/' + HOOKID elif not WXTEAMS or not 'https://api.ciscospark.com/v1/webhooks/incoming' in WXTEAMS: return ( "WXTEAMS parameter must be set properly, please edit the shim!", 500, None) else: URL = WXTEAMS a = parse(request) try: if ('alertId' in a): timezone = pytz.timezone('America/Chicago') d = datetime.fromtimestamp(int(a['startDate'] / 1000.0)).replace(tzinfo=timezone) alertTime = d.strftime('%Y-%m-%d %H:%M:%S') message = "Resource Name: {resourceName}\n" \ "Timestamp: {alertTime}\n" \ "Status: {status}\n" \ "Info: {info}".format(resourceName=a['resourceName'], alertTime=alertTime, status=a['status'], info=a['info'] ) payload = {"text": message} except: logging.exception( "Can't create new payload. Check code and try again.") raise return callapi(URL, 'post', json.dumps(payload))
def travisci(ALERTID=None, TOKEN=None, REPO=None, BRANCH=None): """ If called, run a Travis CI job. If `TOKEN`, `REPO`, and/or `BRANCH` are not passed then the must be defined For more information, see https://docs.travis-ci.com/user/triggering-builds """ if (not TRAVISCIURL or (not TRAVISCITOKEN and not TOKEN) or (not TRAVISCIREPO and not REPO) or (not TRAVISCIBRANCH and not BRANCH)): return ("TRAVISCI* parameters must be set, please edit the shim!", 500, None) # Prefer tokens over passwords if not TOKEN: TOKEN = TRAVISCITOKEN if not REPO: REPO = TRAVISCIREPO if not BRANCH: BRANCH = TRAVISCIBRANCH a = parse(request) payload = { "request": { "message": "Override the commit message: this is an api request", "branch": BRANCH, "token": TOKEN } } # Defaults to Content-type: application/json # If changed you must specify the content-type manually headers = { 'Content-type': 'application/json', 'Accept': 'application/json', 'Accept': 'application/vnd.travis-ci.2+json', 'Client': 'LogInsight/1.0', 'Authorization': 'token ' + TOKEN, 'Travis-API-Version': '3' } return callapi(TRAVISCIURL + REPO + '/requests', 'post', json.dumps(payload), headers)
def bigpanda(TOKEN=None, APPKEY=None, ALERTID=None): """ Create a new incident for the bigpanda service identified by `APIKEY` in the URL. Uses https://docs.bigpanda.io/reference#alerts. """ if not BIGPANDAURL: return ("BIGPANDAURL parameter must be set properly, please edit the shim!", 500, None) if not TOKEN: return ("TOKEN must be set in the URL (e.g. /endpoint/bigpanda/<TOKEN>/<APPKEY>", 500, None) if not APPKEY: return ("APPKEY must be set in the URL (e.g. /endpoint/bigpanda/<TOKEN>/<APPKEY>", 500, None) HEADERS = {"Accept": "application/json", "Content-Type": "application/json", "Authorization": "Bearer " + TOKEN} # Retrieve fields in notification a = parse(request) payload = { "app_key": APPKEY, "host": "Webhook Shim", "check": a['AlertName'], "description": a['moreinfo'] } return callapi(BIGPANDAURL, 'post', json.dumps(payload), HEADERS)
def pushbullet(ALERTID=None, TOKEN=None): """ Send a `link` notification to all devices on pushbullet with a link back to the alert's query. If `TOKEN` is not passed, requires `PUSHBULLETTOKEN` defined, see https://www.pushbullet.com/#settings/account """ if not PUSHBULLETURL: return ("PUSHBULLET parameter must be set, please edit the shim!", 500, None) if (TOKEN is not None): PUSHBULLETTOKEN = TOKEN if not PUSHBULLETTOKEN: return ("PUSHBULLETTOKEN parameter must be set, please edit the shim!", 500, None) a = parse(request) payload = { "body": a['info'], "title": a['AlertName'], "type": "link", "url": a['url'], } headers = {'Content-type': 'application/json', 'Access-Token': PUSHBULLETTOKEN} return callapi(PUSHBULLETURL, 'post', json.dumps(payload), headers)
def bugzilla(ALERTID=None, TOKEN=None, PRODUCT=None, COMPONENT=None, VERSION=None): """ Create a new bug for every incoming webhook that does not already have an open bug. If an bug is already open, add a new comment to the open bug. Uniqueness is determined by the incoming webhook alert name combined with bugzilla product and component. Requires BUGZILLA* parameters to be defined. You can pass an authentication token in the URL. For basic auth, pass `-` as the token. """ if (not BUGZILLAURL or ((not BUGZILLAUSER or not BUGZILLAPASS) and (not TOKEN or TOKEN == '-')) or ((not BUGZILLAPRODUCT or not BUGZILLACOMPONENT or not BUGZILLAVERSION) and not VERSION)): logging.debug("URL: %s\nUSER: %s\nPASS: %s\nTOKEN: %s\nPRODUCT: %s / %s\nCOMPONENT: %s / %s\nVERSION: %s / %s" % (BUGZILLAURL, BUGZILLAUSER, BUGZILLAPASS, TOKEN, BUGZILLAPRODUCT, PRODUCT, BUGZILLACOMPONENT, COMPONENT, BUGZILLAVERSION, VERSION)) return ("BUGZILLA* parameters must be set, please edit the shim!", 500, None) a = parse(request) if TOKEN and TOKEN != '-': auth = 'api_key=' + TOKEN else: auth = 'login='******'&password='******'Content-type': 'application/json', 'Accept': 'application/json'} # Get the list of open bugs that contain the AlertName from the incoming webhook bug = callapi(BUGZILLAURL + '/rest/bug?' + auth + '&product=' + PRODUCT + '&component=' + COMPONENT + '&summary=' + a['AlertName'], 'get', None, headers, None, VERIFY) try: i = json.loads(bug) except: return bug try: # Determine if there is an open bug already if i['bugs'][0]['id'] is not None: # Option 1: Do nothing #logging.info('Nothing to do, exiting.') #return ("OK", 200, None) # Option 2: Add a new comment payload = { 'comment': { 'body': a['moreinfo'] } } return callapi(BUGZILLAURL + '/rest/bug/' + str(i['bugs'][0]['id']) + '?' + auth, 'put', json.dumps(payload), headers, None, VERIFY) except: # If no open bug then open one payload = { "product" : PRODUCT, "component" : COMPONENT, "version" : VERSION, "summary" : a['AlertName'], "description": a['info'], } # op_sys and rep_platform may not be needed, but are to test on landfill if "landfill.bugzilla.org" in BUGZILLAURL: payload.update({ "op_sys": "All", "rep_platform": "All", }) return callapi(BUGZILLAURL + '/rest/bug?' + auth, 'post', json.dumps(payload), headers, None, VERIFY)
def groove(ALERTID=None, TOKEN=None, FROM=None, TO=None): """ Create a new ticket for every incoming webhook. Groove does not support searching for open tickets by subject today. Requires GROOVE* parameters to be defined. """ if not GROOVEURL or (not GROOVEFROM and not FROM) or (not GROOVETO and not TO): return ("GROOVE* parameters must be set, please edit the shim!", 500, None) if TOKEN is None: return ( "TOKEN must be passed in the URL, please edit the incoming webhook!", 500, None) if GROOVEFROM and not FROM: FROM = GROOVEFROM if GROOVETO and not TO: TO = GROOVETO a = parse(request) headers = { 'Content-type': 'application/json', 'Authorization': 'Bearer ' + TOKEN } # Unfortunately, the Groove API does not support searching for open tickets by subject today # https://www.groovehq.com/docs/tickets#finding-one-ticket # As a result, every incoming webhook will result in a new ticket # Commented sections has been wired for when the Groove API is updated to support the use case ## Get the list of open incidents that contain the AlertName from the incoming webhook # payload = { # "state": "notstarted,opened,pending", # "subject": a['AlertName'] # } # incident = callapi(GROOVEURL + '/tickets', 'get', payload, headers) # # try: # i = json.loads(incident) # except: # return incident # try: # Determine if there is an open incident already # if i['tickets'][0]['number'] is not None: # # Option 1: Do nothing # #logging.info('Nothing to do, exiting.') # #return ("OK", 200, None) # # Option 2: Add a new message # payload = { 'body': a['moreinfo'] } # return callapi(GROOVEURL + '/tickets/' + str(i['tickets'][0]['number']) + '/messages', 'put', json.dumps(payload), headers) # except: # If no open incident then open one payload = { "ticket": { "body": a['moreinfo'], "from": FROM, "to": TO, "subject": a['AlertName'], "tags": ["loginsight"] } } return callapi(GROOVEURL + '/tickets', 'post', json.dumps(payload), headers)
def vro(WORKFLOWID=None, TOKEN=None, HOK=None, ALERTID=None): """ Start a vRealize Orchestrator workflow, passing the entire JSON alert as a base64-encoded string. The `WORKFLOWID` and optionally `TOKEN` is passed in the webhook URL. The workflow is responsible for parsing base64 -> json -> messages """ if not WORKFLOWID: return ( "WORKFLOWID must be set in the URL (e.g. /endpoint/vro/<WORKFLOWID>", 500, None) if not re.match('[a-z0-9-]+', WORKFLOWID, flags=re.IGNORECASE): return ( "WORKFLOWID must consist of alphanumeric and dash characters only", 500, None) if not VROHOSTNAME: return ("VROHOSTNAME parameter is not net, please edit the shim!", 500, None) if not USENETRC and (not VROUSER and not VROPASS and not VROTOKEN and not TOKEN and not VROHOK and not HOK): return ( "VRO* authentication parameters must be set, please edit the shim!", 500, None) if TOKEN is None and VROTOKEN: TOKEN = VROTOKEN elif HOK is None and VROHOK: HOK = VROHOK AUTH = None HEADERS = None if TOKEN is not None: HEADERS = {"Authorization": TOKEN} elif HOK is not None: HEADERS = {"Authorization": "Bearer " + HOK} elif not USENETRC: AUTH = (VROUSER, VROPASS) if ALERTID is None: # LI payload a = parse(request) payload = { "parameters": [{ "value": { "string": { "value": base64.b64encode(request.get_data()) } }, "type": "string", "name": "messages", "scope": "local" }, { "value": { "string": { "value": a['AlertName'] } }, "type": "string", "name": "alertName", "scope": "local" }, { "value": { "number": { "value": a['NumHits'] } }, "type": "number", "name": "hitCount", "scope": "local" }] } else: # vROps payload # If you would like, you can parse the payload from vROps. However, it is # probably easier to just pass the ALERTID as workflow input to a wrapper and # look up the alert from vRO. This gets around the problem of having to encode # the alert payload for vRO. # a = parse(request) payload = { "parameters": [{ "value": { "string": { "value": ALERTID } }, "type": "string", "name": "alertId", "scope": "local" }] } return callapi( "https://" + VROHOSTNAME + "/vco/api/workflows/" + WORKFLOWID + "/executions", 'post', json.dumps(payload), HEADERS, AUTH, VERIFY)
def zendesk(ALERTID=None, EMAIL=None, TOKEN=None): """ Create a new incident for every incoming webhook that does not already have an open incident. If an incident is already open, add a new comment to the open alert. Uniqueness is determined by the incoming webhook alert name. Requires ZENDESK* parameters to be defined. """ bauth = request.authorization if bauth is not None: global ZENDESKUSER global ZENDESKPASS ZENDESKUSER = bauth.username ZENDESKPASS = bauth.password if (not ZENDESKURL or (not ZENDESKUSER and not EMAIL) or (not ZENDESKPASS and not ZENDESKTOKEN and not TOKEN)): return ("ZENDESK* parameters must be set, please edit the shim!", 500, None) if not ZENDESKUSER: USER = EMAIL else: USER = ZENDESKUSER # Prefer tokens over passwords if ZENDESKTOKEN or TOKEN: if ZENDESKTOKEN: USER = USER + '/token' PASS = ZENDESKTOKEN else: USER = USER + '/token' PASS = TOKEN else: PASS = ZENDESKPASS headers = { 'Content-type': 'application/json', 'Accept': 'application/json' } a = parse(request) # Get the list of open incidents that contain the AlertName from the incoming webhook incident = callapi( ZENDESKURL + '/api/v2/search.json?query=type:ticket status:open subject:"' + a['AlertName'] + '"', 'get', None, headers, (USER, PASS)) i = json.loads(incident) try: # Determine if there is an open incident already if i['results'][0]['id'] is not None: # Option 1: Do nothing #logging.info('Nothing to do, exiting.') #return ("OK", 200, None) # Option 2: Add a new comment # Limited to 30 updates per 10 minutes (https://developer.zendesk.com/rest_api/docs/core/introduction) payload = {'ticket': {'comment': {'body': a['moreinfo']}}} return callapi( ZENDESKURL + '/api/v2/tickets/' + str(i['results'][0]['id']) + '.json', 'put', json.dumps(payload), headers, (USER, PASS)) except: # If no open incident then open one payload = { "ticket": { #"requester": { # "name": "Log Insight", # "email": USER #}, "subject": a['AlertName'], "comment": { "body": a['moreinfo'], }, "type": 'incident', "tags": ["loginsight"] } } return callapi(ZENDESKURL + '/api/v2/tickets.json', 'post', json.dumps(payload), headers, (USER, PASS))
def moogsoft(ALERTID=None): """ Information about this shim. Requires moogsoft* parameters to be defined. """ if (not moogsoftURL or (not moogsoftUSER) or (not moogsoftPASS) or (not vropsURL) or (not vropsUser) or (not vropsPass)): return ( "moogsoft* and vrops* parameters must be set, please edit the shim!", 500, None) a = parse(request) ######################################## #Map vROps crticiality to moog sev here ######################################## sevMap = { "ALERT_CRITICALITY_LEVEL_CRITICAL": 5, "ALERT_CRITICALITY_LEVEL_IMMEDIATE": 4, "ALERT_CRITICALITY_LEVEL_WARNING": 3, "ALERT_CRITICALITY_LEVEL_INFO": 2 } link = vropsURL + "/ui/index.action#/object/" + a[ 'resourceId'] + "/alertsAndSymptoms/alerts/" + ALERTID recommendation = recommendations(a['alertId']) resourceProperties = fetchResourceProperties(a['resourceId']) payload = { "signature": ALERTID, "source_id": a['resourceId'], "external_id": ALERTID, "manager": a['hookName'], "source": a['resourceName'] if (a['resourceName'] != "") else "undefined", "class": a['subType'], "agent": a['adapterKind'], "agent_location": "", "type": a['type'] + "::" + a['subType'], "severity": sevMap[a['criticality']] if (a['status'] != 'CANCELED') else 0, "description": a['AlertName'], "agent_time": a['updateDate'] / 1000, "recommendation": recommendation, "resource_properties": resourceProperties, "link": link } # Defaults to Content-type: application/json # If changed you must specify the content-type manually headers = { 'Content-type': 'application/json', 'Accept': 'application/json' } if not headers: headers = None return callapi(moogsoftURL, 'post', json.dumps(payload), headers, check=VERIFY)
def hipchat(NUMRESULTS=1, ALERTID=None, TEAM=None, ROOMNUM=None, AUTHTOKEN=None): """ Consume messages, and send them to HipChat as an Attachment object. If `TEAM/ROOMNUM/AUTHOKEN` is not passed, requires `HIPCHATURL` defined in the form `https://TEAM.hipchat.com/v2/room/0000000/notification?auth_token=XXXXXXXXXXXXXXXXXXXXXXXXXXX` For more information, see https://www.hipchat.com/docs/apiv2/method/send_room_notification """ # Prefer URL parameters to HIPCHATURL if AUTHTOKEN is not None: URL = 'https://' + TEAM + '.hipchat.com/v2/room/' + ROOMNUM + '/notification?auth_token=' + AUTHTOKEN elif not HIPCHATURL or not 'hipchat.com/v2/room' in HIPCHATURL: return ("HIPCHATURL parameter must be set properly, please edit the shim!", 500, None) else: URL = HIPCHATURL a = parse(request) hipchat_attachments = [] attachment_prefix = { "style": "application", "format": "medium", "id": "1", } attachment = { "icon": { "url": a['icon'] }, } try: # Log Insight if ('Messages' in a): for message in a['Messages'][:NUMRESULTS]: attachment.update(attachment_prefix) attachment.update({ "title": a['AlertName'], }) if (a['url'] != ""): attachment.update({ "url": a['url'], "activity": { "html": a['moreinfo'], } }) elif (message['text'] is not None): attachment.update({ "activity": { "html": message['text'], } }) if (message['fields'] is not None): message['fields'] = message['fields'] + a['fields'] attachment.update({ "attributes": [{ # start of dict comprehension "label": f['name'], "value": { "label": f['content'] } } for f in message['fields'] if not f['name'].startswith('__')], }) hipchat_attachments.append(attachment) attachment = {} # only attach the icon to the first card # Additional information (non-product specific) elif (a['fields'] is not None): attachment.update(attachment_prefix) attachment.update({ "title": a['AlertName'], "activity": { "html": a['moreinfo'], }, "attributes": [{ # start of dict comprehension "label": f['name'], "value": { "label": f['content'] } } for f in a['fields']], }) hipchat_attachments.append(attachment) except: logging.exception("Can't create new payload. Check code and try again.") raise if 'Messages' in a and not a['Messages']: # If a test alert attachment.update(attachment_prefix) attachment.update({ "title": "This is a test webhook alert", "attributes": [ { # start of dict comprehension "label": "Test", "value": { "label": "It works!" } } ], }) hipchat_attachments.append(attachment) payload = { "from": a['hookName'], "color": a['color'] if ('color' in a and a['color'] is not None) else "gray", "message_format": "text", "message": a['moreinfo'], "notify": 'true', "card": hipchat_attachments.pop(0), } return callapi(URL, 'post', json.dumps(payload))
def bugzilla(ALERTID=None, TOKEN=None, PRODUCT=None, COMPONENT=None, VERSION=None): """ Create a new bug for every incoming webhook that does not already have an open bug. If an bug is already open, add a new comment to the open bug. Uniqueness is determined by the incoming webhook alert name combined with bugzilla product and component. Requires BUGZILLA* parameters to be defined. You can pass an authentication token in the URL. For basic auth, pass `-` as the token. """ bauth = request.authorization if bauth is not None: global BUGZILLAUSER global BUGZILLAPASS BUGZILLAUSER = bauth.username BUGZILLAPASS = bauth.password if (not BUGZILLAURL or ((not BUGZILLAUSER or not BUGZILLAPASS) and (not TOKEN or TOKEN == '-')) or ((not BUGZILLAPRODUCT or not BUGZILLACOMPONENT or not BUGZILLAVERSION) and not VERSION)): logging.debug( "URL: %s\nUSER: %s\nPASS: %s\nTOKEN: %s\nPRODUCT: %s / %s\nCOMPONENT: %s / %s\nVERSION: %s / %s" % (BUGZILLAURL, BUGZILLAUSER, BUGZILLAPASS, TOKEN, BUGZILLAPRODUCT, PRODUCT, BUGZILLACOMPONENT, COMPONENT, BUGZILLAVERSION, VERSION)) return ("BUGZILLA* parameters must be set, please edit the shim!", 500, None) a = parse(request) if TOKEN and TOKEN != '-': auth = 'api_key=' + TOKEN else: auth = 'login='******'&password='******'Content-type': 'application/json', 'Accept': 'application/json' } # Get the list of open bugs that contain the AlertName from the incoming webhook bug = callapi( BUGZILLAURL + '/rest/bug?' + auth + '&product=' + PRODUCT + '&component=' + COMPONENT + '&summary=' + a['AlertName'], 'get', None, headers, None, VERIFY) try: i = json.loads(bug) except: return bug try: # Determine if there is an open bug already if i['bugs'][0]['id'] is not None: # Option 1: Do nothing #logging.info('Nothing to do, exiting.') #return ("OK", 200, None) # Option 2: Add a new comment payload = {'comment': {'body': a['moreinfo']}} return callapi( BUGZILLAURL + '/rest/bug/' + str(i['bugs'][0]['id']) + '?' + auth, 'put', json.dumps(payload), headers, None, VERIFY) except: # If no open bug then open one payload = { "product": PRODUCT, "component": COMPONENT, "version": VERSION, "summary": a['AlertName'], "description": a['info'], } # op_sys and rep_platform may not be needed, but are to test on landfill if "landfill.bugzilla.org" in BUGZILLAURL: payload.update({ "op_sys": "All", "rep_platform": "All", }) return callapi(BUGZILLAURL + '/rest/bug?' + auth, 'post', json.dumps(payload), headers, None, VERIFY)
def msteams(NUMRESULTS=10, ALERTID=None): """ Posts a message to a Microsoft Teams channel. Set TEAMSURL to the incoming webhook URL provided by Teams. To create a Teams webhook see: http://aka.ms/o365-connectors """ if not TEAMSURL: return ("The TEAMSURL parameter must be set, please edit the shim!", 500, None) a = parse(request) teams_attachments = [{ "activityImage": a['icon'], "activityTitle": a['AlertName'] if 'AlertName' in a else "Alert details", "activitySubtitle": "[Link to Alert](" + a['url'] + ")" if a['url'] else "", }] try: if ('AlertName' in a): if ('Messages' in a): for message in a['Messages'][:NUMRESULTS]: teams_attachments.append(teams_fields(message)) if (a['fields'] is not None): teams_attachments.append(teams_fields(a)) except: logging.exception( "Can't create new payload. Check code and try again.") raise if (a['color'] == 'red'): color = 'CC0000' elif (a['color'] == 'yellow'): color = 'FFCC00' elif (a['color'] == 'green'): color = '009000' else: color = '0075FF' payload = { "@type": "MessageCard", "@context": "http://schema.org/extensions", "themeColor": color, "summary": "Message from " + a['hookName'], "sections": teams_attachments } # Defaults to Content-type: application/json # If changed you must specify the content-type manually headers = { 'Content-type': 'application/json', 'Accept': 'application/json' } if not headers: headers = None return callapi(TEAMSURL, 'post', json.dumps(payload), headers)
def vro(WORKFLOWID=None, TOKEN=None, HOK=None, ALERTID=None): """ Start a vRealize Orchestrator workflow, passing the entire JSON alert as a base64-encoded string. The `WORKFLOWID` and optionally `TOKEN` is passed in the webhook URL. The workflow is responsible for parsing base64 -> json -> messages """ bauth = request.authorization if bauth is not None: global VROUSER global VROPASS VROUSER = bauth.username VROPASS = bauth.password if not WORKFLOWID: return ("WORKFLOWID must be set in the URL (e.g. /endpoint/vro/<WORKFLOWID>", 500, None) if not re.match('[a-z0-9-]+', WORKFLOWID, flags=re.IGNORECASE): return ("WORKFLOWID must consist of alphanumeric and dash characters only", 500, None) if not VROHOSTNAME: return("VROHOSTNAME parameter is not net, please edit the shim!", 500, None) if not USENETRC and (not VROUSER and not VROPASS and not VROTOKEN and not TOKEN and not VROHOK and not HOK): return ("VRO* authentication parameters must be set, please edit the shim!", 500, None) if TOKEN is None and VROTOKEN: TOKEN = VROTOKEN elif HOK is None and VROHOK: HOK = VROHOK AUTH = None HEADERS = None if TOKEN is not None: HEADERS = {"Authorization": TOKEN} elif HOK is not None: HEADERS = {"Authorization": "Bearer " + HOK} elif not USENETRC: AUTH = (VROUSER, VROPASS) if ALERTID is None: # LI payload a = parse(request) payload = { "parameters": [ { "value": { "string": { "value": base64.b64encode(request.get_data()) } }, "type": "string", "name": "messages", "scope": "local" }, { "value": { "string": { "value": a['AlertName'] } }, "type": "string", "name": "alertName", "scope": "local" }, { "value": { "number": { "value": a['NumHits'] } }, "type": "number", "name": "hitCount", "scope": "local" } ] } else: # vROps payload # If you would like, you can parse the payload from vROps. However, it is # probably easier to just pass the ALERTID as workflow input to a wrapper and # look up the alert from vRO. This gets around the problem of having to encode # the alert payload for vRO. # a = parse(request) payload = { "parameters": [ { "value": { "string": { "value": ALERTID } }, "type": "string", "name": "alertId", "scope": "local" } ] } return callapi("https://" + VROHOSTNAME + "/vco/api/workflows/" + WORKFLOWID + "/executions", 'post', json.dumps(payload), HEADERS, AUTH, VERIFY)
def hipchat(NUMRESULTS=1, ALERTID=None, TEAM=None, ROOMNUM=None, AUTHTOKEN=None): """ Consume messages, and send them to HipChat as an Attachment object. If `TEAM/ROOMNUM/AUTHOKEN` is not passed, requires `HIPCHATURL` defined in the form `https://TEAM.hipchat.com/v2/room/0000000/notification?auth_token=XXXXXXXXXXXXXXXXXXXXXXXXXXX` For more information, see https://www.hipchat.com/docs/apiv2/method/send_room_notification """ # Prefer URL parameters to HIPCHATURL if AUTHTOKEN is not None: URL = 'https://' + TEAM + '.hipchat.com/v2/room/' + ROOMNUM + '/notification?auth_token=' + AUTHTOKEN elif not HIPCHATURL or not 'hipchat.com/v2/room' in HIPCHATURL: return ( "HIPCHATURL parameter must be set properly, please edit the shim!", 500, None) else: URL = HIPCHATURL a = parse(request) hipchat_attachments = [] attachment_prefix = { "style": "application", "format": "medium", "id": "1", } attachment = { "icon": { "url": a['icon'] }, } try: # Log Insight if ('Messages' in a): for message in a['Messages'][:NUMRESULTS]: attachment.update(attachment_prefix) attachment.update({ "title": a['AlertName'], }) if (a['url'] != ""): attachment.update({ "url": a['url'], "activity": { "html": a['moreinfo'], } }) elif (message['text'] is not None): attachment.update( {"activity": { "html": message['text'], }}) if (message['fields'] is not None): message['fields'] = message['fields'] + a['fields'] attachment.update({ "attributes": [ { # start of dict comprehension "label": f['name'], "value": { "label": f['content'] } } for f in message['fields'] if not f['name'].startswith('__') ], }) hipchat_attachments.append(attachment) attachment = {} # only attach the icon to the first card # Additional information (non-product specific) elif (a['fields'] is not None): attachment.update(attachment_prefix) attachment.update({ "title": a['AlertName'], "activity": { "html": a['moreinfo'], }, "attributes": [ { # start of dict comprehension "label": f['name'], "value": { "label": f['content'] } } for f in a['fields'] ], }) hipchat_attachments.append(attachment) except: logging.exception( "Can't create new payload. Check code and try again.") raise if 'Messages' in a and not a['Messages']: # If a test alert attachment.update(attachment_prefix) attachment.update({ "title": "This is a test webhook alert", "attributes": [{ # start of dict comprehension "label": "Test", "value": { "label": "It works!" } }], }) hipchat_attachments.append(attachment) payload = { "from": a['hookName'], "color": a['color'] if ('color' in a and a['color'] is not None) else "gray", "message_format": "text", "message": a['moreinfo'], "notify": 'true', "card": hipchat_attachments.pop(0), } return callapi(URL, 'post', json.dumps(payload))