From 196a617659ba70caa70c090dd2178f757a495e4e Mon Sep 17 00:00:00 2001 From: Aapo Kuuselo Date: Fri, 16 Oct 2015 20:53:12 +0300 Subject: [PATCH] Added support for Github shared secret --- examples/deploys-with-secret.json | 11 ++++ lib/incoming.js | 96 ++++++++++++++++++++----------- lib/webdep.js | 5 ++ package.json | 1 + 4 files changed, 81 insertions(+), 32 deletions(-) create mode 100644 examples/deploys-with-secret.json diff --git a/examples/deploys-with-secret.json b/examples/deploys-with-secret.json new file mode 100644 index 0000000..0c4dd77 --- /dev/null +++ b/examples/deploys-with-secret.json @@ -0,0 +1,11 @@ +{ + "secret": "secret", + "deploys": [{ + "name": "Webhook Deployer", + "type": "github", + "repo": "https://github.com/Camme/webhook-deployer", + "basepath": "/Users/camilo/Projects/24hr-deployer", + "command": "git pull", + "branch": "master" + }] +} diff --git a/lib/incoming.js b/lib/incoming.js index 9bdb58f..83842e1 100644 --- a/lib/incoming.js +++ b/lib/incoming.js @@ -2,6 +2,7 @@ var webdep = require('./webdep'); var fs = require("fs"); var path = require("path"); var exec = require('child_process').exec; +var crypto = require('crypto'); exports.control = function(options) { @@ -22,55 +23,66 @@ exports.control = function(options) { error = true; } - deploysList.forEach(function(deploy) { + if (repoData.hasOwnProperty('signature') && config.hasOwnProperty('signature')) { - if (deploy.type == "github") { + error = true; + } - if (!repoData.error && !repoData.repository) { - webdep.log("Webhook payload was not for a push."); - return; - } + if (validateSignature(req)) { + deploysList.forEach(function(deploy) { + + if (deploy.type == "github") { + + if (!repoData.error && !repoData.repository) { + webdep.log("Webhook payload was not for a push."); + return; + } - if (!repoData.error) { + if (!repoData.error) { - webdep.log("Checking incoming repo " + repoData.repository.url + "..."); + webdep.log("Checking incoming repo " + repoData.repository.url + "..."); - // remove .git from the repo string, since the hook will not have that address when it comes from github - // so instead of explaining that you shouldnt enter ".git", we just remove it here. - if (repoData.repository.url == deploy.repo.replace(".git$", "")) { + // remove .git from the repo string, since the hook will not have that address when it comes from github + // so instead of explaining that you shouldnt enter ".git", we just remove it here. + if (repoData.repository.url == deploy.repo.replace(".git$", "")) { - var branch = repoData.ref.split("/").pop(); + var branch = repoData.ref.split("/").pop(); - webdep.log("> Checking branch '" + branch + "' ..."); + webdep.log("> Checking branch '" + branch + "' ..."); - if (branch == deploy.branch) { + if (branch == deploy.branch) { - runDeploy(deploy); + runDeploy(deploy); + } + else { + webdep.log("> No, wrong branch, nothing to do"); + webdep.log("> Got:"); + webdep.log("> " + branch); + webdep.log("> But expected:"); + webdep.log("> " + deploy.branch); + } } else { - webdep.log("> No, wrong branch, nothing to do"); - webdep.log("> Got:"); - webdep.log("> " + branch); - webdep.log("> But expected:"); - webdep.log("> " + deploy.branch); + webdep.log("No, wrong repo, nothing to do"); + webdep.log("Got:"); + webdep.log(repoData.repository.url); + webdep.log("But expected:"); + webdep.log(deploy.repo.replace(".git", "")); } + } else { - webdep.log("No, wrong repo, nothing to do"); - webdep.log("Got:"); - webdep.log(repoData.repository.url); - webdep.log("But expected:"); - webdep.log(deploy.repo.replace(".git", "")); + webdep.log(""); + webdep.log("Error while parsing post body"); + webdep.log(repoData.error); } - } - else { - webdep.log(""); - webdep.log("Error while parsing post body"); - } - } - }); + }); + } else { + error = true; + webdep.log("Signatures don't match"); + } if (error) { res.statusCode = 500; @@ -88,6 +100,26 @@ exports.control = function(options) { }; +function signBlob(key, blob) { + return 'sha1=' + crypto.createHmac('sha1', key).update(blob).digest('hex') +} + +function validateSignature(req) { + var headers = req.headers; + var secret = webdep.getSecret(); + var signature; + + if (secret || headers.hasOwnProperty('x-hub-signature')) { + if (!secret || !headers.hasOwnProperty('x-hub-signature')) { + return false; + } + + signature = signBlob(secret, req.rawBody); + return signature === headers['x-hub-signature']; + } + + return true; +} function runDeploy(deploy) { diff --git a/lib/webdep.js b/lib/webdep.js index 63bfdd8..6450264 100644 --- a/lib/webdep.js +++ b/lib/webdep.js @@ -111,6 +111,7 @@ exports.init = function(newOptions, callback) { req.on("end", function() { req.params = {}; + req.rawBody = data; if (req.method == "POST") { req.body = querystring.parse(data); @@ -335,6 +336,10 @@ function getDeployFromId(id) { return null; } +exports.getSecret = getSecret = function() { + return options.hasOwnProperty('secret') ? options.secret : false; +} + exports.getDeployList = getDeployList = function() { var deploysList = {}; diff --git a/package.json b/package.json index 8616ae6..677640c 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "dependencies": { "commander": "~1.1.1", "cookies": "~0.3.6", + "crypto": "0.0.3", "keygrip": "~0.2.2", "mustache": "~0.7.2", "node-github": "0.0.3",