From 83f4c808bef9a94b52d82da50f5daaea2f34d5e4 Mon Sep 17 00:00:00 2001 From: "Pol.Alvarez@BSC" Date: Sat, 10 Feb 2018 19:55:24 +0100 Subject: [PATCH 1/4] WIP: Signature validation --- index.js | 72 ++++++++++++++++++++++++++++++++++++++++++++++--------- readme.md | 33 +++++++++++++------------ 2 files changed, 79 insertions(+), 26 deletions(-) diff --git a/index.js b/index.js index c7c24a6..7b6356e 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,8 @@ const config = require('./config'); const bot = require('./bot'); +const crypto = require('crypto'); + var express = require('express'), cors = require('cors'), @@ -23,27 +25,75 @@ app.use(helmet()); // Receive a POST request at the url specified by an env. var. app.post(`${config.urlEndpoint}`, jsonParser, function (req, res, next) { + console.log("Request received"); + if (!req.body || !req.body.action) { + console.log("No body or action"); return res.sendStatus(400); } else if (!bot.needsFunding(req)) { + console.log("No needed funding"); return res.sendStatus(204); } - setTimeout(() => { - processRequest(req) - .then(() => { - bot.info('issue well funded: ' + res.body.issue.url); - }) - .catch((err) => { - bot.error('Error funding issue: ' + req.body.issue.url); - bot.error('error: ' + err); - bot.error('dump: ' + req); - }); - }, config.delayInMiliSeconds); + console.log("Calling validation"); + validation = validateRequest(req); + + if (validation.correct) { + + setTimeout(() => { + processRequest(req) + .then(() => { + bot.info('issue well funded: ' + res.body.issue.url); + }) + .catch((err) => { + bot.error('Error funding issue: ' + req.body.issue.url); + bot.error('error: ' + err); + bot.error('dump: ' + req); + }); + }, config.delayInMiliSeconds); + + } else { + bot.error('Error funding issue: ' + req.body.issue.url); + bot.error('error: ' + validation.error); + } return res.sendStatus(200); }); +const validateRequest = function (req) { + console.log("Validating request..."); + validation = {correct: false, error: ''}; + webhookSecret = process.env.WEBHOOK_SECRET; + + if(!webhookSecret) { + validation.error = 'Github Webhook Secret key not found. ' + + 'Please set env variable WEBHOOK_SECRET to github\'s webhook secret value'; + } else { + + + const blob = JSON.stringify(req.body); + const hmac = crypto.createHmac('sha1', webhookSecret); + const ourSignature = `sha1=${hmac.update(blob).digest('hex')}`; + + const theirSignature = req.get('X-Hub-Signature'); + + const bufferA = Buffer.from(ourSignature, 'utf8'); + const bufferB = Buffer.from(theirSignature, 'utf8'); + + const safe = crypto.timingSafeEqual(bufferA, bufferB); + + if (safe) { + validation.correct = true; + } else { + validation.error = 'Invalid signature. Check that WEBHOOK_SECRET ' + + 'env variable matches github\'s webhook secret value'; + } + } + + return validation; +} + const processRequest = function (req) { + const eth = bot.eth; const from = config.sourceAddress; const to = bot.getAddress(req); diff --git a/readme.md b/readme.md index 7e40c97..6053e3e 100644 --- a/readme.md +++ b/readme.md @@ -31,39 +31,42 @@ All issues tagged with **[bounty](https://github.com/status-im/status-react/issu Autobounty is build using docker. Before building the image, you need to set up a configuration as follows: -The [config]() folder contains the files for configuring the bot. The description for the variables can be found in *default.js*. Create a production config file (e.g. *production.js*) uing the {default,development}.js as template to override the default ones. **Remeber** to set the environment variable *NODE_ENV* in the dockerfile (e.g. `ENV NODE_ENV production`). +The [config]() folder contains the files for configuring the bot. The description for the variables can be found in *default.js*. Create a production config file (e.g. *production.js*) uing the {default,development}.js as template to override the default ones. + +**Remeber** to set the environment variable *NODE_ENV* in the dockerfile (e.g. `ENV NODE_ENV production`) and *WEBHOOK_SECRET* to the value specified in the secret field during the webhook creation (e.g. for random creation *ruby -rsecurerandom -e 'puts SecureRandom.hex(20)'*). +) ```javascript // Debug mode for testing the bot debug: true, - + // URL where the bot is listening (e.g. '/funding') urlEndpoint: '', - - // Path for the log files inside the docker image (e.g. './log/'), - remember to create the folder inside the docker workspace if you change it + + // Path for the log files inside the docker image (e.g. './log/'), + remember to create the folder inside the docker workspace if you change it (the folde will be copied to the docker image during the build) logPath: '', - + // URL for the signer (e.g. 'https://ropsten.infura.io') signerPath: '', - - // Address with the funding for the bounties + + // Address with the funding for the bounties sourceAddress: '', - + // Token of the currency for fetching real time prices (e.g. 'SNT') token: '', - + // Limit for the gas used in a transaction (e.g. 92000) gasLimit: 0, - + // Price per hour you will pay in dolars (e.g. 35) priceHour: 0, - + // Delay before funding a bounty (e.g. 3600000) delayInMiliSeconds: 0, - + // Bounty Labels for the issues and the correspondent houres (e.g. {'bounty-xs': 3}) bountyLabels: {}, @@ -80,12 +83,12 @@ Create a github webhook with the following information: * Payoload URL: IP_HOST/URL_ENDPOINT * Content Type: application/json -* Secret: blank +* Secret: the value you set for environment variable WEBHOOK_SECRET. * Configure the webhook to be triggered by comments in issues selecting the Issue Comment box in 'Let me select individual events' Where *IP_HOST* is the ip of the machine running the docker image and *URL_ENDPOINT* is the configuration variable with the same name in your custom config file. -#### Build +#### Build To build and run the docker image issue the following commands: From 9e7b06b5234bc771a10dc46c0f3032abee773b01 Mon Sep 17 00:00:00 2001 From: "Pol.Alvarez@BSC" Date: Tue, 13 Feb 2018 11:43:28 +0100 Subject: [PATCH 2/4] Alpha test: secret webhook --- Dockerfile | 3 +++ bot/index.js | 2 +- index.js | 13 ++++++------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0177afd..49b3813 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,3 +5,6 @@ EXPOSE 8080 # Set this variable to the name of your production config file (without the extension) ENV NODE_ENV development + +# Set this variable to the value of the secret field of the Github webhook +ENV WEBHOOK_SECRET '' diff --git a/bot/index.js b/bot/index.js index 43c8f02..f03a9cf 100644 --- a/bot/index.js +++ b/bot/index.js @@ -113,7 +113,7 @@ const logTransaction = function (txId, from, to, amount, gasPrice) { logger.info("===================================================="); } -const log = function (msg) { +const info = function (msg) { logger.info(msg); } diff --git a/index.js b/index.js index 7b6356e..e1eea84 100644 --- a/index.js +++ b/index.js @@ -43,17 +43,17 @@ app.post(`${config.urlEndpoint}`, jsonParser, function (req, res, next) { setTimeout(() => { processRequest(req) .then(() => { - bot.info('issue well funded: ' + res.body.issue.url); + bot.info('issue well funded: ' + req.body.issue.url); }) .catch((err) => { - bot.error('Error funding issue: ' + req.body.issue.url); + bot.error('Error processing request: ' + req.body.issue.url); bot.error('error: ' + err); - bot.error('dump: ' + req); + bot.error('dump: ' + req.body); }); }, config.delayInMiliSeconds); } else { - bot.error('Error funding issue: ' + req.body.issue.url); + bot.error('Error validating issue: ' + req.body.issue.url); bot.error('error: ' + validation.error); } return res.sendStatus(200); @@ -69,8 +69,7 @@ const validateRequest = function (req) { 'Please set env variable WEBHOOK_SECRET to github\'s webhook secret value'; } else { - - const blob = JSON.stringify(req.body); + const blob = JSON.stringify(req.body); const hmac = crypto.createHmac('sha1', webhookSecret); const ourSignature = `sha1=${hmac.update(blob).digest('hex')}`; @@ -153,5 +152,5 @@ const sendTransaction = function (eth, from, to, amount, gasPrice) { const port = process.env.PORT || 8181 app.listen(port, function () { - bot.log('Autobounty listening on port', port); + bot.info('Autobounty listening on port', port); }); From a144b6987dfb7fa565e4c374d3c7af47ef52db63 Mon Sep 17 00:00:00 2001 From: "Pol.Alvarez@BSC" Date: Tue, 13 Feb 2018 11:45:55 +0100 Subject: [PATCH 3/4] Export glitch --- bot/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/index.js b/bot/index.js index f03a9cf..812a249 100644 --- a/bot/index.js +++ b/bot/index.js @@ -127,7 +127,7 @@ module.exports = { getAddress: getAddress, getAmount: getAmount, getGasPrice: prices.getGasPrice, - log: log, + info: info, logTransaction: logTransaction, error: error } From 5ae278406677fffe1e9edde68d23ca78ec0764d8 Mon Sep 17 00:00:00 2001 From: "Pol.Alvarez@BSC" Date: Tue, 13 Feb 2018 11:52:03 +0100 Subject: [PATCH 4/4] Removed debug logs --- index.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/index.js b/index.js index e1eea84..59c47a0 100644 --- a/index.js +++ b/index.js @@ -25,16 +25,12 @@ app.use(helmet()); // Receive a POST request at the url specified by an env. var. app.post(`${config.urlEndpoint}`, jsonParser, function (req, res, next) { - console.log("Request received"); if (!req.body || !req.body.action) { - console.log("No body or action"); return res.sendStatus(400); } else if (!bot.needsFunding(req)) { - console.log("No needed funding"); return res.sendStatus(204); } - console.log("Calling validation"); validation = validateRequest(req); @@ -60,7 +56,6 @@ app.post(`${config.urlEndpoint}`, jsonParser, function (req, res, next) { }); const validateRequest = function (req) { - console.log("Validating request..."); validation = {correct: false, error: ''}; webhookSecret = process.env.WEBHOOK_SECRET;