@mapbox/cloudfriend
Advanced tools
Comparing version 2.7.0 to 2.8.0
@@ -0,1 +1,6 @@ | ||
# v2.8.0 | ||
- Allows Hookshot callers to bring their own webhook secret. This is used for | ||
signature-verification in the `.Github()` case. | ||
# v2.7.0 | ||
@@ -2,0 +7,0 @@ |
@@ -351,2 +351,6 @@ <!-- Generated by documentation.js. Update this documentation by updating the source code. --> | ||
least $context.requestId. [See AWS documentation for details][89]. | ||
- `WebhookSecret` **([String][31] \| [Object][30])?** A secret string to be used to verify | ||
payload signatures that are delivered to the endpoint. This is optional. If | ||
not specified, a string will be autogenerated for you. Implementation of | ||
signature verification is up to the caller. | ||
@@ -403,2 +407,7 @@ ### Properties | ||
to a CloudWatch LogGroup named `API-Gateway-Execution-Logs_{rest-api-id}/hookshot` | ||
- `WebhookSecret` **([String][31] \| [Object][30])?** A secret string to be used to verify | ||
payload signatures that are delivered to the endpoint. This is optional. If | ||
not specified, a string will be autogenerated for you. You should provide this | ||
value to Github, and signature verification will be performed prior to your | ||
Lambda function being invoked to respond to the event. | ||
@@ -405,0 +414,0 @@ ### Properties |
@@ -55,2 +55,6 @@ 'use strict'; | ||
* least $context.requestId. [See AWS documentation for details](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-accesslogsetting.html#cfn-apigateway-stage-accesslogsetting-format). | ||
* @param {String|Object} [WebhookSecret] A secret string to be used to verify | ||
* payload signatures that are delivered to the endpoint. This is optional. If | ||
* not specified, a string will be autogenerated for you. Implementation of | ||
* signature verification is up to the caller. | ||
* | ||
@@ -84,3 +88,4 @@ * @example | ||
DataTraceEnabled = false, | ||
MetricsEnabled = false | ||
MetricsEnabled = false, | ||
WebhookSecret | ||
} = options; | ||
@@ -104,11 +109,5 @@ | ||
this.PassthroughTo = PassthroughTo; | ||
this.WebhookSecret = options.WebhookSecret; | ||
const Resources = { | ||
[`${Prefix}Secret`]: { | ||
Type: 'AWS::ApiGateway::ApiKey', | ||
Properties: { | ||
Enabled: false | ||
} | ||
}, | ||
[`${Prefix}Api`]: { | ||
@@ -189,2 +188,9 @@ Type: 'AWS::ApiGateway::RestApi', | ||
if (!WebhookSecret) Resources[`${Prefix}Secret`] = { | ||
Type: 'AWS::ApiGateway::ApiKey', | ||
Properties: { | ||
Enabled: false | ||
} | ||
}; | ||
if (AccessLogFormat) { | ||
@@ -234,3 +240,3 @@ Resources[`${Prefix}AccessLogs`] = { | ||
Description: 'A secret key to give Github to use when signing webhook requests', | ||
Value: { Ref: `${Prefix}Secret` } | ||
Value: WebhookSecret ? WebhookSecret : { Ref: `${Prefix}Secret` } | ||
} | ||
@@ -328,2 +334,7 @@ }; | ||
* to a CloudWatch LogGroup named `API-Gateway-Execution-Logs_{rest-api-id}/hookshot` | ||
* @param {String|Object} [WebhookSecret] A secret string to be used to verify | ||
* payload signatures that are delivered to the endpoint. This is optional. If | ||
* not specified, a string will be autogenerated for you. You should provide this | ||
* value to Github, and signature verification will be performed prior to your | ||
* Lambda function being invoked to respond to the event. | ||
* | ||
@@ -355,2 +366,3 @@ * @example | ||
} | ||
method() { | ||
@@ -412,33 +424,38 @@ return { | ||
return { | ||
'Fn::Sub': redent(` | ||
'use strict'; | ||
'Fn::Sub': [ | ||
redent(` | ||
'use strict'; | ||
const crypto = require('crypto'); | ||
const AWS = require('aws-sdk'); | ||
const lambda = new AWS.Lambda(); | ||
const secret = '\${${this.Prefix}Secret}'; | ||
const crypto = require('crypto'); | ||
const AWS = require('aws-sdk'); | ||
const lambda = new AWS.Lambda(); | ||
const secret = '\${WebhookSecret}'; | ||
module.exports.lambda = (event, context, callback) => { | ||
const body = event.body; | ||
const hash = 'sha1=' + crypto | ||
.createHmac('sha1', secret) | ||
.update(new Buffer(JSON.stringify(body))) | ||
.digest('hex'); | ||
module.exports.lambda = (event, context, callback) => { | ||
const body = event.body; | ||
const hash = 'sha1=' + crypto | ||
.createHmac('sha1', secret) | ||
.update(new Buffer(JSON.stringify(body))) | ||
.digest('hex'); | ||
if (event.signature !== hash) | ||
return callback('invalid: signature does not match'); | ||
if (event.signature !== hash) | ||
return callback('invalid: signature does not match'); | ||
if (body.zen) return callback(null, 'ignored ping request'); | ||
if (body.zen) return callback(null, 'ignored ping request'); | ||
const lambdaParams = { | ||
FunctionName: '\${${this.PassthroughTo}}', | ||
Payload: JSON.stringify(event.body), | ||
InvocationType: 'Event' | ||
const lambdaParams = { | ||
FunctionName: '\${${this.PassthroughTo}}', | ||
Payload: JSON.stringify(event.body), | ||
InvocationType: 'Event' | ||
}; | ||
lambda.invoke(lambdaParams).promise() | ||
.then(() => callback(null, 'success')) | ||
.catch((err) => callback(err)); | ||
}; | ||
lambda.invoke(lambdaParams).promise() | ||
.then(() => callback(null, 'success')) | ||
.catch((err) => callback(err)); | ||
}; | ||
`).trim() | ||
`).trim(), | ||
{ | ||
WebhookSecret: this.WebhookSecret || { Ref: `${this.Prefix}Secret` } | ||
} | ||
] | ||
}; | ||
@@ -445,0 +462,0 @@ } |
{ | ||
"name": "@mapbox/cloudfriend", | ||
"version": "2.7.0", | ||
"version": "2.8.0", | ||
"description": "Helper functions for assembling CloudFormation templates in JavaScript", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -8,8 +8,2 @@ { | ||
"Resources": { | ||
"PassSecret": { | ||
"Type": "AWS::ApiGateway::ApiKey", | ||
"Properties": { | ||
"Enabled": false | ||
} | ||
}, | ||
"PassApi": { | ||
@@ -28,3 +22,3 @@ "Type": "AWS::ApiGateway::RestApi", | ||
"DeploymentId": { | ||
"Ref": "PassDeployment60b3cb7b" | ||
"Ref": "PassDeployment55cc8548" | ||
}, | ||
@@ -48,3 +42,3 @@ "StageName": "hookshot", | ||
}, | ||
"PassDeployment60b3cb7b": { | ||
"PassDeployment55cc8548": { | ||
"Type": "AWS::ApiGateway::Deployment", | ||
@@ -144,2 +138,8 @@ "DependsOn": "PassMethod", | ||
}, | ||
"PassSecret": { | ||
"Type": "AWS::ApiGateway::ApiKey", | ||
"Properties": { | ||
"Enabled": false | ||
} | ||
}, | ||
"PassFunctionLogs": { | ||
@@ -211,3 +211,10 @@ "Type": "AWS::Logs::LogGroup", | ||
"ZipFile": { | ||
"Fn::Sub": "'use strict';\n\nconst crypto = require('crypto');\nconst AWS = require('aws-sdk');\nconst lambda = new AWS.Lambda();\nconst secret = '${PassSecret}';\n\nmodule.exports.lambda = (event, context, callback) => {\n const body = event.body;\n const hash = 'sha1=' + crypto\n .createHmac('sha1', secret)\n .update(new Buffer(JSON.stringify(body)))\n .digest('hex');\n\n if (event.signature !== hash)\n return callback('invalid: signature does not match');\n\n if (body.zen) return callback(null, 'ignored ping request');\n\n const lambdaParams = {\n FunctionName: '${Destination}',\n Payload: JSON.stringify(event.body),\n InvocationType: 'Event'\n };\n\n lambda.invoke(lambdaParams).promise()\n .then(() => callback(null, 'success'))\n .catch((err) => callback(err));\n};" | ||
"Fn::Sub": [ | ||
"'use strict';\n\nconst crypto = require('crypto');\nconst AWS = require('aws-sdk');\nconst lambda = new AWS.Lambda();\nconst secret = '${WebhookSecret}';\n\nmodule.exports.lambda = (event, context, callback) => {\n const body = event.body;\n const hash = 'sha1=' + crypto\n .createHmac('sha1', secret)\n .update(new Buffer(JSON.stringify(body)))\n .digest('hex');\n\n if (event.signature !== hash)\n return callback('invalid: signature does not match');\n\n if (body.zen) return callback(null, 'ignored ping request');\n\n const lambdaParams = {\n FunctionName: '${Destination}',\n Payload: JSON.stringify(event.body),\n InvocationType: 'Event'\n };\n\n lambda.invoke(lambdaParams).promise()\n .then(() => callback(null, 'success'))\n .catch((err) => callback(err));\n};", | ||
{ | ||
"WebhookSecret": { | ||
"Ref": "PassSecret" | ||
} | ||
} | ||
] | ||
} | ||
@@ -214,0 +221,0 @@ }, |
@@ -18,3 +18,3 @@ 'use strict'; | ||
return cf.validate(path.join(__dirname, 'fixtures', 'shortcuts', filename)) | ||
.catch(() => assert.fail(`${filename} fixture fails validation`)) | ||
.catch((err) => assert.fail(`${filename} fixture fails validation: ${err.message}`)) | ||
.then(() => assert.pass(`${filename} fixture passed validation`)); | ||
@@ -523,3 +523,3 @@ }); | ||
const github = new cf.shortcuts.hookshot.Github({ | ||
let github = new cf.shortcuts.hookshot.Github({ | ||
Prefix: 'Pass', | ||
@@ -529,3 +529,3 @@ PassthroughTo: 'Destination' | ||
const template = cf.merge(github, to); | ||
let template = cf.merge(github, to); | ||
if (update) fixtures.update('hookshot-github', template); | ||
@@ -538,3 +538,31 @@ assert.deepEqual( | ||
github = new cf.shortcuts.hookshot.Github({ | ||
Prefix: 'Pass', | ||
PassthroughTo: 'Destination', | ||
WebhookSecret: 'abc123' | ||
}); | ||
template = cf.merge(github, to); | ||
if (update) fixtures.update('hookshot-github-secret-string', template); | ||
assert.deepEqual( | ||
normalizeDeployment(noUndefined(template)), | ||
normalizeDeployment(fixtures.get('hookshot-github-secret-string')), | ||
'expected resources generated when secret passed as string' | ||
); | ||
github = new cf.shortcuts.hookshot.Github({ | ||
Prefix: 'Pass', | ||
PassthroughTo: 'Destination', | ||
WebhookSecret: cf.ref('SomeParameter') | ||
}); | ||
const Parameters = { SomeParameter: { Type: 'String' } }; | ||
template = cf.merge(github, to, { Parameters }); | ||
if (update) fixtures.update('hookshot-github-secret-ref', template); | ||
assert.deepEqual( | ||
normalizeDeployment(noUndefined(template)), | ||
normalizeDeployment(fixtures.get('hookshot-github-secret-ref')), | ||
'expected resources generated when secret passed as ref' | ||
); | ||
assert.end(); | ||
}); |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
275048
59
7407
2