|
| 1 | +'use strict' |
| 2 | + |
| 3 | +const { HTTP_CLIENT_IP } = require('../../../../ext/tags') |
| 4 | + |
| 5 | +const log = require('../log') |
| 6 | +const addresses = require('./addresses') |
| 7 | +const Reporter = require('./reporter') |
| 8 | +const waf = require('./waf') |
| 9 | + |
| 10 | +// Tracks spans for which start-invocation has been processed, so that |
| 11 | +// end-invocation can gate correctly |
| 12 | +const activeInvocations = new WeakSet() |
| 13 | + |
| 14 | +/** |
| 15 | + * Maps pre-extracted HTTP data from the Lambda event to WAF addresses, |
| 16 | + * runs the WAF, and reports results on the span. |
| 17 | + * |
| 18 | + * @param {{ span: object, headers: Record<string, string>, method: string, path: string, |
| 19 | + * query: Record<string, string | string[]> | undefined, body: string | object | undefined, |
| 20 | + * isBase64Encoded: boolean, clientIp: string | undefined, |
| 21 | + * pathParams: Record<string, string> | undefined, |
| 22 | + * cookies: Record<string, string> | undefined, |
| 23 | + * route: string | undefined }} data |
| 24 | + */ |
| 25 | +function onLambdaStartInvocation (data) { |
| 26 | + try { |
| 27 | + const { span, headers, method, path, query, body, clientIp, pathParams, cookies } = data |
| 28 | + |
| 29 | + if (!span) { |
| 30 | + log.warn('[ASM] No span provided in Lambda start invocation') |
| 31 | + return |
| 32 | + } |
| 33 | + |
| 34 | + activeInvocations.add(span) |
| 35 | + |
| 36 | + span.setTag('_dd.appsec.enabled', 1) |
| 37 | + |
| 38 | + const persistent = {} |
| 39 | + |
| 40 | + if (path) { |
| 41 | + persistent[addresses.HTTP_INCOMING_URL] = path |
| 42 | + } |
| 43 | + |
| 44 | + if (method) { |
| 45 | + persistent[addresses.HTTP_INCOMING_METHOD] = method |
| 46 | + } |
| 47 | + |
| 48 | + if (headers) { |
| 49 | + // Cookie header is already stripped by the Lambda layer's event-data-extractor |
| 50 | + persistent[addresses.HTTP_INCOMING_HEADERS] = headers |
| 51 | + } |
| 52 | + |
| 53 | + if (clientIp) { |
| 54 | + span.setTag(HTTP_CLIENT_IP, clientIp) |
| 55 | + persistent[addresses.HTTP_CLIENT_IP] = clientIp |
| 56 | + } |
| 57 | + |
| 58 | + if (query) { |
| 59 | + persistent[addresses.HTTP_INCOMING_QUERY] = query |
| 60 | + } |
| 61 | + |
| 62 | + if (body !== undefined && body !== null) { |
| 63 | + persistent[addresses.HTTP_INCOMING_BODY] = body |
| 64 | + } |
| 65 | + |
| 66 | + if (pathParams) { |
| 67 | + persistent[addresses.HTTP_INCOMING_PARAMS] = pathParams |
| 68 | + } |
| 69 | + |
| 70 | + if (cookies) { |
| 71 | + persistent[addresses.HTTP_INCOMING_COOKIES] = cookies |
| 72 | + } |
| 73 | + |
| 74 | + waf.run({ persistent }, span, undefined, span) |
| 75 | + } catch (err) { |
| 76 | + log.error('[ASM] Error in Lambda start-invocation handler', err) |
| 77 | + } |
| 78 | +} |
| 79 | + |
| 80 | +/** |
| 81 | + * Maps response data to WAF addresses, runs a final WAF pass, |
| 82 | + * disposes the WAF context, and finishes the request report. |
| 83 | + * |
| 84 | + * @param {{ span: object, statusCode: string | undefined, |
| 85 | + * responseHeaders: Record<string, string> | undefined }} data |
| 86 | + */ |
| 87 | +function onLambdaEndInvocation (data) { |
| 88 | + try { |
| 89 | + const { span, statusCode, responseHeaders } = data |
| 90 | + |
| 91 | + if (!span) { |
| 92 | + log.warn('[ASM] No span provided in Lambda end invocation') |
| 93 | + return |
| 94 | + } |
| 95 | + |
| 96 | + if (!activeInvocations.has(span)) { |
| 97 | + return |
| 98 | + } |
| 99 | + |
| 100 | + activeInvocations.delete(span) |
| 101 | + |
| 102 | + let hasPersistentData = false |
| 103 | + const persistent = {} |
| 104 | + |
| 105 | + if (statusCode) { |
| 106 | + persistent[addresses.HTTP_INCOMING_RESPONSE_CODE] = String(statusCode) |
| 107 | + hasPersistentData = true |
| 108 | + } |
| 109 | + |
| 110 | + if (responseHeaders) { |
| 111 | + const filteredHeaders = { ...responseHeaders } |
| 112 | + delete filteredHeaders['set-cookie'] |
| 113 | + persistent[addresses.HTTP_INCOMING_RESPONSE_HEADERS] = filteredHeaders |
| 114 | + hasPersistentData = true |
| 115 | + } |
| 116 | + |
| 117 | + if (hasPersistentData) { |
| 118 | + waf.run({ persistent }, span, undefined, span) |
| 119 | + } |
| 120 | + |
| 121 | + waf.disposeContext(span) |
| 122 | + |
| 123 | + Reporter.finishRequest(span, null, {}, undefined, span) |
| 124 | + } catch (err) { |
| 125 | + log.error('[ASM] Error in Lambda end-invocation handler', err) |
| 126 | + } |
| 127 | +} |
| 128 | + |
| 129 | +module.exports = { |
| 130 | + onLambdaStartInvocation, |
| 131 | + onLambdaEndInvocation, |
| 132 | +} |
0 commit comments