Skip to content

Instantly share code, notes, and snippets.

@zcapper
Created July 1, 2020 02:05
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zcapper/82a9ef2ad8dc5156c77d22dde15d6391 to your computer and use it in GitHub Desktop.
Save zcapper/82a9ef2ad8dc5156c77d22dde15d6391 to your computer and use it in GitHub Desktop.
AWS Synthetics Canary CloudFormation template
Parameters:
CanaryName:
Type: String
Default: my-canary
MaxLength: 21
Resources:
CloudWatchSyntheticsRole:
Type: AWS::IAM::Role
Properties:
RoleName:
Fn::Sub: CloudWatchSyntheticsRole-${CanaryName}-${AWS::Region}
Description: CloudWatch Synthetics lambda execution role for running canaries
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Condition: {}
RolePermissions:
Type: AWS::IAM::Policy
Properties:
Roles:
- Ref: CloudWatchSyntheticsRole
PolicyName:
Fn::Sub: CloudWatchSyntheticsPolicy-${CanaryName}-${AWS::Region}
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- s3:PutObject
- s3:GetBucketLocation
Resource:
- Fn::Sub: arn:aws:s3:::${ResultsBucket}/*
- Effect: Allow
Action:
- logs:CreateLogStream
- logs:PutLogEvents
- logs:CreateLogGroup
Resource:
- Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/cwsyn-test-*
- Effect: Allow
Action:
- s3:ListAllMyBuckets
Resource: '*'
- Effect: Allow
Resource: '*'
Action: cloudwatch:PutMetricData
Condition:
StringEquals:
cloudwatch:namespace: CloudWatchSynthetics
ResultsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName:
Fn::Sub: cw-syn-results-${AWS::AccountId}-${AWS::Region}
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
Canary:
Type: AWS::Synthetics::Canary
Properties:
Name:
Fn::Sub: ${CanaryName}
Code:
Handler: exports.handler
Script: |
const https = require('https')
let url = "https://docs.aws.amazon.com/lambda/latest/dg/welcome.html"
exports.handler = async function(event) {
const promise = new Promise(function(resolve, reject) {
https.get(url, (res) => {
resolve(res.statusCode)
}).on('error', (e) => {
reject(Error(e))
})
})
return promise
}
ExecutionRoleArn:
Fn::GetAtt:
- CloudWatchSyntheticsRole
- Arn
RuntimeVersion: syn-1.0
RunConfig:
TimeoutInSeconds: 60
ArtifactS3Location:
Fn::Join:
- ''
- - s3://
- Ref: ResultsBucket
StartCanaryAfterCreation: True
Schedule:
Expression: rate(1 minute) # every minute
DurationInSeconds: 0 # run indefinitely
SuccessRetentionPeriod: 90
FailureRetentionPeriod: 180
Outputs:
CanaryRoleArn:
Value:
Fn::GetAtt:
- CloudWatchSyntheticsRole
- Arn
ResultsBucketArn:
Value:
Fn::GetAtt:
- ResultsBucket
- Arn
ResultsBucketName:
Value:
Ref: ResultsBucket
@sskorol
Copy link

sskorol commented Dec 2, 2020

Hi @zcapper, thanks for the snippet. Have you tried to read canary code from S3? I couldn't make it work due to some weird issues. Amazon tries to read the handler via "customerCanary.handler" path. However, I specify a completely different one in the template. As a result I just see a classic js error: "customerCanary.handler is not a function". Wondering if you already tried that? And if yes, what was your template syntax and S3 zip structure?

@zcapper
Copy link
Author

zcapper commented Dec 4, 2020

@sskorol Yep! I've put up an end to end example here:
https://github.com/zcapper/aws-snippets/tree/master/synthetics-cloudformation

The key is that you need to have the following folder structure:

my-canary.zip
└── nodejs
    └── node_modules
        ├── index.js
        ├── ...
        └── ...

This is for a handler named index.handler. I believe your handler name needs to match the .js file name.
There's an example of the JS file structure in the repo 😄

@sskorol
Copy link

sskorol commented Dec 4, 2020

@zcapper thanks! Will try.

@dduvnjak
Copy link

Your policy will cause this error in the canary:

Unable to fetch S3 bucket location: Access Denied. Fallback to S3 client in current region: us-west-2.

The s3:GetBucketLocation action only has effect if the resource is the actual bucket, not a path inside the bucket, including /*. The statement should look like this:

- Effect: Allow
  Action:
    - s3:GetBucketLocation
  Resource:
    - Fn::Sub: arn:aws:s3:::${ResultsBucket}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment