Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report general tags and metrics #5335

Merged
merged 59 commits into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
a2100ff
update native appsec
IlyasShabi Feb 20, 2025
6a55c87
report appsec block failed
IlyasShabi Feb 20, 2025
bc78fc2
report truncation tags
IlyasShabi Feb 20, 2025
4580619
add error blocking log
IlyasShabi Feb 24, 2025
4fd5bcf
failed graphql blocking tag
IlyasShabi Feb 24, 2025
747211f
report after block
IlyasShabi Feb 24, 2025
57e7a4a
add waf timeout span tag
IlyasShabi Feb 24, 2025
b182a41
add rasp timeout span tag
IlyasShabi Feb 24, 2025
1631647
add waf error tag
IlyasShabi Feb 24, 2025
bfd0e2c
add rasp error tag
IlyasShabi Feb 24, 2025
bc3f245
remove raspRule from waf run call
IlyasShabi Feb 24, 2025
21a301a
add waf wrapper tests
IlyasShabi Feb 24, 2025
eaea97b
report metrics after blocking
IlyasShabi Feb 25, 2025
6ed1996
call runWaf
IlyasShabi Feb 26, 2025
2794d70
rasp timout test
IlyasShabi Feb 26, 2025
882120c
add waf/rasp errors and timeout
IlyasShabi Feb 26, 2025
96849b5
report metrics after catch on graphql
IlyasShabi Feb 26, 2025
4a9fc38
return false if waf result is not defined
IlyasShabi Feb 27, 2025
b13b0f5
graphql blocking action
IlyasShabi Feb 27, 2025
83c753c
graphql report metrics
IlyasShabi Feb 27, 2025
b12a6e9
check if request data exist first
IlyasShabi Feb 27, 2025
0e46435
user tracking blocking action
IlyasShabi Feb 27, 2025
da29930
waf and rasp timeout
IlyasShabi Feb 27, 2025
9af7e1d
fix sql injection tests
IlyasShabi Feb 27, 2025
1ea5a80
add comment for waf timeout
IlyasShabi Feb 27, 2025
fdd94b6
keep only waf error test
IlyasShabi Feb 27, 2025
45fb27b
Merge branch 'master' into ishabi/general-tags
IlyasShabi Feb 27, 2025
d57e33d
fallback to dummy BlockList for cypress 6
IlyasShabi Feb 27, 2025
4a44a86
fix linter
IlyasShabi Feb 27, 2025
e51917c
add waf error test
IlyasShabi Feb 28, 2025
2a141b5
fix rasp resources path
IlyasShabi Feb 28, 2025
a7617a3
fix waf error on windows
IlyasShabi Feb 28, 2025
5c2df8e
fix waf error on windows path
IlyasShabi Feb 28, 2025
b321418
remove waf errors test file
IlyasShabi Feb 28, 2025
e835103
Merge branch 'master' into ishabi/general-tags
IlyasShabi Feb 28, 2025
327f239
report waf metrics on api security schema extraction
IlyasShabi Mar 3, 2025
d0d72c5
report metrics inside run waf
IlyasShabi Mar 5, 2025
2df48d2
user blocking tests
IlyasShabi Mar 5, 2025
2560c74
add reporter tests
IlyasShabi Mar 5, 2025
95ffc19
Merge branch 'master' into ishabi/general-tags
IlyasShabi Mar 5, 2025
1e236f2
linter
IlyasShabi Mar 5, 2025
32b629b
add test for block failure
IlyasShabi Mar 5, 2025
f05d5f4
use settag instead of addtags
IlyasShabi Mar 5, 2025
f290905
metrics order
IlyasShabi Mar 5, 2025
1e2f50a
fix linter and test
IlyasShabi Mar 5, 2025
e779cb2
remove return null
IlyasShabi Mar 6, 2025
082cbf4
add waf context run stubs
IlyasShabi Mar 6, 2025
29dc7ae
waf error code readability
IlyasShabi Mar 6, 2025
6465441
fix timers
IlyasShabi Mar 6, 2025
3a4f212
report metrics on waf failure
IlyasShabi Mar 6, 2025
e4a6023
fix metrics
IlyasShabi Mar 6, 2025
d78502e
remove run waf function
IlyasShabi Mar 6, 2025
2ec60f7
add unit tests
IlyasShabi Mar 6, 2025
14af79a
add telemetry tests
IlyasShabi Mar 6, 2025
afe2e55
test message
IlyasShabi Mar 6, 2025
3804308
replace total runtime with duration
IlyasShabi Mar 6, 2025
bb3e807
reporter test
IlyasShabi Mar 6, 2025
e08217d
test title
IlyasShabi Mar 6, 2025
ca6ae33
add more tests
IlyasShabi Mar 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
},
"dependencies": {
"@datadog/libdatadog": "^0.5.0",
"@datadog/native-appsec": "8.4.0",
"@datadog/native-appsec": "8.5.0",
"@datadog/native-iast-rewriter": "2.8.0",
"@datadog/native-iast-taint-tracking": "3.3.0",
"@datadog/native-metrics": "^3.1.0",
Expand Down
39 changes: 23 additions & 16 deletions packages/dd-trace/src/appsec/blocking.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,29 +100,36 @@ function getBlockingData (req, specificType, actionParameters) {
}

function block (req, res, rootSpan, abortController, actionParameters = defaultBlockingActionParameters) {
if (res.headersSent) {
log.warn('[ASM] Cannot send blocking response when headers have already been sent')
return
}
try {
if (res.headersSent) {
log.warn('[ASM] Cannot send blocking response when headers have already been sent')

const { body, headers, statusCode } = getBlockingData(req, null, actionParameters)
throw new Error('Headers have already been sent')
}

rootSpan.addTags({
'appsec.blocked': 'true'
})
const { body, headers, statusCode } = getBlockingData(req, null, actionParameters)

for (const headerName of res.getHeaderNames()) {
res.removeHeader(headerName)
}
for (const headerName of res.getHeaderNames()) {
res.removeHeader(headerName)
}

res.writeHead(statusCode, headers)
res.writeHead(statusCode, headers)

// this is needed to call the original end method, since express-session replaces it
res.constructor.prototype.end.call(res, body)
// this is needed to call the original end method, since express-session replaces it
res.constructor.prototype.end.call(res, body)

responseBlockedSet.add(res)
rootSpan.setTag('appsec.blocked', 'true')

abortController?.abort()
responseBlockedSet.add(res)
abortController?.abort()

return true
} catch (err) {
rootSpan?.setTag('_dd.appsec.block.failed', 1)
log.error('[ASM] Blocking error', err)

return false
}
}

function getBlockingAction (actions) {
Expand Down
19 changes: 13 additions & 6 deletions packages/dd-trace/src/appsec/graphql.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {
getBlockingData,
getBlockingAction
} = require('./blocking')
const log = require('../log')
const waf = require('./waf')
const addresses = require('./addresses')
const web = require('../plugins/util/web')
Expand Down Expand Up @@ -94,14 +95,20 @@ function beforeWriteApolloGraphqlResponse ({ abortController, abortData }) {
const rootSpan = web.root(req)
if (!rootSpan) return

const blockingData = getBlockingData(req, specificBlockingTypes.GRAPHQL, requestData.wafAction)
abortData.statusCode = blockingData.statusCode
abortData.headers = blockingData.headers
abortData.message = blockingData.body
try {
const blockingData = getBlockingData(req, specificBlockingTypes.GRAPHQL, requestData.wafAction)
abortData.statusCode = blockingData.statusCode
abortData.headers = blockingData.headers
abortData.message = blockingData.body

rootSpan.setTag('appsec.blocked', 'true')
rootSpan.setTag('appsec.blocked', 'true')

abortController?.abort()
abortController?.abort()
} catch (err) {
rootSpan.setTag('_dd.appsec.block.failed', 1)

log.error('[ASM] Blocking error', err)
}
}

graphqlRequestData.delete(req)
Expand Down
1 change: 0 additions & 1 deletion packages/dd-trace/src/appsec/rasp/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ function handleResult (actions, req, res, abortController, config) {

const blockingAction = getBlockingAction(actions)
if (blockingAction) {
const rootSpan = web.root(req)
// Should block only in express
if (rootSpan?.context()._name === 'express.request') {
const abortError = new DatadogRaspAbortError(req, res, blockingAction)
Expand Down
35 changes: 35 additions & 0 deletions packages/dd-trace/src/appsec/reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,34 @@ function reportWafInit (wafVersion, rulesVersion, diagnosticsRules = {}) {
function reportMetrics (metrics, raspRule) {
const store = storage('legacy').getStore()
const rootSpan = store?.req && web.root(store.req)

if (!rootSpan) return

if (metrics.rulesVersion) {
rootSpan.setTag('_dd.appsec.event_rules.version', metrics.rulesVersion)
}

if (raspRule) {
updateRaspRequestsMetricTags(metrics, store.req, raspRule)
} else {
updateWafRequestsMetricTags(metrics, store.req)
}

reportTruncationMetrics(rootSpan, metrics)
}

function reportTruncationMetrics (rootSpan, metrics) {
if (metrics.maxTruncatedString) {
rootSpan.setTag('_dd.appsec.truncated.string_length', metrics.maxTruncatedString)
}

if (metrics.maxTruncatedContainerSize) {
rootSpan.setTag('_dd.appsec.truncated.container_size', metrics.maxTruncatedContainerSize)
}

if (metrics.maxTruncatedContainerDepth) {
rootSpan.setTag('_dd.appsec.truncated.container_depth', metrics.maxTruncatedContainerDepth)
}
}

function reportAttack (attackData) {
Expand Down Expand Up @@ -189,6 +207,7 @@ function finishRequest (req, res) {
}

const metrics = getRequestMetrics(req)

if (metrics?.duration) {
rootSpan.setTag('_dd.appsec.waf.duration', metrics.duration)
}
Expand All @@ -197,6 +216,14 @@ function finishRequest (req, res) {
rootSpan.setTag('_dd.appsec.waf.duration_ext', metrics.durationExt)
}

if (metrics?.wafErrorCode) {
rootSpan.setTag('_dd.appsec.waf.error', metrics.wafErrorCode)
}

if (metrics?.wafTimeouts) {
rootSpan.setTag('_dd.appsec.waf.timeouts', metrics.wafTimeouts)
}

if (metrics?.raspDuration) {
rootSpan.setTag('_dd.appsec.rasp.duration', metrics.raspDuration)
}
Expand All @@ -205,6 +232,14 @@ function finishRequest (req, res) {
rootSpan.setTag('_dd.appsec.rasp.duration_ext', metrics.raspDurationExt)
}

if (metrics?.raspErrorCode) {
rootSpan.setTag('_dd.appsec.rasp.error', metrics.raspErrorCode)
}

if (metrics?.raspTimeouts) {
rootSpan.setTag('_dd.appsec.rasp.timeout', metrics.raspTimeouts)
}

if (metrics?.raspEvalCount) {
rootSpan.setTag('_dd.appsec.rasp.rule.eval', metrics.raspEvalCount)
}
Expand Down
4 changes: 1 addition & 3 deletions packages/dd-trace/src/appsec/sdk/user_blocking.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ function blockRequest (tracer, req, res) {
return false
}

block(req, res, rootSpan)

return true
return block(req, res, rootSpan)
}

module.exports = {
Expand Down
6 changes: 5 additions & 1 deletion packages/dd-trace/src/appsec/telemetry/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ function newStore () {
durationExt: 0,
raspDuration: 0,
raspDurationExt: 0,
raspEvalCount: 0
raspEvalCount: 0,
wafTimeouts: 0,
raspTimeouts: 0,
wafErrorCode: null,
raspErrorCode: null
}
}
}
Expand Down
17 changes: 16 additions & 1 deletion packages/dd-trace/src/appsec/telemetry/rasp.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,25 @@ const { DD_TELEMETRY_REQUEST_METRICS } = require('./common')

const appsecMetrics = telemetryMetrics.manager.namespace('appsec')

function addRaspRequestMetrics (store, { duration, durationExt }) {
function addRaspRequestMetrics (store, { duration, durationExt, wafTimeout, errorCode }) {
store[DD_TELEMETRY_REQUEST_METRICS].raspDuration += duration || 0
store[DD_TELEMETRY_REQUEST_METRICS].raspDurationExt += durationExt || 0
store[DD_TELEMETRY_REQUEST_METRICS].raspEvalCount++

if (wafTimeout) {
store[DD_TELEMETRY_REQUEST_METRICS].raspTimeouts++
}

if (errorCode) {
if (store[DD_TELEMETRY_REQUEST_METRICS].raspErrorCode) {
store[DD_TELEMETRY_REQUEST_METRICS].raspErrorCode = Math.max(
errorCode,
store[DD_TELEMETRY_REQUEST_METRICS].raspErrorCode
)
} else {
store[DD_TELEMETRY_REQUEST_METRICS].raspErrorCode = errorCode
}
}
}

function trackRaspMetrics (metrics, raspRule) {
Expand Down
17 changes: 16 additions & 1 deletion packages/dd-trace/src/appsec/telemetry/waf.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,24 @@ const appsecMetrics = telemetryMetrics.manager.namespace('appsec')

const DD_TELEMETRY_WAF_RESULT_TAGS = Symbol('_dd.appsec.telemetry.waf.result.tags')

function addWafRequestMetrics (store, { duration, durationExt }) {
function addWafRequestMetrics (store, { duration, durationExt, wafTimeout, errorCode }) {
store[DD_TELEMETRY_REQUEST_METRICS].duration += duration || 0
store[DD_TELEMETRY_REQUEST_METRICS].durationExt += durationExt || 0

if (wafTimeout) {
store[DD_TELEMETRY_REQUEST_METRICS].wafTimeouts++
}

if (errorCode) {
if (store[DD_TELEMETRY_REQUEST_METRICS].wafErrorCode) {
store[DD_TELEMETRY_REQUEST_METRICS].wafErrorCode = Math.max(
errorCode,
store[DD_TELEMETRY_REQUEST_METRICS].wafErrorCode
)
} else {
store[DD_TELEMETRY_REQUEST_METRICS].wafErrorCode = errorCode
}
}
}

function trackWafDurations ({ duration, durationExt }, versionsTags) {
Expand Down
56 changes: 43 additions & 13 deletions packages/dd-trace/src/appsec/waf/waf_context_wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class WAFContextWrapper {
this.wafTimeout = wafTimeout
this.wafVersion = wafVersion
this.rulesVersion = rulesVersion
this.addressesToSkip = new Set()
this.knownAddresses = knownAddresses
this.addressesToSkip = new Set()
this.cachedUserIdActions = new Map()
}

Expand Down Expand Up @@ -77,13 +77,44 @@ class WAFContextWrapper {

if (!payloadHasData) return

const metrics = {
rulesVersion: this.rulesVersion,
wafVersion: this.wafVersion,
wafTimeout: false,
duration: 0,
durationExt: 0,
blockTriggered: false,
ruleTriggered: false,
errorCode: null,
maxTruncatedString: null,
maxTruncatedContainerSize: null,
maxTruncatedContainerDepth: null
}

try {
const start = process.hrtime.bigint()

const result = this.ddwafContext.run(payload, this.wafTimeout)

const end = process.hrtime.bigint()

metrics.durationExt = parseInt(end - start) / 1e3

if (typeof result.errorCode === 'number' && result.errorCode < 0) {
const error = new Error('WAF code error')
error.errorCode = result.errorCode

throw error
}

if (result.metrics) {
const { maxTruncatedString, maxTruncatedContainerSize, maxTruncatedContainerDepth } = result.metrics

if (maxTruncatedString) metrics.maxTruncatedString = maxTruncatedString
if (maxTruncatedContainerSize) metrics.maxTruncatedContainerSize = maxTruncatedContainerSize
if (maxTruncatedContainerDepth) metrics.maxTruncatedContainerDepth = maxTruncatedContainerDepth
}

this.addressesToSkip = newAddressesToSkip

const ruleTriggered = !!result.events?.length
Expand All @@ -96,29 +127,28 @@ class WAFContextWrapper {
this.setUserIdCache(userId, result)
}

Reporter.reportMetrics({
duration: result.totalRuntime / 1e3,
durationExt: parseInt(end - start) / 1e3,
rulesVersion: this.rulesVersion,
ruleTriggered,
blockTriggered,
wafVersion: this.wafVersion,
wafTimeout: result.timeout
}, raspRule)
metrics.duration = result.totalRuntime / 1e3
metrics.blockTriggered = blockTriggered
metrics.ruleTriggered = ruleTriggered
metrics.wafTimeout = result.timeout

if (ruleTriggered) {
Reporter.reportAttack(JSON.stringify(result.events))
}

Reporter.reportDerivatives(result.derivatives)

return result.actions
} catch (err) {
log.error('[ASM] Error while running the AppSec WAF', err)

metrics.errorCode = err.errorCode ?? -127
} finally {
if (wafRunFinished.hasSubscribers) {
wafRunFinished.publish({ payload })
}

return result.actions
} catch (err) {
log.error('[ASM] Error while running the AppSec WAF', err)
Reporter.reportMetrics(metrics, raspRule)
}
}

Expand Down
Loading
Loading