Skip to content

Commit d6df5c1

Browse files
authored
manual keep trace on asm standalone and api security (#5649)
* manual keep trace on asm standalone and api security * set priority to user reject from auto * add test for sample request * keep auto reject
1 parent f3cf426 commit d6df5c1

File tree

5 files changed

+140
-43
lines changed

5 files changed

+140
-43
lines changed

integration-tests/standalone-asm.spec.js

+75-23
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,35 @@ describe('Standalone ASM', () => {
2525
await sandbox.remove()
2626
})
2727

28+
function assertKeep ({ meta, metrics }) {
29+
assert.propertyVal(meta, '_dd.p.ts', '02')
30+
31+
assert.propertyVal(metrics, '_sampling_priority_v1', USER_KEEP)
32+
assert.propertyVal(metrics, '_dd.apm.enabled', 0)
33+
}
34+
35+
function assertDrop ({ meta, metrics }) {
36+
assert.notProperty(meta, '_dd.p.ts')
37+
38+
assert.propertyVal(metrics, '_sampling_priority_v1', AUTO_REJECT)
39+
assert.propertyVal(metrics, '_dd.apm.enabled', 0)
40+
}
41+
42+
async function doWarmupRequests (procOrUrl, number = 3) {
43+
for (let i = number; i > 0; i--) {
44+
await curl(procOrUrl)
45+
}
46+
}
47+
2848
describe('enabled', () => {
2949
beforeEach(async () => {
3050
agent = await new FakeAgent().start()
3151

3252
env = {
3353
AGENT_PORT: agent.port,
34-
DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED: 'true'
54+
DD_APM_TRACING_ENABLED: 'false',
55+
DD_APPSEC_ENABLED: 'true',
56+
DD_API_SECURITY_ENABLED: 'false'
3557
}
3658

3759
const execArgv = []
@@ -44,28 +66,6 @@ describe('Standalone ASM', () => {
4466
await agent.stop()
4567
})
4668

47-
function assertKeep (payload) {
48-
const { meta, metrics } = payload
49-
50-
assert.propertyVal(meta, '_dd.p.ts', '02')
51-
52-
assert.propertyVal(metrics, '_sampling_priority_v1', USER_KEEP)
53-
assert.propertyVal(metrics, '_dd.apm.enabled', 0)
54-
}
55-
56-
function assertDrop (payload) {
57-
const { metrics } = payload
58-
assert.propertyVal(metrics, '_sampling_priority_v1', AUTO_REJECT)
59-
assert.propertyVal(metrics, '_dd.apm.enabled', 0)
60-
assert.notProperty(metrics, '_dd.p.ts')
61-
}
62-
63-
async function doWarmupRequests (procOrUrl, number = 3) {
64-
for (let i = number; i > 0; i--) {
65-
await curl(procOrUrl)
66-
}
67-
}
68-
6969
// first req initializes the waf and reports the first appsec event adding manual.keep tag
7070
it('should send correct headers and tags on first req', async () => {
7171
return curlAndAssertMessage(agent, proc, ({ headers, payload }) => {
@@ -254,6 +254,58 @@ describe('Standalone ASM', () => {
254254
})
255255
})
256256

257+
describe('With API Security enabled', () => {
258+
beforeEach(async () => {
259+
agent = await new FakeAgent().start()
260+
261+
env = {
262+
AGENT_PORT: agent.port,
263+
DD_APM_TRACING_ENABLED: 'false',
264+
DD_APPSEC_ENABLED: 'true'
265+
}
266+
267+
const execArgv = []
268+
269+
proc = await spawnProc(startupTestFile, { cwd, env, execArgv })
270+
})
271+
272+
afterEach(async () => {
273+
proc.kill()
274+
await agent.stop()
275+
})
276+
277+
it('should keep fifth req because of api security sampler', async () => {
278+
const promise = curlAndAssertMessage(agent, proc, ({ headers, payload }) => {
279+
assert.propertyVal(headers, 'datadog-client-computed-stats', 'yes')
280+
assert.isArray(payload)
281+
if (payload.length === 4) {
282+
assertKeep(payload[0][0])
283+
assertDrop(payload[1][0])
284+
assertDrop(payload[2][0])
285+
assertDrop(payload[3][0])
286+
287+
// req after 30s
288+
} else {
289+
const fifthReq = payload[0]
290+
assert.isArray(fifthReq)
291+
assert.strictEqual(fifthReq.length, 5)
292+
assertKeep(fifthReq[0])
293+
}
294+
}, 40000, 2)
295+
296+
// 1st req kept because waf init
297+
// next in the 30s are dropped
298+
// 5nd req manual kept because of api security sampler
299+
await doWarmupRequests(proc)
300+
301+
await new Promise(resolve => setTimeout(resolve, 30000))
302+
303+
await curl(proc)
304+
305+
return promise
306+
}).timeout(40000)
307+
})
308+
257309
describe('disabled', () => {
258310
beforeEach(async () => {
259311
agent = await new FakeAgent().start()

packages/dd-trace/src/appsec/api_security_sampler.js

+20-12
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ const TTLCache = require('@isaacs/ttlcache')
44
const web = require('../plugins/util/web')
55
const log = require('../log')
66
const { AUTO_REJECT, USER_REJECT } = require('../../../../ext/priority')
7+
const { keepTrace } = require('../priority_sampler')
8+
const { ASM } = require('../standalone/product')
79

810
const MAX_SIZE = 4096
911

1012
let enabled
13+
let asmStandaloneEnabled
1114

1215
/**
1316
* @type {TTLCache}
@@ -20,11 +23,12 @@ class NoopTTLCache {
2023
has (_key) { return false }
2124
}
2225

23-
function configure ({ apiSecurity }) {
24-
enabled = apiSecurity.enabled
25-
sampledRequests = apiSecurity.sampleDelay === 0
26+
function configure ({ appsec, apmTracingEnabled }) {
27+
enabled = appsec.apiSecurity.enabled
28+
asmStandaloneEnabled = apmTracingEnabled === false
29+
sampledRequests = appsec.apiSecurity.sampleDelay === 0
2630
? new NoopTTLCache()
27-
: new TTLCache({ max: MAX_SIZE, ttl: apiSecurity.sampleDelay * 1000 })
31+
: new TTLCache({ max: MAX_SIZE, ttl: appsec.apiSecurity.sampleDelay * 1000 })
2832
}
2933

3034
function disable () {
@@ -41,14 +45,18 @@ function sampleRequest (req, res, force = false) {
4145
const rootSpan = web.root(req)
4246
if (!rootSpan) return false
4347

44-
let priority = getSpanPriority(rootSpan)
45-
if (!priority) {
46-
rootSpan._prioritySampler?.sample(rootSpan)
47-
priority = getSpanPriority(rootSpan)
48-
}
49-
50-
if (priority === AUTO_REJECT || priority === USER_REJECT) {
51-
return false
48+
if (asmStandaloneEnabled) {
49+
keepTrace(rootSpan, ASM)
50+
} else {
51+
let priority = getSpanPriority(rootSpan)
52+
if (!priority) {
53+
rootSpan._prioritySampler?.sample(rootSpan)
54+
priority = getSpanPriority(rootSpan)
55+
}
56+
57+
if (priority === AUTO_REJECT || priority === USER_REJECT) {
58+
return false
59+
}
5260
}
5361

5462
if (force) {

packages/dd-trace/src/appsec/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ function enable (_config) {
5959

6060
Reporter.setRateLimit(_config.appsec.rateLimit)
6161

62-
apiSecuritySampler.configure(_config.appsec)
62+
apiSecuritySampler.configure(_config)
6363

6464
UserTracking.setCollectionMode(_config.appsec.eventTracking.mode, false)
6565

packages/dd-trace/src/standalone/product.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@
33
const { SAMPLING_MECHANISM_APPSEC } = require('../constants')
44
const RateLimiter = require('../rate_limiter')
55

6-
const dropAll = new RateLimiter(0)
7-
const onePerMinute = new RateLimiter(1, 'minute')
8-
96
function getProductRateLimiter (config) {
107
if (config?.appsec?.enabled || config?.iast?.enabled) {
11-
return onePerMinute
8+
return new RateLimiter(1, 'minute') // onePerMinute
129
}
13-
return dropAll
10+
11+
return new RateLimiter(0) // dropAll
1412
}
1513

1614
const PRODUCTS = {

packages/dd-trace/test/appsec/api_security_sampler.spec.js

+41-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ describe('API Security Sampler', () => {
6969

7070
describe('with TTLCache', () => {
7171
beforeEach(() => {
72-
apiSecuritySampler.configure({ apiSecurity: { enabled: true, sampleDelay: 30 } })
72+
apiSecuritySampler.configure({ appsec: { apiSecurity: { enabled: true, sampleDelay: 30 } } })
7373
})
7474

7575
it('should not sample before 30 seconds', () => {
@@ -152,7 +152,7 @@ describe('API Security Sampler', () => {
152152

153153
describe('with NoopTTLCache', () => {
154154
beforeEach(() => {
155-
apiSecuritySampler.configure({ apiSecurity: { enabled: true, sampleDelay: 0 } })
155+
apiSecuritySampler.configure({ appsec: { apiSecurity: { enabled: true, sampleDelay: 0 } } })
156156
})
157157

158158
it('should always return true for sampleRequest', () => {
@@ -197,4 +197,43 @@ describe('API Security Sampler', () => {
197197
assert.isTrue(apiSecuritySampler.sampleRequest(req, res, true))
198198
})
199199
})
200+
201+
describe('ASM Standalone', () => {
202+
let keepTraceStub
203+
204+
beforeEach(() => {
205+
keepTraceStub = sinon.stub()
206+
apiSecuritySampler = proxyquire('../../src/appsec/api_security_sampler', {
207+
'../plugins/util/web': webStub,
208+
'../priority_sampler': {
209+
keepTrace: keepTraceStub
210+
},
211+
'../standalone/product': {
212+
ASM: 'asm'
213+
}
214+
})
215+
apiSecuritySampler.configure({
216+
appsec: {
217+
apiSecurity: {
218+
enabled: true,
219+
sampleDelay: 30
220+
}
221+
},
222+
apmTracingEnabled: false
223+
})
224+
})
225+
226+
it('should keep trace with ASM product when in standalone mode', () => {
227+
assert.isTrue(apiSecuritySampler.sampleRequest(req, res, true))
228+
assert.isFalse(apiSecuritySampler.sampleRequest(req, res, true))
229+
assert.isTrue(keepTraceStub.calledOnceWith(span, 'asm'))
230+
})
231+
232+
it('should not check priority sampling in standalone mode', () => {
233+
span.context.returns({ _sampling: { priority: AUTO_REJECT } })
234+
assert.isTrue(apiSecuritySampler.sampleRequest(req, res, true))
235+
assert.isFalse(apiSecuritySampler.sampleRequest(req, res, true))
236+
assert.isTrue(keepTraceStub.calledOnceWith(span, 'asm'))
237+
})
238+
})
200239
})

0 commit comments

Comments
 (0)