UNPKG

@mapbox/cloudfriend

Version:

Helper functions for assembling CloudFormation templates in JavaScript

423 lines 11.5 kB
{ "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" } } }