@mapbox/cloudfriend
Version:
Helper functions for assembling CloudFormation templates in JavaScript
423 lines • 11.5 kB
JSON
{
"AWSTemplateFormatVersion": "2010-09-09",
"Metadata": {},
"Parameters": {},
"Rules": {},
"Mappings": {},
"Conditions": {},
"Resources": {
"PassApi": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"Name": {
"Fn::Sub": "${AWS::StackName}-webhook"
},
"FailOnWarnings": true,
"EndpointConfiguration": {
"Types": [
"REGIONAL"
]
}
}
},
"PassStage": {
"Type": "AWS::ApiGateway::Stage",
"Properties": {
"DeploymentId": {
"Ref": "PassDeploymentbdbc0f16"
},
"StageName": "hookshot",
"RestApiId": {
"Ref": "PassApi"
},
"MethodSettings": [
{
"HttpMethod": "*",
"ResourcePath": "/*",
"ThrottlingBurstLimit": 20,
"ThrottlingRateLimit": 5,
"LoggingLevel": "OFF",
"DataTraceEnabled": false,
"MetricsEnabled": false
}
]
}
},
"PassDeploymentbdbc0f16": {
"Type": "AWS::ApiGateway::Deployment",
"DependsOn": "PassMethod",
"Properties": {
"RestApiId": {
"Ref": "PassApi"
},
"StageName": "unused"
}
},
"PassResource": {
"Type": "AWS::ApiGateway::Resource",
"Properties": {
"ParentId": {
"Fn::GetAtt": [
"PassApi",
"RootResourceId"
]
},
"RestApiId": {
"Ref": "PassApi"
},
"PathPart": "webhook"
}
},
"PassMethod": {
"Type": "AWS::ApiGateway::Method",
"Properties": {
"RestApiId": {
"Ref": "PassApi"
},
"ResourceId": {
"Ref": "PassResource"
},
"ApiKeyRequired": false,
"AuthorizationType": "NONE",
"HttpMethod": "POST",
"Integration": {
"Type": "AWS",
"IntegrationHttpMethod": "POST",
"IntegrationResponses": [
{
"StatusCode": "200"
},
{
"StatusCode": "500",
"SelectionPattern": "^error.*"
},
{
"StatusCode": "403",
"SelectionPattern": "^invalid.*"
}
],
"Uri": {
"Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PassFunction.Arn}/invocations"
},
"RequestTemplates": {
"application/json": "{\"signature\":\"$input.params('X-Hub-Signature')\",\"body\":$input.json('$')}"
}
},
"MethodResponses": [
{
"StatusCode": "200",
"ResponseModels": {
"application/json": "Empty"
}
},
{
"StatusCode": "500",
"ResponseModels": {
"application/json": "Empty"
}
},
{
"StatusCode": "403",
"ResponseModels": {
"application/json": "Empty"
}
}
]
}
},
"PassPermission": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"FunctionName": {
"Ref": "PassFunction"
},
"Action": "lambda:InvokeFunction",
"Principal": "apigateway.amazonaws.com",
"SourceArn": {
"Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${PassApi}/*"
}
}
},
"PassFunctionLogs": {
"Type": "AWS::Logs::LogGroup",
"Properties": {
"LogGroupName": {
"Fn::Sub": [
"/aws/lambda/${name}",
{
"name": {
"Fn::Sub": "${AWS::StackName}-Pass"
}
}
]
},
"RetentionInDays": 14
}
},
"PassFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"ZipFile": {
"Fn::Sub": [
"'use strict';\n\nconst crypto = require('crypto');\nconst { InvokeCommand, LambdaClient } = require('@aws-sdk/client-lambda');\nconst client = new LambdaClient();\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(Buffer.from(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 command = new InvokeCommand({\n FunctionName: '${Destination}',\n Payload: JSON.stringify(event.body),\n InvocationType: 'Event'\n });\n\n client.send(command)\n .then(() => callback(null, 'success'))\n .catch((err) => callback(err));\n};",
{
"WebhookSecret": "abc123"
}
]
}
},
"Description": {
"Fn::Sub": "Passthrough function for ${AWS::StackName}"
},
"FunctionName": {
"Fn::Sub": "${AWS::StackName}-Pass"
},
"Handler": "index.lambda",
"MemorySize": 128,
"Runtime": "nodejs22.x",
"Timeout": 30,
"Role": {
"Fn::GetAtt": [
"PassFunctionRole",
"Arn"
]
}
}
},
"PassFunctionErrorAlarm": {
"Type": "AWS::CloudWatch::Alarm",
"Properties": {
"AlarmName": {
"Fn::Sub": "${AWS::StackName}-PassFunction-Errors-${AWS::Region}"
},
"AlarmDescription": {
"Fn::Sub": [
"Error alarm for ${name} lambda function in ${AWS::StackName} stack",
{
"name": {
"Fn::Sub": "${AWS::StackName}-Pass"
}
}
]
},
"AlarmActions": [],
"Period": 60,
"EvaluationPeriods": 1,
"DatapointsToAlarm": 1,
"Statistic": "Sum",
"Threshold": 0,
"ComparisonOperator": "GreaterThanThreshold",
"TreatMissingData": "notBreaching",
"Namespace": "AWS/Lambda",
"Dimensions": [
{
"Name": "FunctionName",
"Value": {
"Ref": "PassFunction"
}
}
],
"MetricName": "Errors"
}
},
"PassFunctionLogPolicy": {
"Type": "AWS::IAM::Policy",
"DependsOn": "PassFunctionRole",
"Properties": {
"PolicyName": "PassFunction-lambda-log-access",
"Roles": [
{
"Ref": "PassFunctionRole"
}
],
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "logs:*",
"Resource": {
"Fn::GetAtt": [
"PassFunctionLogs",
"Arn"
]
}
}
]
}
}
},
"PassFunctionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Principal": {
"Service": {
"Fn::Sub": "lambda.amazonaws.com"
}
}
}
]
},
"Policies": [
{
"PolicyName": "main",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": {
"Fn::GetAtt": [
"Destination",
"Arn"
]
}
}
]
}
}
]
}
},
"DestinationLogs": {
"Type": "AWS::Logs::LogGroup",
"Properties": {
"LogGroupName": {
"Fn::Sub": [
"/aws/lambda/${name}",
{
"name": {
"Fn::Sub": "${AWS::StackName}-Destination"
}
}
]
},
"RetentionInDays": 14
}
},
"Destination": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"ZipFile": "module.exports.handler = (e, c, cb) => cb();"
},
"Description": {
"Fn::Sub": "Destination in the ${AWS::StackName} stack"
},
"FunctionName": {
"Fn::Sub": "${AWS::StackName}-Destination"
},
"Handler": "index.handler",
"MemorySize": 128,
"Runtime": "nodejs22.x",
"Timeout": 300,
"Role": {
"Fn::GetAtt": [
"DestinationRole",
"Arn"
]
}
}
},
"DestinationErrorAlarm": {
"Type": "AWS::CloudWatch::Alarm",
"Properties": {
"AlarmName": {
"Fn::Sub": "${AWS::StackName}-Destination-Errors-${AWS::Region}"
},
"AlarmDescription": {
"Fn::Sub": [
"Error alarm for ${name} lambda function in ${AWS::StackName} stack",
{
"name": {
"Fn::Sub": "${AWS::StackName}-Destination"
}
}
]
},
"AlarmActions": [],
"Period": 60,
"EvaluationPeriods": 5,
"DatapointsToAlarm": 1,
"Statistic": "Sum",
"Threshold": 0,
"ComparisonOperator": "GreaterThanThreshold",
"TreatMissingData": "notBreaching",
"Namespace": "AWS/Lambda",
"Dimensions": [
{
"Name": "FunctionName",
"Value": {
"Ref": "Destination"
}
}
],
"MetricName": "Errors"
}
},
"DestinationLogPolicy": {
"Type": "AWS::IAM::Policy",
"DependsOn": "DestinationRole",
"Properties": {
"PolicyName": "Destination-lambda-log-access",
"Roles": [
{
"Ref": "DestinationRole"
}
],
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "logs:*",
"Resource": {
"Fn::GetAtt": [
"DestinationLogs",
"Arn"
]
}
}
]
}
}
},
"DestinationRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Principal": {
"Service": {
"Fn::Sub": "lambda.amazonaws.com"
}
}
}
]
}
}
}
},
"Outputs": {
"PassEndpointOutput": {
"Description": "The HTTPS endpoint used to send github webhooks",
"Value": {
"Fn::Sub": "https://${PassApi}.execute-api.${AWS::Region}.amazonaws.com/hookshot/webhook"
}
},
"PassSecretOutput": {
"Description": "A secret key to give Github to use when signing webhook requests",
"Value": "abc123"
}
}
}