db_callback.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. #!/usr/bin/env python
  2. #
  3. # Copyright (c) 2017-2018 Joe Clarke <jclarke@cisco.com>
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions
  8. # are met:
  9. # 1. Redistributions of source code must retain the above copyright
  10. # notice, this list of conditions and the following disclaimer.
  11. # 2. Redistributions in binary form must reproduce the above copyright
  12. # notice, this list of conditions and the following disclaimer in the
  13. # documentation and/or other materials provided with the distribution.
  14. #
  15. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  16. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  18. # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  19. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  21. # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  22. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  23. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  24. # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  25. # SUCH DAMAGE.
  26. from flask import Flask
  27. from flask import jsonify
  28. from flask import request
  29. import sys
  30. import json
  31. from hashlib import sha1
  32. import hmac
  33. import time
  34. import cgi
  35. from sparker import Sparker
  36. import CLEUCreds
  37. SPARK_TEAM = 'CL18-Infra_team'
  38. SPARK_ROOM = 'proTACtive Alerts'
  39. MAX_DEVS = 10
  40. MSG_FORMAT = '''### {}\n
  41. **Severity:** {}\n
  42. **Details:** {}\n
  43. **Affects:** [{}]{}'''
  44. app = Flask('Diagnostic Bridge Spark Gateway')
  45. @app.route('/callback/dbridge', methods=['POST'])
  46. def dbridge():
  47. global SPARK_TEAM, SPARK_ROOM, MAX_DEVS, MSG_FORMAT, spark
  48. phash = None
  49. j = None
  50. try:
  51. phash = request.headers.get('X-DB-Hash')
  52. if phash is None:
  53. raise Exception('Unable to get hash')
  54. except Exception as e:
  55. resp = jsonify({'error': 'Unable to get payload hash'})
  56. resp.status_code = 403
  57. sys.stderr.write('Unable to get payload hash\n')
  58. return resp
  59. req = request.get_data()
  60. hashed = hmac.new(CLEUCreds.PROTACTIVE_KEY, req, sha1)
  61. if hashed.digest().encode('hex').lower() != phash.lower():
  62. resp = jsonify({'error': 'Unauthorized payload'})
  63. resp.status_code = 403
  64. sys.stderr.write('Unauthorized payload\n')
  65. return resp
  66. content_type = request.headers.get('Content-Type')
  67. mimetype, ctoptions = cgi.parse_header(content_type)
  68. if mimetype != 'application/json':
  69. resp = jsonify({'error': 'Content-Type must be application/json'})
  70. resp.status_code = 400
  71. sys.stderr.write(
  72. 'Content-Type was not application/json (was {})\n'.format(content_type))
  73. return resp
  74. sys.stderr.write('Got "{}"\n'.format(req))
  75. try:
  76. j = json.loads(req)
  77. except Exception as e:
  78. resp = jsonify({'error': 'Failed to decode JSON: {}'.format(e)})
  79. resp.status_code = 400
  80. sys.stderr.write('Failed to decode JSON: {}\n'.format(e))
  81. return resp
  82. for problem in j:
  83. title = problem['summary']
  84. desc = problem['details']
  85. num_devs = len(problem['instances']) if 'instances' in problem else 0
  86. if num_devs == 0:
  87. continue
  88. extra_msg = ''
  89. devs = []
  90. for i in range(0, num_devs):
  91. devs.append(problem['instances'][i]['hostname'])
  92. if i == MAX_DEVS:
  93. break
  94. if num_devs > MAX_DEVS:
  95. extra_msg = ' and {} other device(s)'.format(
  96. str(num_devs - MAX_DEVS))
  97. sres = spark.post_to_spark(SPARK_TEAM, SPARK_ROOM, MSG_FORMAT.format(
  98. title, problem['severity'], desc, ', '.join(devs), extra_msg))
  99. if not sres:
  100. resp = jsonify({'error': 'Error posting to Spark; see log'})
  101. resp.status_code = 500
  102. return resp
  103. time.sleep(1)
  104. resp = jsonify({'success': 'Posted to Spark successfully'})
  105. resp.status_code = 200
  106. return resp
  107. if __name__ == '__main__':
  108. spark = Sparker()
  109. app.run(host='10.100.253.13', port=8080, threaded=True)