I have a CloudFormation template creating a Cloudwatch Synthetics Canary. Part of the template has a Lambda embedded written in Node.js 2: syn-nodejs-2.0. I have a few parameters that are being passed into the CFT and I want to pass them into the node script to use the values of the website I'm trying to test. I'm pretty sure I can do this with something like this:
{ "Ref" : "${Param}" }
where ${Param} is the Cloudformation parameter I'm trying to reference, but that doesn't seem to work for me. Maybe I have a small syntax issue, or maybe I'm off base in my logic, I'm not really sure. The ultimate goal is to read the variables stored in SSM, but I haven't gotten to that point yet. Here is my code. The problem spot I'm having is near the end:
Parameters:
CanaryName:
Type: String
Default: my-canary
MaxLength: 21
HostName:
Type: String
Default: foo.bar.net
MaxLength: 128
Path:
Type: String
Default: /v1/status
MaxLength: 256
Port:
Type: Number
Default: 443
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
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource: "arn:aws:secretsmanager:*:MYACCOUNT:secret:*"
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: |
var synthetics = require('Synthetics');
const log = require('SyntheticsLogger');
const https = require('https');
const http = require('http');
const apiCanaryBlueprint = async function () {
const postData = "";
const verifyRequest = async function (requestOption) {
return new Promise((resolve, reject) => {
log.info("Making request with options: " + JSON.stringify(requestOption));
let req
if (requestOption.port === 443) {
req = https.request(requestOption);
} else {
req = http.request(requestOption);
}
req.on('response', (res) => {
log.info(`Status Code: ${res.statusCode}`)
log.info(`Response Headers: ${JSON.stringify(res.headers)}`)
// If the response status code is not a 2xx success code
if (res.statusCode < 200 || res.statusCode > 299) {
reject("Failed: " + requestOption.path);
}
res.on('data', (d) => {
log.info("Response: " + d);
});
res.on('end', () => {
resolve();
})
});
req.on('error', (error) => {
reject(error);
});
if (postData) {
req.write(postData);
}
req.end();
});
}
secret = "MYSECREYKEY";
const headers = {"Authorization":"Basic ${secret}"}
headers['User-Agent'] = [synthetics.getCanaryUserAgentString(), headers['User-Agent']].join(' ');
// PROBLEM SPOT: HOW TO ACCESS THE CFT PARAMETERS?
const requestOptions = `"hostname" : { "!Ref" : "$HostName" }, "method" : "GET", "path" : { "!Ref" : "$Path" }, "port" : { "!Ref" : "$Port" }`
requestOptions['headers'] = headers;
await verifyRequest(requestOptions);
};
exports.handler = async () => {
return await apiCanaryBlueprint();
};