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, verbose=False):
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
75
        if verbose:
76
            print('Getting sensu stash info from URL: %s' % url)
77
78
        try:
79
            response = requests.get(url, headers=_get_headers())
80
        except requests.exceptions.ConnectionError:
81
            traceback.print_exc(limit=20)
82
            msg = 'Couldn\'t connect to sensu to get stash info. Is sensu running on %s:%s?' % (
83
                SENSU_HOST, SENSU_PORT
84
            )
85
            raise Exception(msg)
86
87
        if verbose:
88
            print('Sensu response code: %s.' % response.status_code)
89
90
        if response.status_code == 200:
91
            print "Check or client is stashed"
92
            sys.exit(0)
93
94
95
def _create_trigger_type(verbose=False):
96
    try:
97
        url = _get_st2_triggers_url()
98
        payload = {
99
            'name': ST2_TRIGGERTYPE_NAME,
100
            'pack': ST2_TRIGGERTYPE_PACK,
101
            'description': 'Trigger type for sensu event handler.'
102
        }
103
104
        headers = {}
105
        headers['Content-Type'] = 'application/json; charset=utf-8'
106
107
        if ST2_AUTH_TOKEN:
108
            headers['X-Auth-Token'] = ST2_AUTH_TOKEN
109
110
        if verbose:
111
            print('POST to URL %s for registering trigger. Body = %s, headers = %s.' %
112
                  (url, payload, headers))
113
        post_resp = requests.post(url, data=json.dumps(payload),
114
                                  headers=headers, verify=False)
115
    except:
116
        traceback.print_exc(limit=20)
117
        raise Exception('Unable to connect to st2 endpoint %s.' % url)
118
    else:
119
        status = post_resp.status_code
120
        if status in UNREACHABLE_CODES:
121
            msg = 'Got response %s. Invalid triggers endpoint %s. Check configuration!' % (
122
                status,
123
                url
124
            )
125
            raise Exception(msg)
126
127
        if status not in OK_CODES:
128
            msg = 'Failed to register trigger type %s.%s with st2. HTTP_CODE: %s' % (
129
                ST2_TRIGGERTYPE_PACK, ST2_TRIGGERTYPE_NAME, status
130
            )
131
            raise Exception(msg)
132
        else:
133
            print('Registered trigger type with st2.')
134
135
136
def _get_auth_url():
137
    return urljoin(ST2_API_BASE_URL, ST2_AUTH_PATH)
138
139
140
def _get_auth_token(verbose=False):
141
    auth_url = _get_auth_url()
142
143
    if verbose:
144
        print('Will POST to URL %s to get auth token.' % auth_url)
145
146
    try:
147
        resp = requests.post(auth_url, json.dumps({'ttl': 5 * 60}),
148
                             auth=(ST2_USERNAME, ST2_PASSWORD), verify=False)
149
    except:
150
        traceback.print_exc(limit=20)
151
        raise Exception('Unable to connect to st2 endpoint %s.' % auth_url)
152
    else:
153
        if resp.status_code in UNREACHABLE_CODES:
154
            msg = 'Got response %s. Invalid auth endpoint %s. Check configuration!' % (
155
                resp.status_code,
156
                auth_url
157
            )
158
            raise Exception(msg)
159
        if resp.status_code not in OK_CODES:
160
            msg = 'Cannot get a valid auth token from %s. HTTP_CODE: %s' % (
161
                auth_url,
162
                resp.status_code
163
            )
164
            raise Exception(msg)
165
        return resp.json()['token']
166
167
168
def _register_trigger_with_st2(verbose=False):
169
    global REGISTERED_WITH_ST2
170
    global ST2_AUTH_TOKEN
171
    triggers_url = urljoin(_get_st2_triggers_url(), ST2_TRIGGERTYPE_REF)
172
173
    if verbose:
174
        print('Unauthed? : %s' % UNAUTHED)
175
176
    if not UNAUTHED:
177
        try:
178
            if not ST2_AUTH_TOKEN:
179
                if verbose:
180
                    print('No auth token found. Let\'s get one from StackStorm!')
181
                ST2_AUTH_TOKEN = _get_auth_token(verbose=verbose)
182
        except:
183
            raise Exception('Unable to negotiate an auth token. Exiting!')
184
185
    try:
186
        if verbose:
187
            print('Will GET from URL %s for detecting trigger %s.' % (
188
                  triggers_url, ST2_TRIGGERTYPE_REF))
189
190
        if ST2_AUTH_TOKEN:
191
            get_resp = requests.get(triggers_url, headers={'X-Auth-Token':
192
                                    ST2_AUTH_TOKEN}, verify=False)
193
        else:
194
            if verbose:
195
                print('Resorting to unauthed requests to register trigger type.')
196
            get_resp = requests.get(triggers_url, verify=False)
197
198
        if get_resp.status_code != httplib.OK:
199
            _create_trigger_type(verbose=verbose)
200
        else:
201
            body = json.loads(get_resp.text)
202
            if len(body) == 0:
203
                _create_trigger_type(verbose=verbose)
204
    except:
205
        traceback.print_exc(limit=20)
206
        raise Exception('Unable to connect to st2 endpoint %s.' % triggers_url)
207
    else:
