Skip to content

Commit

Permalink
Standalone ASM priority sampler and tag propagation (#4416)
Browse files Browse the repository at this point in the history
* DD_APM_TRACING_ENABLED and span _dd.apm.enabled tag

* clean up

* Use MANUAL_KEEP const

* Add _dd.p.appsec tag on standalone ASM events

* Include apmTracingEnabled checks

* Appsec Reporter tests

* Appsec sdk track_event test

* Use numeric value for _dd.p.appsec

* Include appsec standalone config in .ts files

* Clean up null and undefined values

* Remove not needed config properties

* standalone module

* Clean up

* standalone proxy test

* Update packages/dd-trace/test/appsec/iast/vulnerability-reporter.spec.js

Co-authored-by: Ugaitz Urien <[email protected]>

* appsec reporter test

* Use standalone singletone in vulnerability-reporter

* continue applying ratelimiter on appsec standalone events

* Update packages/dd-trace/src/appsec/reporter.js

Co-authored-by: simon-id <[email protected]>

* Add _dd.apm.enabled:0 in root spans with remote parent

* Use a method to add the tag

* Remove apmTracingEnabled config property

* Add _dd.p.appsec tag in trace tags

* Some tests

* Set _dd.apm.enabled in root span

* configure standalone if _tracingInitialized

* Use dd-trace:span:start channel

* PrioritySampler and propagation

* Clean up

* Clean up

* use a meta tag

* Use dc to modify what is injected and extracted

* set USER_KEEP priority

* integration tests

* hasSubscribers check

* test description

* hasSubscribers check

* standalone tests

* Check span context has tags before using them and check if config has changed

* clean up

* Pass prioritySampler as argument to DatadogTracer

* clean up

* clean up

* protect span context sampling access

* Disable span stats if standalone enabled

* clean up

* clean up

* Clean up

* Clean up

* clean up

* Remove all headers from carrier

* inject integration tests

* remove only

* Update packages/dd-trace/test/appsec/sdk/track_event.spec.js

Co-authored-by: Ugaitz Urien <[email protected]>

* Update packages/dd-trace/test/appsec/standalone.spec.js

Co-authored-by: Ugaitz Urien <[email protected]>

* protect sample method

* Use assert instead expect

* reset sampling prio

* unsubscribe after test

* clear dd context from tracestate

* propagation with and without ASM events

* suggestions

* test inject and extrach channels

* use two services to test propagation

* integration tests cleanup

* clean up

* clean up

* Update packages/dd-trace/src/priority_sampler.js

Co-authored-by: Ugaitz Urien <[email protected]>

* Update packages/dd-trace/src/appsec/standalone.js

Co-authored-by: Ugaitz Urien <[email protected]>

* Move hasOwn, remove not used argument and fix test

* simplify iast integration-test using weak_hash

* Update packages/dd-trace/src/appsec/standalone.js

Co-authored-by: Ugaitz Urien <[email protected]>

* suggestions

* Fix integration tests

* Update packages/dd-trace/test/span_stats.spec.js

Co-authored-by: Ugaitz Urien <[email protected]>

* Update packages/dd-trace/test/exporters/agent/exporter.spec.js

Co-authored-by: Ugaitz Urien <[email protected]>

* Remove redundant check

* Remove standalone option

* protect onSpanInject and onSpanExtract

* do not set _dd.p.dm

* remove _dd.p.dm check from integration tests

* increase coverage

* increase coverage

* more coverage

* remove not needed async

* set default mechanism

* sugestions

* Remove throw tests

* Remove throw tests

* Update packages/dd-trace/test/appsec/standalone.spec.js

Co-authored-by: simon-id <[email protected]>

---------

Co-authored-by: Ugaitz Urien <[email protected]>
Co-authored-by: simon-id <[email protected]>
  • Loading branch information
3 people authored and juan-fernandez committed Jul 11, 2024
1 parent f3c342c commit 3f1e21a
Show file tree
Hide file tree
Showing 17 changed files with 953 additions and 31 deletions.
306 changes: 306 additions & 0 deletions integration-tests/standalone-asm.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
'use strict'

const { assert } = require('chai')
const path = require('path')

const {
createSandbox,
FakeAgent,
spawnProc,
curlAndAssertMessage,
curl
} = require('./helpers')

describe('Standalone ASM', () => {
let sandbox, cwd, startupTestFile, agent, proc, env

before(async () => {
sandbox = await createSandbox(['express'])
cwd = sandbox.folder
startupTestFile = path.join(cwd, 'standalone-asm/index.js')
})

after(async () => {
await sandbox.remove()
})

describe('enabled', () => {
beforeEach(async () => {
agent = await new FakeAgent().start()

env = {
AGENT_PORT: agent.port,
DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED: 'true'
}

const execArgv = []

proc = await spawnProc(startupTestFile, { cwd, env, execArgv })
})

afterEach(async () => {
proc.kill()
await agent.stop()
})

function assertKeep (payload, manual = true) {
const { meta, metrics } = payload
if (manual) {
assert.propertyVal(meta, 'manual.keep', 'true')
} else {
assert.notProperty(meta, 'manual.keep')
}
assert.propertyVal(meta, '_dd.p.appsec', '1')

assert.propertyVal(metrics, '_sampling_priority_v1', 2)
assert.propertyVal(metrics, '_dd.apm.enabled', 0)
}

function assertDrop (payload) {
const { metrics } = payload
assert.propertyVal(metrics, '_sampling_priority_v1', 0)
assert.propertyVal(metrics, '_dd.apm.enabled', 0)
assert.notProperty(metrics, '_dd.p.appsec')
}

async function doWarmupRequests (procOrUrl, number = 3) {
for (let i = number; i > 0; i--) {
await curl(procOrUrl)
}
}

// first req initializes the waf and reports the first appsec event adding manual.keep tag
it('should send correct headers and tags on first req', async () => {
return curlAndAssertMessage(agent, proc, ({ headers, payload }) => {
assert.propertyVal(headers, 'datadog-client-computed-stats', 'yes')
assert.isArray(payload)
assert.strictEqual(payload.length, 1)
assert.isArray(payload[0])

// express.request + 4 middlewares
assert.strictEqual(payload[0].length, 5)

assertKeep(payload[0][0])
})
})

it('should keep second req because RateLimiter allows 1 req/min and discard the next', async () => {
// 1st req kept because waf init
// 2nd req kept because it's the first one hitting RateLimiter
// next in the first minute are dropped
await doWarmupRequests(proc)

return curlAndAssertMessage(agent, proc, ({ headers, payload }) => {
assert.propertyVal(headers, 'datadog-client-computed-stats', 'yes')
assert.isArray(payload)
assert.strictEqual(payload.length, 4)

const secondReq = payload[1]
assert.isArray(secondReq)
assert.strictEqual(secondReq.length, 5)

const { meta, metrics } = secondReq[0]
assert.notProperty(meta, 'manual.keep')
assert.notProperty(meta, '_dd.p.appsec')

assert.propertyVal(metrics, '_sampling_priority_v1', 1)
assert.propertyVal(metrics, '_dd.apm.enabled', 0)

assertDrop(payload[2][0])

assertDrop(payload[3][0])
})
})

it('should keep attack requests', async () => {
await doWarmupRequests(proc)

const urlAttack = proc.url + '?query=1 or 1=1'
return curlAndAssertMessage(agent, urlAttack, ({ headers, payload }) => {
assert.propertyVal(headers, 'datadog-client-computed-stats', 'yes')
assert.isArray(payload)
assert.strictEqual(payload.length, 4)

assertKeep(payload[3][0])
})
})

it('should keep sdk events', async () => {
await doWarmupRequests(proc)

const url = proc.url + '/login?user=test'
return curlAndAssertMessage(agent, url, ({ headers, payload }) => {
assert.propertyVal(headers, 'datadog-client-computed-stats', 'yes')
assert.isArray(payload)
assert.strictEqual(payload.length, 4)

assertKeep(payload[3][0])
})
})

it('should keep custom sdk events', async () => {
await doWarmupRequests(proc)

const url = proc.url + '/sdk'
return curlAndAssertMessage(agent, url, ({ headers, payload }) => {
assert.propertyVal(headers, 'datadog-client-computed-stats', 'yes')
assert.isArray(payload)
assert.strictEqual(payload.length, 4)

assertKeep(payload[3][0])
})
})

it('should keep iast events', async () => {
await doWarmupRequests(proc)

const url = proc.url + '/vulnerableHash'
return curlAndAssertMessage(agent, url, ({ headers, payload }) => {
assert.propertyVal(headers, 'datadog-client-computed-stats', 'yes')
assert.isArray(payload)
assert.strictEqual(payload.length, 4)

const expressReq4 = payload[3][0]
assertKeep(expressReq4)
assert.property(expressReq4.meta, '_dd.iast.json')
assert.propertyVal(expressReq4.metrics, '_dd.iast.enabled', 1)
})
})

describe('propagation', () => {
let proc2
let port2

beforeEach(async () => {
const execArgv = []

proc2 = await spawnProc(startupTestFile, { cwd, env, execArgv })

port2 = parseInt(proc2.url.substring(proc2.url.lastIndexOf(':') + 1), 10)
})

afterEach(async () => {
proc2.kill()
})

// proc/drop-and-call-sdk:
// after setting a manual.drop calls to downstream proc2/sdk which triggers an appsec event
it('should keep trace even if parent prio is -1 but there is an event in the local trace', async () => {
await doWarmupRequests(proc)
await doWarmupRequests(proc2)

const url = `${proc.url}/propagation-after-drop-and-call-sdk?port=${port2}`
return curlAndAssertMessage(agent, url, ({ headers, payload }) => {
assert.propertyVal(headers, 'datadog-client-computed-stats', 'yes')
assert.isArray(payload)

const innerReq = payload.find(p => p[0].resource === 'GET /sdk')
assert.notStrictEqual(innerReq, undefined)
assertKeep(innerReq[0])
}, undefined, undefined, true)
})

// proc/propagation-with-event triggers an appsec event and calls downstream proc2/down with no event
it('should keep if parent trace is (prio:2, _dd.p.appsec:1) but there is no event in the local trace',
async () => {
await doWarmupRequests(proc)
await doWarmupRequests(proc2)

const url = `${proc.url}/propagation-with-event?port=${port2}`
return curlAndAssertMessage(agent, url, ({ headers, payload }) => {
assert.propertyVal(headers, 'datadog-client-computed-stats', 'yes')
assert.isArray(payload)

const innerReq = payload.find(p => p[0].resource === 'GET /down')
assert.notStrictEqual(innerReq, undefined)
assertKeep(innerReq[0], false)
}, undefined, undefined, true)
})

it('should remove parent trace data if there is no event in the local trace', async () => {
await doWarmupRequests(proc)
await doWarmupRequests(proc2)

const url = `${proc.url}/propagation-without-event?port=${port2}`
return curlAndAssertMessage(agent, url, ({ headers, payload }) => {
assert.propertyVal(headers, 'datadog-client-computed-stats', 'yes')
assert.isArray(payload)

const innerReq = payload.find(p => p[0].resource === 'GET /down')
assert.notStrictEqual(innerReq, undefined)
assert.notProperty(innerReq[0].meta, '_dd.p.other')
}, undefined, undefined, true)
})

it('should not remove parent trace data if there is event in the local trace', async () => {
await doWarmupRequests(proc)

const url = `${proc.url}/propagation-with-event?port=${port2}`
return curlAndAssertMessage(agent, url, ({ headers, payload }) => {
assert.propertyVal(headers, 'datadog-client-computed-stats', 'yes')
assert.isArray(payload)

const innerReq = payload.find(p => p[0].resource === 'GET /down')
assert.notStrictEqual(innerReq, undefined)
assert.property(innerReq[0].meta, '_dd.p.other')
}, undefined, undefined, true)
})
})
})

describe('disabled', () => {
beforeEach(async () => {
agent = await new FakeAgent().start()

env = {
AGENT_PORT: agent.port
}

proc = await spawnProc(startupTestFile, { cwd, env })
})

afterEach(async () => {
proc.kill()
await agent.stop()
})

it('should not add standalone related tags in iast events', () => {
const url = proc.url + '/vulnerableHash'
return curlAndAssertMessage(agent, url, ({ headers, payload }) => {
assert.notProperty(headers, 'datadog-client-computed-stats')
assert.isArray(payload)
assert.strictEqual(payload.length, 1)
assert.isArray(payload[0])

// express.request + 4 middlewares
assert.strictEqual(payload[0].length, 5)

const { meta, metrics } = payload[0][0]
assert.property(meta, '_dd.iast.json') // WEAK_HASH and XCONTENTTYPE_HEADER_MISSING reported

assert.notProperty(meta, '_dd.p.appsec')
assert.notProperty(metrics, '_dd.apm.enabled')
})
})

it('should not add standalone related tags in appsec events', () => {
const urlAttack = proc.url + '?query=1 or 1=1'

return curlAndAssertMessage(agent, urlAttack, ({ headers, payload }) => {
assert.notProperty(headers, 'datadog-client-computed-stats')
assert.isArray(payload)
assert.strictEqual(payload.length, 1)
assert.isArray(payload[0])

// express.request + 4 middlewares
assert.strictEqual(payload[0].length, 5)

const { meta, metrics } = payload[0][0]
assert.property(meta, '_dd.appsec.json') // crs-942-100 triggered

assert.notProperty(meta, '_dd.p.appsec')
assert.notProperty(metrics, '_dd.apm.enabled')
})
})
})
})
Loading

0 comments on commit 3f1e21a

Please sign in to comment.