Skip to content

Commit

Permalink
fix: Improved AWS Lambda event detection (#2498)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsumners-nr authored Aug 21, 2024
1 parent c395779 commit 5e8b260
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 114 deletions.
67 changes: 38 additions & 29 deletions lib/serverless/api-gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,38 +106,45 @@ function isLambdaProxyEvent(event) {
return isGatewayV1Event(event) || isGatewayV2Event(event)
}

function isGatewayV1Event(event) {
let result = false

if (event?.version === '1.0') {
result = true
} else if (
typeof event?.path === 'string' &&
(event.headers ?? event.multiValueHeaders) &&
typeof event?.httpMethod === 'string'
// eslint-disable-next-line sonarjs/no-duplicated-branches
) {
result = true
}
const v1Keys = [
'body',
'headers',
'httpMethod',
'isBase64Encoded',
'multiValueHeaders',
'multiValueQueryStringParameters',
'path',
'pathParameters',
'queryStringParameters',
'requestContext',
'resource',
'stageVariables',
'version'
].join(',')

return result
function isGatewayV1Event(event) {
const keys = Object.keys(event).sort().join(',')
return keys === v1Keys && event?.version === '1.0'
}

function isGatewayV2Event(event) {
let result = false

if (event?.version === '2.0') {
result = true
} else if (
typeof event?.requestContext?.http?.path === 'string' &&
Object.prototype.toString.call(event.headers) === '[object Object]' &&
typeof event?.requestContext?.http?.method === 'string'
// eslint-disable-next-line sonarjs/no-duplicated-branches
) {
result = true
}
const v2Keys = [
'body',
'cookies',
'headers',
'isBase64Encoded',
'pathParameters',
'queryStringParameters',
'rawPath',
'rawQueryString',
'requestContext',
'routeKey',
'stageVariables',
'version'
].join(',')

return result
function isGatewayV2Event(event) {
const keys = Object.keys(event).sort().join(',')
return keys === v2Keys && event?.version === '2.0'
}

/**
Expand All @@ -155,5 +162,7 @@ module.exports = {
LambdaProxyWebRequest,
LambdaProxyWebResponse,
isLambdaProxyEvent,
isValidLambdaProxyResponse
isValidLambdaProxyResponse,
isGatewayV1Event,
isGatewayV2Event
}
68 changes: 1 addition & 67 deletions test/unit/serverless/api-gateway-v2.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,73 +13,7 @@ const AwsLambda = require('../../../lib/serverless/aws-lambda')

const ATTR_DEST = require('../../../lib/config/attribute-filter').DESTINATIONS

// https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
const v2Event = {
version: '2.0',
routeKey: '$default',
rawPath: '/my/path',
rawQueryString: 'parameter1=value1&parameter1=value2&parameter2=value',
cookies: ['cookie1', 'cookie2'],
headers: {
header1: 'value1',
header2: 'value1,value2',
accept: 'application/json'
},
queryStringParameters: {
parameter1: 'value1,value2',
parameter2: 'value',
name: 'me',
team: 'node agent'
},
requestContext: {
accountId: '123456789012',
apiId: 'api-id',
authentication: {
clientCert: {
clientCertPem: 'CERT_CONTENT',
subjectDN: 'www.example.com',
issuerDN: 'Example issuer',
serialNumber: 'a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1',
validity: {
notBefore: 'May 28 12:30:02 2019 GMT',
notAfter: 'Aug 5 09:36:04 2021 GMT'
}
}
},
authorizer: {
jwt: {
claims: {
claim1: 'value1',
claim2: 'value2'
},
scopes: ['scope1', 'scope2']
}
},
domainName: 'id.execute-api.us-east-1.amazonaws.com',
domainPrefix: 'id',
http: {
method: 'POST',
path: '/my/path',
protocol: 'HTTP/1.1',
sourceIp: '192.0.2.1',
userAgent: 'agent'
},
requestId: 'id',
routeKey: '$default',
stage: '$default',
time: '12/Mar/2020:19:03:58 +0000',
timeEpoch: 1583348638390
},
body: 'Hello from Lambda',
pathParameters: {
parameter1: 'value1'
},
isBase64Encoded: false,
stageVariables: {
stageVariable1: 'value1',
stageVariable2: 'value2'
}
}
const { gatewayV2Event: v2Event } = require('./fixtures')

tap.beforeEach((t) => {
// This env var suppresses console output we don't need to inspect.
Expand Down
181 changes: 181 additions & 0 deletions test/unit/serverless/fixtures.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
* Copyright 2024 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

// https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
const gatewayV1Event = {
version: '1.0',
resource: '/my/path',
path: '/my/path',
httpMethod: 'GET',
headers: {
header1: 'value1',
header2: 'value2'
},
multiValueHeaders: {
header1: ['value1'],
header2: ['value1', 'value2']
},
queryStringParameters: {
parameter1: 'value1',
parameter2: 'value'
},
multiValueQueryStringParameters: {
parameter1: ['value1', 'value2'],
parameter2: ['value']
},
requestContext: {
accountId: '123456789012',
apiId: 'id',
authorizer: {
claims: null,
scopes: null
},
domainName: 'id.execute-api.us-east-1.amazonaws.com',
domainPrefix: 'id',
extendedRequestId: 'request-id',
httpMethod: 'GET',
identity: {
accessKey: null,
accountId: null,
caller: null,
cognitoAuthenticationProvider: null,
cognitoAuthenticationType: null,
cognitoIdentityId: null,
cognitoIdentityPoolId: null,
principalOrgId: null,
sourceIp: '192.0.2.1',
user: null,
userAgent: 'user-agent',
userArn: null,
clientCert: {
clientCertPem: 'CERT_CONTENT',
subjectDN: 'www.example.com',
issuerDN: 'Example issuer',
serialNumber: 'a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1',
validity: {
notBefore: 'May 28 12:30:02 2019 GMT',
notAfter: 'Aug 5 09:36:04 2021 GMT'
}
}
},
path: '/my/path',
protocol: 'HTTP/1.1',
requestId: 'id=',
requestTime: '04/Mar/2020:19:15:17 +0000',
requestTimeEpoch: 1583349317135,
resourceId: null,
resourcePath: '/my/path',
stage: '$default'
},
pathParameters: null,
stageVariables: null,
body: 'Hello from Lambda!',
isBase64Encoded: false
}

// https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
const gatewayV2Event = {
version: '2.0',
routeKey: '$default',
rawPath: '/my/path',
rawQueryString: 'parameter1=value1&parameter1=value2&parameter2=value',
cookies: ['cookie1', 'cookie2'],
headers: {
header1: 'value1',
header2: 'value1,value2',
accept: 'application/json'
},
queryStringParameters: {
parameter1: 'value1,value2',
parameter2: 'value',
name: 'me',
team: 'node agent'
},
requestContext: {
accountId: '123456789012',
apiId: 'api-id',
authentication: {
clientCert: {
clientCertPem: 'CERT_CONTENT',
subjectDN: 'www.example.com',
issuerDN: 'Example issuer',
serialNumber: 'a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1',
validity: {
notBefore: 'May 28 12:30:02 2019 GMT',
notAfter: 'Aug 5 09:36:04 2021 GMT'
}
}
},
authorizer: {
jwt: {
claims: {
claim1: 'value1',
claim2: 'value2'
},
scopes: ['scope1', 'scope2']
}
},
domainName: 'id.execute-api.us-east-1.amazonaws.com',
domainPrefix: 'id',
http: {
method: 'POST',
path: '/my/path',
protocol: 'HTTP/1.1',
sourceIp: '192.0.2.1',
userAgent: 'agent'
},
requestId: 'id',
routeKey: '$default',
stage: '$default',
time: '12/Mar/2020:19:03:58 +0000',
timeEpoch: 1583348638390
},
body: 'Hello from Lambda',
pathParameters: {
parameter1: 'value1'
},
isBase64Encoded: false,
stageVariables: {
stageVariable1: 'value1',
stageVariable2: 'value2'
}
}

// Event used when one Lambda directly invokes another Lambda.
// https://docs.aws.amazon.com/lambda/latest/dg/invocation-async-retain-records.html#invocation-async-destinations
const lambaV1InvocationEvent = {
version: '1.0',
timestamp: '2019-11-14T18:16:05.568Z',
requestContext: {
requestId: 'e4b46cbf-b738-xmpl-8880-a18cdf61200e',
functionArn: 'arn:aws:lambda:us-east-2:123456789012:function:my-function:$LATEST',
condition: 'RetriesExhausted',
approximateInvokeCount: 3
},
requestPayload: {
ORDER_IDS: [
'9e07af03-ce31-4ff3-xmpl-36dce652cb4f',
'637de236-e7b2-464e-xmpl-baf57f86bb53',
'a81ddca6-2c35-45c7-xmpl-c3a03a31ed15'
]
},
responseContext: {
statusCode: 200,
executedVersion: '$LATEST',
functionError: 'Unhandled'
},
responsePayload: {
errorMessage:
'RequestId: e4b46cbf-b738-xmpl-8880-a18cdf61200e Process exited before completing request'
}
}

module.exports = {
gatewayV1Event,
gatewayV2Event,
lambaV1InvocationEvent
}
Loading

0 comments on commit 5e8b260

Please sign in to comment.