UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

102 lines (77 loc) 3.32 kB
'use strict' const { format } = require('url') const { httpClientRequestStart, httpClientResponseFinish, } = require('../channels') const addresses = require('../addresses') const web = require('../../plugins/util/web') const { getActiveRequest } = require('../store') const waf = require('../waf') const downstream = require('../downstream_requests') const { updateRaspRuleMatchMetricTags } = require('../telemetry') const { RULE_TYPES, handleResult } = require('./utils') let config function enable (_config) { config = _config downstream.enable(_config) httpClientRequestStart.subscribe(analyzeSsrf) httpClientResponseFinish.subscribe(handleResponseFinish) } function disable () { downstream.disable() if (httpClientRequestStart.hasSubscribers) httpClientRequestStart.unsubscribe(analyzeSsrf) if (httpClientResponseFinish.hasSubscribers) httpClientResponseFinish.unsubscribe(handleResponseFinish) } function analyzeSsrf (ctx) { const req = getActiveRequest() const outgoingUrl = (ctx.args.options?.uri && format(ctx.args.options.uri)) ?? ctx.args.uri if (!req || !outgoingUrl) return // Determine if we should collect the response body based on sampling rate and redirect URL ctx.shouldCollectBody = downstream.shouldSampleBody(req, outgoingUrl) const requestAddresses = downstream.extractRequestData(ctx) const ephemeral = { [addresses.HTTP_OUTGOING_URL]: outgoingUrl, ...requestAddresses, } const raspRule = { type: RULE_TYPES.SSRF, variant: 'request' } const result = waf.run({ ephemeral }, req, raspRule) handleResult(result, req, web.getContext(req)?.res, ctx.abortController, config, raspRule) downstream.incrementDownstreamAnalysisCount(req) } /** * Finalizes body collection for the response and triggers RASP analysis. * @param {{ * ctx: object, * res: import('http').IncomingMessage, * body: string|Buffer|null * }} payload event payload from the channel. */ function handleResponseFinish ({ ctx, res, body }) { // downstream response object if (!res) return const originatingRequest = getActiveRequest() if (!originatingRequest) return // Skip body analysis for redirect responses const evaluateBody = ctx.shouldCollectBody && !downstream.handleRedirectResponse(originatingRequest, res) const responseBody = evaluateBody ? body : null runResponseEvaluation(res, originatingRequest, responseBody) } /** * Evaluates the downstream response and records telemetry. * @param {import('http').IncomingMessage} res incoming response from downstream service. * @param {import('http').IncomingMessage} req originating request. * @param {string|Buffer|null} responseBody collected downstream response body. */ function runResponseEvaluation (res, req, responseBody) { const responseAddresses = downstream.extractResponseData(res, responseBody) if (!Object.keys(responseAddresses).length) return const raspRule = { type: RULE_TYPES.SSRF, variant: 'response' } const result = waf.run({ ephemeral: responseAddresses }, req, raspRule) // TODO: this should be done in the waf functions directly instead of calling it everywhere const ruleTriggered = !!result?.events?.length if (ruleTriggered) { updateRaspRuleMatchMetricTags(req, raspRule, false, false) } } module.exports = { enable, disable }