dd-trace
Version:
Datadog APM tracing client for JavaScript
159 lines (122 loc) • 3.68 kB
JavaScript
const addresses = require('./addresses')
const Limiter = require('../rate_limiter')
// default limiter, configurable with setRateLimit()
let limiter = new Limiter(100)
const REQUEST_HEADERS_PASSLIST = [
'accept',
'accept-encoding',
'accept-language',
'content-encoding',
'content-language',
'content-length',
'content-type',
'forwarded',
'forwarded-for',
'host',
'true-client-ip',
'user-agent',
'via',
'x-client-ip',
'x-cluster-client-ip',
'x-forwarded',
'x-forwarded-for',
'x-real-ip'
]
const RESPONSE_HEADERS_PASSLIST = [
'content-encoding',
'content-language',
'content-length',
'content-type'
]
function resolveHTTPRequest (context) {
if (!context) return {}
const headers = context.resolve(addresses.HTTP_INCOMING_HEADERS)
return {
remote_ip: context.resolve(addresses.HTTP_INCOMING_REMOTE_IP),
headers: filterHeaders(headers, REQUEST_HEADERS_PASSLIST, 'http.request.headers.')
}
}
function resolveHTTPResponse (context) {
if (!context) return {}
const headers = context.resolve(addresses.HTTP_INCOMING_RESPONSE_HEADERS)
return {
endpoint: context.resolve(addresses.HTTP_INCOMING_ENDPOINT),
headers: filterHeaders(headers, RESPONSE_HEADERS_PASSLIST, 'http.response.headers.')
}
}
function filterHeaders (headers, passlist, prefix) {
const result = {}
if (!headers) return result
for (let i = 0; i < passlist.length; ++i) {
const headerName = passlist[i]
if (headers[headerName]) {
result[`${prefix}${formatHeaderName(headerName)}`] = headers[headerName] + ''
}
}
return result
}
// TODO: this can be precomputed at start time
function formatHeaderName (name) {
return name
.trim()
.slice(0, 200)
.replace(/[^a-zA-Z0-9_\-:/]/g, '_')
.toLowerCase()
}
function reportAttack (attackData, store) {
const req = store && store.get('req')
const topSpan = req && req._datadog && req._datadog.span
if (!topSpan) return false
const currentTags = topSpan.context()._tags
const newTags = {
'appsec.event': 'true'
}
if (limiter.isAllowed()) {
newTags['manual.keep'] = 'true' // TODO: figure out how to keep appsec traces with sampling revamp
}
// TODO: maybe add this to format.js later (to take decision as late as possible)
if (!currentTags['_dd.origin']) {
newTags['_dd.origin'] = 'appsec'
}
const currentJson = currentTags['_dd.appsec.json']
// merge JSON arrays without parsing them
if (currentJson) {
newTags['_dd.appsec.json'] = currentJson.slice(0, -2) + ',' + attackData.slice(1, -1) + currentJson.slice(-2)
} else {
newTags['_dd.appsec.json'] = '{"triggers":' + attackData + '}'
}
const context = store.get('context')
if (context) {
const resolvedRequest = resolveHTTPRequest(context)
Object.assign(newTags, resolvedRequest.headers)
const ua = resolvedRequest.headers['http.request.headers.user-agent']
if (ua) {
newTags['http.useragent'] = ua
}
newTags['network.client.ip'] = resolvedRequest.remote_ip
}
topSpan.addTags(newTags)
}
function finishAttacks (req, context) {
const topSpan = req && req._datadog && req._datadog.span
if (!topSpan || !context) return false
const resolvedResponse = resolveHTTPResponse(context)
const newTags = resolvedResponse.headers
if (resolvedResponse.endpoint) {
newTags['http.endpoint'] = resolvedResponse.endpoint
}
topSpan.addTags(newTags)
}
function setRateLimit (rateLimit) {
limiter = new Limiter(rateLimit)
}
module.exports = {
resolveHTTPRequest,
resolveHTTPResponse,
filterHeaders,
formatHeaderName,
reportAttack,
finishAttacks,
setRateLimit
}