To ensure the effectiveness of my lambdas, I constructed a series of canaries for testing purposes. These canaries follow a fat-lambda approach for simplicity, utilizing a single parameter to indicate which canary should be triggered.
const { isLambda } = require('../helpers/isLambda');
const { authorizerSuccess } = require('./authorizer-success');
const { authorizerFailure } = require('./authorizer-failure');
const log = require('SyntheticsLogger');
exports.handler = async () => {
const purpose = process.env.CANARY_PURPOSE;
log.info('Initiating canary execution: ', purpose);
switch (purpose) {
case 'authorizer-success':
await authorizerSuccess();
break;
case 'authorizer-failure':
await authorizerFailure();
break;
default:
throw new Error(`Unknown purpose: ${purpose}`);
}
log.info('Canary executed successfully');
};
if (!isLambda) {
exports.handler();
}
The authorization processes for success and failure are relatively straightforward; both involve invoking a Lambda function that validates the parameter and provides a payload with data validity information.
// authorizer-success.js
const { LambdaClient, InvokeCommand } = require('@aws-sdk/client-lambda');
const { jsonData } = require('../helpers/getVariables');
const log = require('SyntheticsLogger');
const authorizerSuccess = async function () {
if (!jsonData.authorizer_arn) {
throw new Error('Authorizer ARN is not defined');
}
if (!jsonData.authorizer_payload) {
throw new Error('Authorizer payload is not defined');
}
const invokeParams = {
FunctionName: jsonData.authorizer_arn,
Payload: JSON.stringify({ ...jsonData.authorizer_payload }),
InvocationType: 'RequestResponse',
};
const input = new InvokeCommand(invokeParams);
log.info({ invokeParams });
const client = new LambdaClient();
const data = await client.send(input);
log.info({ data });
const jsonString = Buffer.from(data.Payload).toString('utf8');
log.info({ jsonString });
if (!jsonString || jsonString.length === 0) {
throw new Error('No response from authorizer');
}
const parsedData = JSON.parse(jsonString);
log.info({ parsedData });
if (parsedData.statusCode === 403) {
throw new Error('Login failed, check credentials');
}
return 'Successful check on Authorizer-success canary!';
};
exports.authorizerSuccess = authorizerSuccess;
The autorization-fail canary performs similarly but alters the payload with incorrect values.
Upon deployment, I observed that they consistently executed sequentially, with the second one always returning an empty payload in the Lambda response.
// log.info({data})
2024-05-22T19:27:15.283Z aaaa8d-a0af-4248-843e-3aaaa11b INFO {
data: {
'$metadata': {
httpStatusCode: 200,
requestId: '8aaaad-3408-4caf-90e2-31aaaa7a',
extendedRequestId: undefined,
cfId: undefined,
attempts: 1,
totalRetryDelay: 0
},
ExecutedVersion: '$LATEST',
Payload: Uint8ArrayBlobAdapter(0) [Uint8Array] [],
StatusCode: 200
}
}
Interestingly, the logs within the authorizer Lambda correctly display the response Payload.
The timestamps within the authorizer Lambda appear accurate: displaying the response payload, followed by the 'invoker' Lambda receiving the response (sans Payload) approximately 10 milliseconds later.
Furthermore, I experimented with updating the canary switch to concurrently call authorizerSuccess()
multiple times. In this scenario, when invoking the lambda 5 times, only 2 of them randomly receive the response payload, while the rest receive an empty payload response. The statusCode consistently registers as 200.