Completed
Pull Request — master (#396)
by
unknown
01:52
created

_get_auth_token()   B

Complexity

Conditions 4

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 4
dl 0
loc 26
rs 8.5806
1
#!/usr/bin/env python
2
3
import argparse
4
import base64
5
import httplib
6
try:
7
    import simplejson as json
8
except ImportError:
9
    import json
10
import os
11
import sys
12
import traceback
13
from urlparse import urljoin
14
15
try:
16
    import requests
17
    requests.packages.urllib3.disable_warnings()
18
except ImportError:
19
    raise ImportError('Missing dependency "requests". \
20
        Do ``pip install requests``.')
21
22
try:
23
    import yaml
24
except ImportError:
25
    raise ImportError('Missing dependency "pyyaml". \
26
        Do ``pip install pyyaml``.')
27
28
# ST2 configuration
29
30
ST2_API_BASE_URL = 'https://localhost/v1/'
31
ST2_USERNAME = None
32
ST2_PASSWORD = None
33
ST2_AUTH_TOKEN = None
34
35
ST2_AUTH_PATH = 'auth/tokens'
36
ST2_WEBHOOKS_PATH = 'api/webhooks/st2/'
37
ST2_TRIGGERS_PATH = 'api/triggertypes/'
38
ST2_TRIGGERTYPE_PACK = 'sensu'
39
ST2_TRIGGERTYPE_NAME = 'event_handler'
40
ST2_TRIGGERTYPE_REF = '.'.join([ST2_TRIGGERTYPE_PACK, ST2_TRIGGERTYPE_NAME])
41
42
# Sensu configuration
43
44
SENSU_HOST = 'localhost'
45
SENSU_PORT = 4567
46
SENSU_USER = ''
47
SENSU_PASS = ''
48
49
REGISTERED_WITH_ST2 = False
50
UNAUTHED = False
51
52
OK_CODES = [httplib.OK, httplib.CREATED, httplib.ACCEPTED, httplib.CONFLICT]
53
UNREACHABLE_CODES = [httplib.NOT_FOUND]
54
55
56
def _get_headers():
57
    b64auth = base64.b64encode(
58
        "%s:%s" %
59
        (SENSU_USER, SENSU_PASS))
60
    auth_header = "BASIC %s" % b64auth
61
    content_header = "application/json"
62
    return {"Authorization": auth_header, "Content-Type": content_header}
63
64
65
def _check_stash(client, check):
66
    sensu_api = "http://%s:%i" % (SENSU_HOST, SENSU_PORT)
67
    endpoints = [
68
        "silence/%s" % client,
69
        "silence/%s/%s" % (client, check),
70
        "silence/all/%s" % check]
71
72
    for endpoint in endpoints:
73
        url = "%s/stashes/%s" % (sensu_api, endpoint)
74
        response = requests.get(url, headers=_get_headers())
75
        # print "%s %s" % (url, str(response.status_code))
76
        if response.status_code == 200:
77
            print "Check or client is stashed"
78
            sys.exit(0)
79
80
81
def _create_trigger_type(verbose=False):
82
    try:
83
        url = _get_st2_triggers_url()
84
        payload = {
85
            'name': ST2_TRIGGERTYPE_NAME,
86
            'pack': ST2_TRIGGERTYPE_PACK,
87
            'description': 'Trigger type for sensu event handler.'
88
        }
89
90
        headers = {}
91
        headers['Content-Type'] = 'application/json; charset=utf-8'
92
93
        if ST2_AUTH_TOKEN:
94
            headers['X-Auth-Token'] = ST2_AUTH_TOKEN
95
96
        if verbose:
97
            print('POST to URL %s for registering trigger. Body = %s, headers = %s.' %
98
                  (url, payload, headers))
99
        post_resp = requests.post(url, data=json.dumps(payload),
100
                                  headers=headers, verify=False)
101
    except:
102
        traceback.print_exc(limit=20)
103
        raise Exception('Unable to connect to st2 endpoint %s.' % url)
104
    else:
105
        status = post_resp.status_code
106
        if status in UNREACHABLE_CODES:
107
            msg = 'Got response %s. Invalid triggers endpoint %s. Check configuration!' % (
108
                status,
109
                url
110
            )
111
            raise Exception(msg)
112
113
        if status not in OK_CODES:
114
            msg = 'Failed to register trigger type %s.%s with st2. HTTP_CODE: %s' % (
115
                ST2_TRIGGERTYPE_PACK, ST2_TRIGGERTYPE_NAME, status
116
            )
117
            raise Exception(msg)
118
        else:
119
            print('Registered trigger type with st2.')
120
121
122
def _get_auth_url():
123
    return urljoin(ST2_API_BASE_URL, ST2_AUTH_PATH)
124
125
126
def _get_auth_token(verbose=False):
127
    auth_url = _get_auth_url()
128
129
    if verbose:
130
        print('Will POST to URL %s to get auth token.' % auth_url)
131
132
    try:
133
        resp = requests.post(auth_url, json.dumps({'ttl': 5 * 60}),
134
                             auth=(ST2_USERNAME, ST2_PASSWORD), verify=False)
135
    except:
136
        traceback.print_exc(limit=20)
137
        raise Exception('Unable to connect to st2 endpoint %s.' % auth_url)
138
    else:
139
        if resp.status_code in UNREACHABLE_CODES:
140
            msg = 'Got response %s. Invalid auth endpoint %s. Check configuration!' % (
141
                resp.status_code,
142
                auth_url
143
            )
144
            raise Exception(msg)
145
        if resp.status_code not in OK_CODES:
146
            msg = 'Cannot get a valid auth token from %s. HTTP_CODE: %s' % (
147
                auth_url,
148
                resp.status_code
149
            )
150
            raise Exception(msg)
151
        return resp.json()['token']
152
153
154
def _register_trigger_with_st2(verbose=False):
155
    global REGISTERED_WITH_ST2
156
    global ST2_AUTH_TOKEN
157
    triggers_url = urljoin(_get_st2_triggers_url(), ST2_TRIGGERTYPE_REF)
158
159
    if verbose:
160
        print('Unauthed? : %s' % UNAUTHED)
161
162
    if not UNAUTHED:
163
        try:
164
            if not ST2_AUTH_TOKEN:
165
                if verbose:
166
                    print('No auth token found. Let\'s get one from StackStorm!')
167
                ST2_AUTH_TOKEN = _get_auth_token(verbose=verbose)
168
        except:
169
            raise Exception('Unable to negotiate an auth token. Exiting!')
170
171
    try:
172
        if verbose:
173
            print('Will GET from URL %s for detecting trigger %s.' % (
174
                  triggers_url, ST2_TRIGGERTYPE_REF))
175
176
        if ST2_AUTH_TOKEN:
177
            get_resp = requests.get(triggers_url, headers={'X-Auth-Token':
178
                                    ST2_AUTH_TOKEN}, verify=False)
179
        else:
180
            if verbose:
181
                print('Resorting to unauthed requests to register trigger type.')
182
            get_resp = requests.get(triggers_url, verify=False)
183
184
        if get_resp.status_code != httplib.OK:
185
            _create_trigger_type(verbose=verbose)
186
        else:
187
            body = json.loads(get_resp.text)
188
            if len(body) == 0:
189
                _create_trigger_type(verbose=verbose)
190
    except:
191
        traceback.print_exc(limit=20)
192
        raise Exception('Unable to connect to st2 endpoint %s.' % triggers_url)
193
    else:
194
        if verbose:
195
            print('Successfully registered trigger %s with st2.' % ST2_TRIGGERTYPE_REF)
196
197
        REGISTERED_WITH_ST2 = True
198
199
200
def _get_st2_triggers_url():
201
    url = urljoin(ST2_API_BASE_URL, ST2_TRIGGERS_PATH)
202
    return url
203
204
205
def _get_st2_webhooks_url():
206
    url = urljoin(ST2_API_BASE_URL, ST2_WEBHOOKS_PATH)
207
    return url
208
209
210
def _post_webhook(url, body, verbose):
211
    headers = {}
212
    headers['X-ST2-Integration'] = 'sensu.'
213
    headers['St2-Trace-Tag'] = body['payload']['id']
214
    headers['Content-Type'] = 'application/json; charset=utf-8'
215
    if ST2_AUTH_TOKEN:
216
        headers['X-Auth-Token'] = ST2_AUTH_TOKEN
217
    try:
218
        if verbose:
219
            print('Webhook POST: url: %s, body: %s\n' % (url, body))
220
        r = requests.post(url, data=json.dumps(body), headers=headers, verify=False)
221
    except:
222
        raise Exception('Cannot connect to st2 endpoint %s.' % url)
223
    else:
224
        status = r.status_code
225
226
        if status in UNREACHABLE_CODES:
227
            msg = 'Webhook URL %s does not exist. Check if you have a rule registered for ' + \
228
                  'trigger with st2. st2 rule list --trigger=%s' % ST2_TRIGGERTYPE_REF
229
            raise Exception(msg)
230
231
        if status not in OK_CODES:
232
            sys.stderr.write('Failed posting sensu event to st2. HTTP_CODE: \
233
                %d\n' % status)
234
        else:
235
            sys.stdout.write('Sent sensu event to st2. HTTP_CODE: \
236
                %d\n' % status)
237
238
239
def _post_event_to_st2(payload, verbose=False):
240
    body = {}
241
    body['trigger'] = ST2_TRIGGERTYPE_REF
242
243
    try:
244
        body['payload'] = json.loads(payload.strip())
245
    except:
246
        print('Invalid JSON payload %s.' % payload)
247
        sys.exit(3)
248
249
    try:
250
        client = body['payload']['client']['name']
251
        check = body['payload']['check']['name']
252
    except KeyError:
253
        print('Invalid payload spec %s.' % payload)
254
255
    if not _check_stash(client, check):
256
        try:
257
            _post_event_to_st2(_get_st2_webhooks_url(), body, verbose=verbose)
258
        except:
259
            traceback.print_exc(limit=20)
260
            print('Cannot send event to st2.')
261
            sys.exit(4)
262
263
264
def _register_with_st2(verbose=False):
265
    try:
266
        if not REGISTERED_WITH_ST2:
267
            if verbose:
268
                print('Checking if trigger %s registered with st2.' % ST2_TRIGGERTYPE_REF)
269
            _register_trigger_with_st2(verbose=verbose)
270
    except:
271
        traceback.print_exc(limit=20)
272
        sys.stderr.write(
273
            'Failed registering with st2. Won\'t post event.\n')
274
        sys.exit(2)
275
276
277
def _set_config_opts(config_file, verbose=False, unauthed=False):
278
    global ST2_USERNAME
279
    global ST2_PASSWORD
280
    global ST2_API_BASE_URL
281
    global SENSU_HOST
282
    global SENSU_PORT
283
    global SENSU_USER
284
    global SENSU_PASS
285
    global UNAUTHED
286
287
    UNAUTHED = unauthed
288
289
    if not os.path.exists(config_file):
290
        print('Configuration file %s not found. Exiting!!!' % config_file)
291
        sys.exit(1)
292
293
    with open(config_file) as f:
294
        config = yaml.safe_load(f)
295
296
        if verbose:
297
            print('Contents of config file: %s' % config)
298
299
        ST2_USERNAME = config['st2_username']
300
        ST2_PASSWORD = config['st2_password']
301
        ST2_API_BASE_URL = config['st2_api_base_url']
302
        SENSU_HOST = config.get('sensu_host', 'localhost')
303
        SENSU_PORT = config.get('sensu_port', '4567')
304
        SENSU_USER = config.get('sensu_user', None)
305
        SENSU_PASS = config.get('sensu_pass', None)
306
307
308
def main(config_file, payload, verbose=False, unauthed=False):
309
    _set_config_opts(config_file=config_file, unauthed=unauthed, verbose=verbose)
310
    _register_with_st2(verbose=verbose)
311
    _post_event_to_st2()
312
313
314
if __name__ == '__main__':
315
    parser = argparse.ArgumentParser(description='StackStorm sensu event handler.')
316
    parser.add_argument('config_path',
317
                        help='Exchange to listen on')
318
    parser.add_argument('--verbose', '-v', required=False, action='store_true',
319
                        help='Verbose mode.')
320
    parser.add_argument('--unauthed', '-u', required=False, action='store_true',
321
                        help='Allow to post to unauthed st2. E.g. when auth is disabled ' +
322
                        'server side.')
323
    args = parser.parse_args()
324
    payload = sys.stdin.read().strip()
325
    main(config_file=args.config_path, payload=payload, verbose=args.verbose,
326
         unauthed=args.unauthed)
327