diff --git a/cfg.sample.toml b/cfg.sample.toml index b9a838ae..c29d5801 100644 --- a/cfg.sample.toml +++ b/cfg.sample.toml @@ -10,6 +10,23 @@ access_token = "" app_client_id = "" app_client_secret = "" +# A base shared secret; this can be used to generate the per-repo github secret, +# which is what's used to verify authenticity of github -> Homu webhooks. The +# per-repo secret can be computed as follows (you'll need to enter this in +# the repo webhook configuration): +# +# $ echo -n "$owner.$name" | openssl sha1 -hmac "$secret" +# +# Note this is new; Homu started with a per-repository `repo.github.secret` +# value, which if specified, overrides this. Hence, you can incrementally +# transition repositories. +# +# You can generate this value with $(openssl rand -hex 20) as per the final +# secret, but it could also be anything you like. +# +# base_secret = "" + + [git] # Use the local Git command. Required to use some advanced features. It also diff --git a/homu/server.py b/homu/server.py index a0fead4b..1f7ee44f 100644 --- a/homu/server.py +++ b/homu/server.py @@ -233,15 +233,28 @@ def github(): owner_info = info['repository']['owner'] owner = owner_info.get('login') or owner_info['name'] - repo_label = g.repo_labels[owner, info['repository']['name']] + repo_name = info['repository']['name'] + repo_label = g.repo_labels[owner, repo_name] repo_cfg = g.repo_cfgs[repo_label] + # This HMAC authenticates that github knows our secret hmac_method, hmac_sig = request.headers['X-Hub-Signature'].split('=') - if hmac_sig != hmac.new( - repo_cfg['github']['secret'].encode('utf-8'), - payload, - hmac_method, - ).hexdigest(): + # Originally, we only supported a per-repo secret + repo_secret = repo_cfg.get('github', {}).get('secret') + if repo_secret is None: + # But here we support automatically deriving a per-repo secret from a + # global secret, without having repo owners be able to compute the + # secret for a different repo. See the comment in cfg.sample.toml for + # more information. + base_secret = g.cfg['github'].get('base_secret') + if base_secret is None: + abort(500, "Repository {} has no secret specified, and no base_secret".format(repo_label)) + repo_secret = hmac.new(base_secret.encode('utf-8'), + "{}.{}".format(owner, repo_name).encode('utf-8'), + 'sha1').hexdigest() + computed_hmac = hmac.new(repo_secret.encode('utf-8'), payload, hmac_method).hexdigest() + + if hmac_sig != computed_hmac: abort(400, 'Invalid signature') event_type = request.headers['X-Github-Event']