208
        if verbose:
209
            print('Successfully registered trigger %s with st2.' % ST2_TRIGGERTYPE_REF)
210
211
        REGISTERED_WITH_ST2 = True
212
213
214
def _get_st2_triggers_url():
215
    url = urljoin(ST2_API_BASE_URL, ST2_TRIGGERS_PATH)
216
    return url
217
218
219
def _get_st2_webhooks_url():
220
    url = urljoin(ST2_API_BASE_URL, ST2_WEBHOOKS_PATH)
221
    return url
222
223
224
def _post_webhook(url, body, verbose=False):
225
    headers = {}
226
    headers['X-ST2-Integration'] = 'sensu.'
227
    headers['St2-Trace-Tag'] = body['payload']['id']
228
    headers['Content-Type'] = 'application/json; charset=utf-8'
229
    if ST2_AUTH_TOKEN:
230
        headers['X-Auth-Token'] = ST2_AUTH_TOKEN
231
    try:
232
        if verbose:
233
            print('Webhook POST: url: %s, body: %s\n' % (url, body))
234
        r = requests.post(url, data=json.dumps(body), headers=headers, verify=False)
235
    except:
236
        raise Exception('Cannot connect to st2 endpoint %s.' % url)
237
    else:
238
        status = r.status_code
239
240
        if status in UNREACHABLE_CODES:
241
            msg = 'Webhook URL %s does not exist. Check StackStorm installation!'
242
            raise Exception(msg)
243
244
        if status not in OK_CODES:
245
            sys.stderr.write('Failed posting sensu event to st2. HTTP_CODE: \
246
                %d\n' % status)
247
        else:
248
            sys.stdout.write('Sent sensu event to st2. HTTP_CODE: \
249
                %d\n' % status)
250
251
252
def _post_event_to_st2(payload, verbose=False):
253
    body = {}
254
    body['trigger'] = ST2_TRIGGERTYPE_REF
255
256
    try:
257
        body['payload'] = json.loads(payload)
258
    except:
259
        print('Invalid JSON payload %s.' % payload)
260
        sys.exit(3)
261
262
    try:
263
        client = body['payload']['client']['name']
264
        check = body['payload']['check']['name']
265
    except KeyError:
266
        print('Invalid payload spec %s.' % payload)
267
268
    if not _check_stash(client, check, verbose=verbose):
269
        try:
270
            _post_webhook(url=_get_st2_webhooks_url(), body=body, verbose=verbose)
271
        except:
272
            traceback.print_exc(limit=20)
273
            print('Cannot send event to st2.')
274
            sys.exit(4)
275
276
277
def _register_with_st2(verbose=False):
278
    try:
279
        if not REGISTERED_WITH_ST2:
280
            if verbose:
281
                print('Checking if trigger %s registered with st2.' % ST2_TRIGGERTYPE_REF)
282
            _register_trigger_with_st2(verbose=verbose)
283
    except:
284
        traceback.print_exc(limit=20)
285
        sys.stderr.write(
286
            'Failed registering with st2. Won\'t post event.\n')
287
        sys.exit(2)
288
289
290
def _set_config_opts(config_file, verbose=False, unauthed=False):
291
    global ST2_USERNAME
292
    global ST2_PASSWORD
293
    global ST2_API_BASE_URL
294
    global SENSU_HOST
295
    global SENSU_PORT
296
    global SENSU_USER
297
    global SENSU_PASS
298
    global UNAUTHED
299
300
    UNAUTHED = unauthed
301
302
    if not os.path.exists(config_file):
303
        print('Configuration file %s not found. Exiting!!!' % config_file)
304
        sys.exit(1)
305
306
    with open(config_file) as f:
307
        config = yaml.safe_load(f)
308
309
        if verbose:
310
            print('Contents of config file: %s' % config)
311
312
        ST2_USERNAME = config['st2_username']
313
        ST2_PASSWORD = config['st2_password']
314
        ST2_API_BASE_URL = config['st2_api_base_url']
315
        SENSU_HOST = config.get('sensu_host', 'localhost')
316
        SENSU_PORT = config.get('sensu_port', '4567')
317
        SENSU_USER = config.get('sensu_user', None)
318
        SENSU_PASS = config.get('sensu_pass', None)
319
320
321
def main(config_file, payload, verbose=False, unauthed=False):
322
    _set_config_opts(config_file=config_file, unauthed=unauthed, verbose=verbose)
323
    _register_with_st2(verbose=verbose)
324
    _post_event_to_st2(payload, verbose=verbose)
325
326
327
if __name__ == '__main__':
328
    parser = argparse.ArgumentParser(description='StackStorm sensu event handler.')
329
    parser.add_argument('config_path',
330
                        help='Exchange to listen on')
331
    parser.add_argument('--verbose', '-v', required=False, action='store_true',
332
                        help='Verbose mode.')
333
    parser.add_argument('--unauthed', '-u', required=False, action='store_true',
334
                        help='Allow to post to unauthed st2. E.g. when auth is disabled ' +
335
                        'server side.')
336
    args = parser.parse_args()
337
    payload = sys.stdin.read().strip()
338
    main(config_file=args.config_path, payload=payload, verbose=args.verbose,
339
         unauthed=args.unauthed)
340