verify the HMAC that GitHub sends
authorRalf Jung <post@ralfj.de>
Sun, 22 Feb 2015 21:58:37 +0000 (22:58 +0100)
committerRalf Jung <post@ralfj.de>
Sun, 22 Feb 2015 21:58:37 +0000 (22:58 +0100)
git_mirror.py
webhook-core.py
webhook.py

index 0bd4e81ecd8b793ad42abca32cf9777cc2a738fb..3a65d8594aeb56d74bf07d947cb86866bb751f6f 100644 (file)
@@ -22,7 +22,8 @@
 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #==============================================================================
 import sys, os, os.path, subprocess
 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #==============================================================================
 import sys, os, os.path, subprocess
-import configparser, itertools, json, re
+import configparser, itertools, re
+import hmac, hashlib
 import email.mime.text, email.utils, smtplib
 
 mail_sender = "null@localhost"
 import email.mime.text, email.utils, smtplib
 
 mail_sender = "null@localhost"
@@ -76,21 +77,13 @@ def send_mail(subject, text, recipients, sender, replyTo = None):
     s.sendmail(sender, recipients, msg.as_string())
     s.quit()
 
     s.sendmail(sender, recipients, msg.as_string())
     s.quit()
 
-def get_github_payload():
-    '''Reeturn the github-style JSON encoded payload (as if we were called as a github webhook)'''
-    try:
-        data = sys.stdin.buffer.read()
-        data = json.loads(data.decode('utf-8'))
-        return data
-    except:
-        return {} # nothing read
-
 class Repo:
     def __init__(self, name, conf):
         '''Creates a repository from a section of the git-mirror configuration file'''
         self.name = name
         self.local = conf['local']
         self.owner = conf['owner'] # email address to notify in case of problems
 class Repo:
     def __init__(self, name, conf):
         '''Creates a repository from a section of the git-mirror configuration file'''
         self.name = name
         self.local = conf['local']
         self.owner = conf['owner'] # email address to notify in case of problems
+        self.hmac_secret = conf['hmac-secret'].encode('utf-8')
         self.deploy_key = conf['deploy-key'] # the SSH ky used for authenticating against remote hosts
         self.mirrors = {} # maps mirrors to their URLs
         mirror_prefix = 'mirror-'
         self.deploy_key = conf['deploy-key'] # the SSH ky used for authenticating against remote hosts
         self.mirrors = {} # maps mirrors to their URLs
         mirror_prefix = 'mirror-'
@@ -101,6 +94,11 @@ class Repo:
     def mail_owner(self, msg):
         global mail_sender
         send_mail("git-mirror {0}".format(self.name), msg, recipients = [self.owner], sender = mail_sender)
     def mail_owner(self, msg):
         global mail_sender
         send_mail("git-mirror {0}".format(self.name), msg, recipients = [self.owner], sender = mail_sender)
+
+    def compute_hmac(self, data):
+        h = hmac.new(self.hmac_secret, digestmod = hashlib.sha1)
+        h.update(data)
+        return h.hexdigest()
     
     def find_mirror_by_url(self, match_urls):
         for mirror, url in self.mirrors.items():
     
     def find_mirror_by_url(self, match_urls):
         for mirror, url in self.mirrors.items():
index d4c1ab7c68bcf9a6455ed7270f1697f267b3184e..934d41f85ffd04436372cf9ff17368355531e4e5 100755 (executable)
 #==============================================================================
 
 # This is the hook called by GitHub as webhook. It updats the local repository, and then all the other mirrors.
 #==============================================================================
 
 # This is the hook called by GitHub as webhook. It updats the local repository, and then all the other mirrors.
-import sys, traceback
+import sys, traceback, json
 from git_mirror import *
 
 from git_mirror import *
 
+def get_github_payload(repo, signature):
+    '''Return the github-style JSON encoded payload (as if we were called as a github webhook)'''
+    data = sys.stdin.buffer.read()
+    verify_signature = repo.compute_hmac(data)
+    if signature != "sha1="+verify_signature:
+        raise Exception("You are not GitHub!")
+    try:
+        data = json.loads(data.decode('utf-8'))
+        return data
+    except ValueError:
+        return {} # nothing read
+
+
 if __name__ == "__main__":
     # call this with: <reponame> <event name> <signature>
     repo = None # we will try to use this during exception handling
 if __name__ == "__main__":
     # call this with: <reponame> <event name> <signature>
     repo = None # we will try to use this during exception handling
@@ -42,7 +55,7 @@ if __name__ == "__main__":
         repo = repos[reponame]
         
         # now sync this repository
         repo = repos[reponame]
         
         # now sync this repository
-        data = get_github_payload()
+        data = get_github_payload(repo, githubSignature)
         if githubEvent == 'ping':
             # github sends this initially
             print("Content-Type: text/plain")
         if githubEvent == 'ping':
             # github sends this initially
             print("Content-Type: text/plain")
index 33cae3974c5b34c65030c4794a9a5ffe19f00276..d91a4aac3ed5774babfcacc81969d44307759638 100755 (executable)
@@ -29,17 +29,6 @@ webhook_core = "/home/git/git-mirror/webhook-core.py"
 #
 import urllib.request, urllib.parse, json, os, sys
 
 #
 import urllib.request, urllib.parse, json, os, sys
 
-def is_github(remote_addr):
-    '''Returns whether the address is a github hook address. This function requires Python 3.3.'''
-    from ipaddress import ip_address, ip_network
-    remote_addr = ip_address(ip_network)
-    github = urllib.request.urlopen('https://api.github.com/meta').read()
-    github = json.loads(github.decode('utf-8'))
-    for net in github['hooks']:
-        if remote_addr in ip_network(net):
-            return True
-    return False
-
 # get repository from query string
 query = os.getenv("QUERY_STRING")
 query = urllib.parse.parse_qs(query)
 # get repository from query string
 query = os.getenv("QUERY_STRING")
 query = urllib.parse.parse_qs(query)