Search code examples
amazon-web-servicesaws-lambda

AWS synthetic monitoring canary forbidden error invoking AWS Lambda Function URL


I am trying to invoke a Lambda function via its function URL from an AWS synthetic monitoring canary. When I provide my secret and access key in postman it works but not from the AWS synthetic canary. I am following the AWS official docs and providing lambda:InvokeFunctionUrl policy but am still getting a forbidden error.


Solution

  • i figured it out below is my synthic canary lambda where i am creating the aws signature to sign the http request. Any one having issue just change accessKey, secretKey, and URL

    const crypto = require('crypto');
    const synthetics = require('Synthetics');
    const log = require('SyntheticsLogger');
    const syntheticsConfiguration = synthetics.getConfiguration();
    
    const apiCanaryBlueprint = async function () {
        syntheticsConfiguration.setConfig({
            restrictedHeaders: [],
            restrictedUrlParameters: []
        });
    
        // AWS credentials from environment variables
        const accessKeyId = "${accessKeyId}";
        const secretAccessKey = "${secretAccessKey}";
        const endpointUrl = "${endpoint_url}";
    
        // Helper function to create AWS Signature Version 4 headers
        function signRequest(requestOptions, region, service) {
            const endpoint = new URL('https://' + requestOptions.hostname + requestOptions.path);
            const method = requestOptions.method || 'GET';
            const now = new Date();
            const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, '');
            const dateStamp = amzDate.slice(0, 8);
    
            // Canonical request
            const canonicalUri = endpoint.pathname;
            const canonicalQuerystring = '';
            const canonicalHeaders = 'host:' + endpoint.hostname + '\n' + 'x-amz-date:' + amzDate + '\n';
            const signedHeaders = 'host;x-amz-date';
            const payloadHash = crypto.createHash('sha256').update(requestOptions.body || '', 'utf8').digest('hex');
            const canonicalRequest = method + '\n' + canonicalUri + '\n' + canonicalQuerystring + '\n' + canonicalHeaders + '\n' + signedHeaders + '\n' + payloadHash;
    
            // String to sign
            const algorithm = 'AWS4-HMAC-SHA256';
            const credentialScope = dateStamp + '/' + region + '/' + service + '/aws4_request';
            const stringToSign = algorithm + '\n' + amzDate + '\n' + credentialScope + '\n' + crypto.createHash('sha256').update(canonicalRequest, 'utf8').digest('hex');
    
            // Calculate the signature
            const signingKey = getSignatureKey(secretAccessKey, dateStamp, region, service);
            const signature = crypto.createHmac('sha256', signingKey).update(stringToSign, 'utf8').digest('hex');
    
            // Add headers to request options
            requestOptions.headers['x-amz-date'] = amzDate;
            requestOptions.headers['Authorization'] = algorithm + ' Credential=' + accessKeyId + '/' + credentialScope + ', SignedHeaders=' + signedHeaders + ', Signature=' + signature;
        }
    
        // Helper function to derive the signing key
        function getSignatureKey(key, dateStamp, regionName, serviceName) {
            const kDate = crypto.createHmac('sha256', 'AWS4' + key).update(dateStamp).digest();
            const kRegion = crypto.createHmac('sha256', kDate).update(regionName).digest();
            const kService = crypto.createHmac('sha256', kRegion).update(serviceName).digest();
            const kSigning = crypto.createHmac('sha256', kService).update('aws4_request').digest();
            return kSigning;
        }
    
        const validateSuccessful = async function (res) {
            return new Promise((resolve, reject) => {
                if (res.statusCode < 200 || res.statusCode > 299) {
                    throw new Error(res.statusCode + ' ' + res.statusMessage);
                }
    
                let responseBody = '';
                res.on('data', (d) => {
                    responseBody += d;
                });
    
                res.on('end', () => {
                    resolve();
                });
            });
        }
    
        function extractRequestOptions(endpointUrl) {
            // Parse the URL using the URL constructor
            const parsedUrl = new URL(endpointUrl);
    
            // Create an object similar to requestOptionsStep1
            const requestOptions = {
                hostname: parsedUrl.hostname,
                method: "POST",
                path: parsedUrl.pathname,
                port: parsedUrl.port || '443',
                protocol: parsedUrl.protocol,
                body: "",
                headers: {}
            };
    
            return requestOptions;
        }
    
        // Set request option for Verify
        let requestOptionsStep1 = extractRequestOptions(endpointUrl);
    
        // Sign the request manually
        signRequest(requestOptionsStep1, 'ca-central-1', 'lambda');
    
        // Add the User-Agent header
        requestOptionsStep1['headers']['User-Agent'] = [synthetics.getCanaryUserAgentString(), requestOptionsStep1['headers']['User-Agent']].join(' ');
    
        let stepConfig1 = {
            includeRequestHeaders: false,
            includeResponseHeaders: false,
            includeRequestBody: false,
            includeResponseBody: false,
            continueOnHttpStepFailure: true
        };
    
        await synthetics.ex