diff --git a/cfg.sample.toml b/cfg.sample.toml index 43fc1cd6..68048754 100644 --- a/cfg.sample.toml +++ b/cfg.sample.toml @@ -21,7 +21,8 @@ app_client_secret = "" #""" [web] - +# The ip homu used +host = "0.0.0.0" # The port homu listens on port = 54856 @@ -74,6 +75,14 @@ secret = "" ## found under . #token = "" +# found travis api document +# https://docs.travis-ci.com/user/notifications/ +# Make sure you use the correct config URL, the .org and .com +# have different keys! +# https://api.travis-ci.org/config +# https://api.travis-ci.com/config +#public_key_url="https://api.travis-ci.org/config" + ## Use the Status API #[repo.NAME.status] # diff --git a/homu/server.py b/homu/server.py index 25f06fa3..f0319563 100644 --- a/homu/server.py +++ b/homu/server.py @@ -565,18 +565,30 @@ def travis(): repo_cfg = g.repo_cfgs[repo_label] token = repo_cfg['travis']['token'] - auth_header = request.headers['Authorization'] - code = hashlib.sha256(('{}/{}{}'.format(state.owner, state.name, token)).encode('utf-8')).hexdigest() - if auth_header != code: - # this isn't necessarily an error, e.g. maybe someone is - # fabricating travis notifications to try to trick Homu, but, - # I imagine that this will most often occur because a repo is - # misconfigured. - logger.warn('authorization failed for {}, maybe the repo has the wrong travis token? ' \ - 'header = {}, computed = {}' - .format(state, auth_header, code)) - abort(400, 'Authorization failed') - + lazy_debug(logger, lambda:'request header details {}'.format(request.headers)) + if 'Authorization' in request.headers.keys(): + auth_header = request.headers['Authorization'] + code = hashlib.sha256(('{}/{}{}'.format(state.owner, state.name, token)).encode('utf-8')).hexdigest() + if auth_header != code: + # this isn't necessarily an error, e.g. maybe someone is + # fabricating travis notifications to try to trick Homu, but, + # I imagine that this will most often occur because a repo is + # misconfigured. + logger.warn('authorization failed for {}, maybe the repo has the wrong travis token? ' \ + 'header = {}, computed = {}' + .format(state, auth_header, code)) + abort(400, 'Authorization failed') + elif 'Signature' in request.headers.keys(): + public_key = utils.get_travis_pubilc_key(repo_cfg['travis']['public_key_url']) + if public_key: + try: + utils.check_authorized(request.headers['Signature'], public_key, request.forms.payload) + except Exception as e: + logger.warn('check travis data signature error: {}'.format(e)) + abort(400, 'Signature failed') + else: + abort(400, 'Get public_key failed') + succ = info['result'] == 0 report_build_res(succ, info['build_url'], 'travis', state, logger, repo_cfg) diff --git a/homu/utils.py b/homu/utils.py index 4ce574b1..2f671cad 100644 --- a/homu/utils.py +++ b/homu/utils.py @@ -6,6 +6,9 @@ import traceback import requests import time +import base64 +from OpenSSL.crypto import verify, load_publickey, FILETYPE_PEM, X509 +from OpenSSL.crypto import Error as SignatureError def github_set_ref(repo, ref, sha, *, force=False, auto_create=True): url = repo._build_url('git', 'refs', ref, base_url=repo._api) @@ -54,11 +57,14 @@ def lazy_debug(logger, f): logger.debug(f()) def logged_call(args): - try: subprocess.check_call(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - except subprocess.CalledProcessError as e: + p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p.wait() + if p.returncode != 0: print('* Failed to execute command: {}'.format(args)) + print('shell ruturn data: {}'.format(p.communicate())) raise + def silent_call(args): return subprocess.call(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) @@ -85,3 +91,21 @@ def retry_until(inner, fail, state): traceback.print_exception(*exc_info) fail(err) + + +def get_travis_pubilc_key(travis_pubilc_key_url): + try: + key_detail = requests.get(travis_pubilc_key_url).json() + except Exception as e: + print('* Get travis public_key url error: {}'.format(e)) + return False + + return key_detail['config']['notifications']['webhook']['public_key'] + +def check_authorized(signature, public_key, payload): + signature = base64.b64decode(signature) + pkey_public_key = load_publickey(FILETYPE_PEM, public_key) + certificate = X509() + certificate.set_pubkey(pkey_public_key) + verify(certificate, signature, payload, str('sha1')) +