diff --git a/hooker/__init__.py b/hooker/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3102dc6fe538cc3c161a7e015083792f84cbf565 --- /dev/null +++ b/hooker/__init__.py @@ -0,0 +1,24 @@ +import logging +import sys + + +def compare(tokens, auth_header): + + major = sys.version_info.major + minor = sys.version_info.minor + micro = sys.version_info.micro + for token in tokens.split(';'): + if (major == 2 and minor <= 7 and micro < 7) \ + or (major == 3 and minor < 3): + logging.debug('Using own compare function for authorization token') + valid = True + if len(token) != len(auth_header): + return False + for iterator, item in enumerate(token): + if item != auth_header[iterator]: + valid = False + return valid + else: + logging.debug('Using hmac compare function for authorization token') + import hmac + return hmac.compare_digest(unicode(token), auth_header) diff --git a/hooker/github.py b/hooker/github.py new file mode 100644 index 0000000000000000000000000000000000000000..fee26d47725ba7142f61df40f94bd4ed62a70a3d --- /dev/null +++ b/hooker/github.py @@ -0,0 +1,5 @@ + + +def authenticate(tokens, request): + #TODO: check headers/payload and implement it for github + return True diff --git a/hooker/gitlab.py b/hooker/gitlab.py new file mode 100644 index 0000000000000000000000000000000000000000..94df6c954223fd845b43cc2139a0b4bf2b2365a7 --- /dev/null +++ b/hooker/gitlab.py @@ -0,0 +1,23 @@ +import logging +from hooker import compare + + +def authenticate(tokens, request): + auth_header = request.headers.get('X-Gitlab-Token') + return compare(tokens, auth_header) if auth_header else False + + +def assess_reload(request): + webhook_action = request.headers.get('X-Gitlab-Event') + if webhook_action == u'Build Hook': + payload = request.get_json() + if payload and payload['build_status'] == 'success': + logging.debug('Got successfull build_status from gitlab, reload') + return True + else: + return False + elif webhook_action == u'Push Hook': + logging.debug('Got push from gitlab, reload') + return True + else: + return False diff --git a/hooker/travis.py b/hooker/travis.py new file mode 100644 index 0000000000000000000000000000000000000000..4b0ed0e94c6ea603785e5f2d2c617812f9ecb89c --- /dev/null +++ b/hooker/travis.py @@ -0,0 +1,7 @@ +from hooker import compare + + +def authenticate(tokens, request): + auth_header = request.headers.get('Authorization') + + return compare(tokens, auth_header) if auth_header else False diff --git a/requirements.txt b/requirements.txt index 332a4a7a3accc4dd9f3b0714eabe4dfed9b9c38d..063bed0dfc0db29747c87ef5095cb714e1612650 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -Flask==0.10.1 -Flask-API==0.6.3 -python-logstash==0.4.5 -Flask-Bootstrap==3.3.5.6 -Flask-SQLAlchemy==2.0 \ No newline at end of file +Flask==0.11.1 +Flask-API==0.6.6.post1 +python-logstash==0.4.6 +Flask-Bootstrap==3.3.6.0 +Flask-SQLAlchemy==2.1 \ No newline at end of file diff --git a/run.py b/run.py index da0d25d07cfb0485ef88b3ef9944c465d830c0ab..17bd46db7073b2d2cc95e7c1bb4f649fe17be230 100644 --- a/run.py +++ b/run.py @@ -10,6 +10,7 @@ from flask import Flask, render_template, request,\ send_from_directory as send_file from flask_bootstrap import Bootstrap from flask_sqlalchemy import SQLAlchemy +from pydoc import locate app = Flask(__name__) @@ -18,20 +19,7 @@ Bootstrap(app) db = SQLAlchemy(app) command = '' -token = False - - -def _compare(token, auth_header): - logging.debug('Using own compare function for authorization token') - # not very performant, but posts are not very often - # and this code is not used in new python versions. - valid = True - if len(token) != len(auth_header): - return False - for iterator, item in enumerate(token): - if item != auth_header[iterator]: - valid = False - return valid +tokens = False class WebhookCall(db.Model): @@ -83,19 +71,13 @@ def _restart(): @app.route('/', methods=['POST']) @app.route('/<hooking_repository>/', methods=['POST']) def post_hook(hooking_repository=None): - global token - auth_header = request.headers.get('Authorization') - if not token: - pass - elif auth_header: - valid = False - for item in token.split(';'): - if compare_func(unicode(item), auth_header): - valid = True - if not valid: + global tokens + if tokens: + if not authenticate(tokens, request): return 'Access forbidden', 403 - else: - return 'Access forbidden', 403 + + for item in request.headers: + logging.debug(item) if hooking_repository: if len(hooking_repository) < 20: repository = hooking_repository @@ -103,26 +85,31 @@ def post_hook(hooking_repository=None): repository = hooking_repository[0:20] else: repository = 'unknown' - current_time = int(time.time()) - output = _restart() - success = False if 'Error' in output else True - output = output.strip() - item = WebhookCall.query.filter_by(timestamp=current_time).first() - if not item: - db.session.add(WebhookCall(current_time, repository, success)) - db.session.add(WebhookCallResult(timestamp=current_time, - output=output)) - else: - db.session.add(WebhookCallResult(timestamp=current_time, - output=output)) - db.session.commit() - - response = 'Executed restart action at {} with output:\n{}'\ - .format(format_datetime(current_time), output) - if success: - return response, 200 + + if assess_reload(request): + current_time = int(time.time()) + output = _restart() + success = False if output.startswith('Error') else True + output = output.strip() + item = WebhookCall.query.filter_by(timestamp=current_time).first() + if not item: + db.session.add(WebhookCall(current_time, repository, success)) + db.session.add(WebhookCallResult(timestamp=current_time, + output=output)) + else: + db.session.add(WebhookCallResult(timestamp=current_time, + output=output)) + db.session.commit() + + response = 'Executed restart action at {} with output:\n{}'\ + .format(format_datetime(current_time), output) + + if success: + return response, 201 + else: + return response, 500 else: - return response, 500 + return 'OK', 200 @app.route('/', methods=['GET']) @@ -131,6 +118,7 @@ def get_logs(): return render_template('logs.html', content=WebhookCall.query.all()) +@app.route('/<log_id>/', methods=['GET']) @app.route('/logs/<log_id>', methods=['GET']) def get_log(log_id): logs = WebhookCallResult.query.filter( @@ -162,10 +150,12 @@ if __name__ == '__main__': parser.add_argument("--logstash", help="log everything (in addition) to logstash " ", give host:port") - parser.add_argument("--token", - help="use token for authenticating a remote service") + parser.add_argument("--tokens", + help="use tokens for authenticating a remote service") parser.add_argument("--port", help="port to use for listening") + parser.add_argument("--hooker", + help="which service is hooking? github,gitlab,travis") args = parser.parse_args() if args.debug: @@ -186,20 +176,20 @@ if __name__ == '__main__': except ImportError as err: logging.error('Logstash module not available %s', err) - if args.token: - major = sys.version_info.major - minor = sys.version_info.minor - micro = sys.version_info.micro - if (major == 2 and minor <= 7 and micro < 7)\ - or (major == 3 and minor < 3): - compare_func = _compare - else: - import hmac - compare_func = hmac.compare_digest - - global token - token = args.token - + if args.hooker: + try: + global authenticate + authenticate = locate('hooker.{}.authenticate'.format(args.hooker)) + global assess_reload + assess_reload = locate('hooker.{}.assess_reload'.format(args.hooker)) + except ImportError as err: + logging.error('Logstash module not available %s', err) + else: + logging.error('--hooker is required, gitlab/github/travis') + exit(1) + if args.tokens: + global tokens + tokens = args.tokens if args.command: global command command = args.command diff --git a/templates/log.html b/templates/log.html index b436006b8571c8b2f16dd51c9bdfbc05b9546492..cf081ff95c952349b6ea6096d67aacf8aaa9d25a 100644 --- a/templates/log.html +++ b/templates/log.html @@ -9,5 +9,5 @@ {{ log.output|e }} </pre> {% endfor %} - <script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js?lang=sh"></script> + <script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?lang=sh"></script> {% endblock %}