Skip to content

Commit

Permalink
Pass resolver address as ephemeral type (#3897)
Browse files Browse the repository at this point in the history
* Update waf bindings to 6.0.0.

* Pass graphql.server.resolver as ephemeral address type.

* Add test.
  • Loading branch information
hoolioh authored and CarlesDD committed Dec 21, 2023
1 parent 083a48e commit 57c84fa
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 98 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"node": ">=14"
},
"dependencies": {
"@datadog/native-appsec": "5.0.0",
"@datadog/native-appsec": "6.0.0",
"@datadog/native-iast-rewriter": "2.2.2",
"@datadog/native-iast-taint-tracking": "1.6.4",
"@datadog/native-metrics": "^2.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/dd-trace/src/appsec/graphql.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function onGraphqlStartResolve ({ context, resolverInfo }) {

if (!resolverInfo || typeof resolverInfo !== 'object') return

const actions = waf.run({ [addresses.HTTP_INCOMING_GRAPHQL_RESOLVER]: resolverInfo }, req)
const actions = waf.run({ ephemeral: { [addresses.HTTP_INCOMING_GRAPHQL_RESOLVER]: resolverInfo } }, req)
if (actions?.includes('block')) {
const requestData = graphqlRequestData.get(req)
if (requestData?.isInGraphqlRequest) {
Expand Down
32 changes: 19 additions & 13 deletions packages/dd-trace/src/appsec/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,21 +83,21 @@ function incomingHttpStartTranslator ({ req, res, abortController }) {
const requestHeaders = Object.assign({}, req.headers)
delete requestHeaders.cookie

const payload = {
const persistent = {
[addresses.HTTP_INCOMING_URL]: req.url,
[addresses.HTTP_INCOMING_HEADERS]: requestHeaders,
[addresses.HTTP_INCOMING_METHOD]: req.method
}

if (clientIp) {
payload[addresses.HTTP_CLIENT_IP] = clientIp
persistent[addresses.HTTP_CLIENT_IP] = clientIp
}

if (apiSecuritySampler.sampleRequest()) {
payload[addresses.WAF_CONTEXT_PROCESSOR] = { 'extract-schema': true }
persistent[addresses.WAF_CONTEXT_PROCESSOR] = { 'extract-schema': true }
}

const actions = waf.run(payload, req)
const actions = waf.run({ persistent }, req)

handleResults(actions, req, res, rootSpan, abortController)
}
Expand All @@ -107,32 +107,32 @@ function incomingHttpEndTranslator ({ req, res }) {
const responseHeaders = Object.assign({}, res.getHeaders())
delete responseHeaders['set-cookie']

const payload = {
const persistent = {
[addresses.HTTP_INCOMING_RESPONSE_CODE]: '' + res.statusCode,
[addresses.HTTP_INCOMING_RESPONSE_HEADERS]: responseHeaders
}

// we need to keep this to support other body parsers
// TODO: no need to analyze it if it was already done by the body-parser hook
if (req.body !== undefined && req.body !== null) {
payload[addresses.HTTP_INCOMING_BODY] = req.body
persistent[addresses.HTTP_INCOMING_BODY] = req.body
}

// TODO: temporary express instrumentation, will use express plugin later
if (req.params && typeof req.params === 'object') {
payload[addresses.HTTP_INCOMING_PARAMS] = req.params
persistent[addresses.HTTP_INCOMING_PARAMS] = req.params
}

// we need to keep this to support other cookie parsers
if (req.cookies && typeof req.cookies === 'object') {
payload[addresses.HTTP_INCOMING_COOKIES] = req.cookies
persistent[addresses.HTTP_INCOMING_COOKIES] = req.cookies
}

if (req.query && typeof req.query === 'object') {
payload[addresses.HTTP_INCOMING_QUERY] = req.query
persistent[addresses.HTTP_INCOMING_QUERY] = req.query
}

waf.run(payload, req)
waf.run({ persistent }, req)

waf.disposeContext(req)

Expand All @@ -151,7 +151,9 @@ function onRequestBodyParsed ({ req, res, body, abortController }) {
if (!rootSpan) return

const results = waf.run({
[addresses.HTTP_INCOMING_BODY]: body
persistent: {
[addresses.HTTP_INCOMING_BODY]: body
}
}, req)

handleResults(results, req, res, rootSpan, abortController)
Expand All @@ -169,7 +171,9 @@ function onRequestQueryParsed ({ req, res, query, abortController }) {
if (!rootSpan) return

const results = waf.run({
[addresses.HTTP_INCOMING_QUERY]: query
persistent: {
[addresses.HTTP_INCOMING_QUERY]: query
}
}, req)

handleResults(results, req, res, rootSpan, abortController)
Expand All @@ -182,7 +186,9 @@ function onRequestCookieParser ({ req, res, abortController, cookies }) {
if (!rootSpan) return

const results = waf.run({
[addresses.HTTP_INCOMING_COOKIES]: cookies
persistent: {
[addresses.HTTP_INCOMING_COOKIES]: cookies
}
}, req)

handleResults(results, req, res, rootSpan, abortController)
Expand Down
2 changes: 1 addition & 1 deletion packages/dd-trace/src/appsec/sdk/user_blocking.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const { setUserTags } = require('./set_user')
const log = require('../../log')

function isUserBlocked (user) {
const actions = waf.run({ [USER_ID]: user.id })
const actions = waf.run({ persistent: { [USER_ID]: user.id } })

if (!actions) return false

Expand Down
38 changes: 25 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 @@ -18,30 +18,42 @@ class WAFContextWrapper {
this.addressesToSkip = new Set()
}

run (params) {
run ({ persistent, ephemeral }) {
const payload = {}
let payloadHasData = false
const inputs = {}
let someInputAdded = false
const newAddressesToSkip = new Set(this.addressesToSkip)

// TODO: possible optimizaion: only send params that haven't already been sent with same value to this wafContext
for (const key of Object.keys(params)) {
// TODO: requiredAddresses is no longer used due to processor addresses are not included in the list. Check on
// future versions when the actual addresses are included in the 'loaded' section inside diagnostics.
if (!this.addressesToSkip.has(key)) {
inputs[key] = params[key]
if (preventDuplicateAddresses.has(key)) {
newAddressesToSkip.add(key)
if (persistent && typeof persistent === 'object') {
// TODO: possible optimization: only send params that haven't already been sent with same value to this wafContext
for (const key of Object.keys(persistent)) {
// TODO: requiredAddresses is no longer used due to processor addresses are not included in the list. Check on
// future versions when the actual addresses are included in the 'loaded' section inside diagnostics.
if (!this.addressesToSkip.has(key)) {
inputs[key] = persistent[key]
if (preventDuplicateAddresses.has(key)) {
newAddressesToSkip.add(key)
}
}
someInputAdded = true
}
}

if (!someInputAdded) return
if (Object.keys(inputs).length) {
payload['persistent'] = inputs
payloadHasData = true
}

if (ephemeral && Object.keys(ephemeral).length) {
payload['ephemeral'] = ephemeral
payloadHasData = true
}

if (!payloadHasData) return

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

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

const end = process.hrtime.bigint()

Expand Down
30 changes: 15 additions & 15 deletions packages/dd-trace/test/appsec/graphql.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,11 @@ describe('GraphQL', () => {

startGraphqlResolve.publish({ context, resolverInfo })

expect(waf.run).to.have.been.calledOnceWithExactly(
{
expect(waf.run).to.have.been.calledOnceWithExactly({
ephemeral: {
[addresses.HTTP_INCOMING_GRAPHQL_RESOLVER]: resolverInfo
},
{}
)
}
}, {})
})
})

Expand Down Expand Up @@ -190,12 +189,12 @@ describe('GraphQL', () => {

startGraphqlResolve.publish({ context, resolverInfo })

expect(waf.run).to.have.been.calledOnceWithExactly(
{
expect(waf.run).to.have.been.calledOnceWithExactly({
ephemeral: {
[addresses.HTTP_INCOMING_GRAPHQL_RESOLVER]: resolverInfo
},
{}
)
}
}, {})

expect(context.abortController.abort).not.to.have.been.called

apolloChannel.asyncEnd.publish({ abortController })
Expand All @@ -221,13 +220,14 @@ describe('GraphQL', () => {

startGraphqlResolve.publish({ context, resolverInfo })

expect(waf.run).to.have.been.calledOnceWithExactly(
{
expect(waf.run).to.have.been.calledOnceWithExactly({
ephemeral: {
[addresses.HTTP_INCOMING_GRAPHQL_RESOLVER]: resolverInfo
},
{}
)
}
}, {})

expect(context.abortController.abort).to.have.been.called

const abortData = {}
apolloChannel.asyncEnd.publish({ abortController, abortData })

Expand Down
Loading

0 comments on commit 57c84fa

Please sign in to comment.