dd-trace 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/LICENSE-3rdparty.csv +2 -1
  2. package/README.md +4 -0
  3. package/package.json +5 -4
  4. package/packages/datadog-instrumentations/src/crypto.js +14 -12
  5. package/packages/datadog-instrumentations/src/grpc/server.js +15 -7
  6. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  7. package/packages/datadog-instrumentations/src/jest.js +136 -14
  8. package/packages/datadog-instrumentations/src/mocha.js +77 -31
  9. package/packages/datadog-instrumentations/src/next.js +7 -3
  10. package/packages/datadog-plugin-jest/src/index.js +106 -6
  11. package/packages/datadog-plugin-mocha/src/index.js +15 -7
  12. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  13. package/packages/dd-trace/src/appsec/iast/analyzers/weak-cipher-analyzer.js +27 -0
  14. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +13 -5
  15. package/packages/dd-trace/src/appsec/recommended.json +1144 -275
  16. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  17. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -3
  18. package/packages/dd-trace/src/config.js +15 -1
  19. package/packages/dd-trace/src/encode/0.4.js +7 -1
  20. package/packages/dd-trace/src/encode/0.5.js +7 -1
  21. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +2 -2
  22. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -20
  23. package/packages/dd-trace/src/exporters/common/request.js +3 -3
  24. package/packages/dd-trace/src/plugins/index.js +3 -0
  25. package/packages/dd-trace/src/plugins/util/ip_blocklist.js +30 -4
  26. package/packages/dd-trace/src/plugins/util/redis.js +0 -74
  27. package/packages/dd-trace/src/plugins/util/tx.js +0 -75
@@ -9,10 +9,15 @@ const {
9
9
  getTestEnvironmentMetadata,
10
10
  getTestParentSpan,
11
11
  getTestCommonTags,
12
+ getTestSessionCommonTags,
13
+ getTestSuiteCommonTags,
12
14
  TEST_PARAMETERS,
13
15
  getCodeOwnersFileEntries,
14
16
  getCodeOwnersForFilename,
15
- TEST_CODE_OWNERS
17
+ TEST_CODE_OWNERS,
18
+ TEST_SESSION_ID,
19
+ TEST_SUITE_ID,
20
+ TEST_COMMAND
16
21
  } = require('../../dd-trace/src/plugins/util/test')
17
22
 
18
23
  // https://github.com/facebook/jest/blob/d6ad15b0f88a05816c2fe034dd6900d28315d570/packages/jest-worker/src/types.ts#L38
@@ -58,12 +63,93 @@ class JestPlugin extends Plugin {
58
63
  this.testEnvironmentMetadata = getTestEnvironmentMetadata('jest', this.config)
59
64
  this.codeOwnersEntries = getCodeOwnersFileEntries()
60
65
 
61
- this.addSub('ci:jest:test:code-coverage', (coverageFiles) => {
66
+ this.addSub('ci:jest:session:start', (command) => {
67
+ if (!this.config.isAgentlessEnabled) {
68
+ return
69
+ }
70
+ const store = storage.getStore()
71
+ const childOf = getTestParentSpan(this.tracer)
72
+ const testSessionSpanMetadata = getTestSessionCommonTags(command, this.tracer._version)
73
+
74
+ const testSessionSpan = this.tracer.startSpan('jest.test_session', {
75
+ childOf,
76
+ tags: {
77
+ ...this.testEnvironmentMetadata,
78
+ ...testSessionSpanMetadata
79
+ }
80
+ })
81
+ this.enter(testSessionSpan, store)
82
+ })
83
+
84
+ this.addSub('ci:jest:session:finish', (status) => {
85
+ if (!this.config.isAgentlessEnabled) {
86
+ return
87
+ }
88
+ const testSessionSpan = storage.getStore().span
89
+ testSessionSpan.setTag(TEST_STATUS, status)
90
+ testSessionSpan.finish()
91
+ finishAllTraceSpans(testSessionSpan)
92
+ this.tracer._exporter._writer.flush()
93
+ })
94
+
95
+ // Test suites can be run in a different process from jest's main one.
96
+ // This subscriber changes the configuration objects from jest to inject the trace id
97
+ // of the test session to the processes that run the test suites.
98
+ this.addSub('ci:jest:session:configuration', configs => {
99
+ if (!this.config.isAgentlessEnabled) {
100
+ return
101
+ }
102
+ const testSessionSpan = storage.getStore().span
103
+ configs.forEach(config => {
104
+ config._ddTestSessionId = testSessionSpan.context()._traceId.toString(10)
105
+ config._ddTestCommand = testSessionSpan.context()._tags[TEST_COMMAND]
106
+ })
107
+ })
108
+
109
+ this.addSub('ci:jest:test-suite:start', ({ testSuite, testEnvironmentOptions }) => {
110
+ if (!this.config.isAgentlessEnabled) {
111
+ return
112
+ }
113
+
114
+ const { _ddTestSessionId: testSessionId, _ddTestCommand: testCommand } = testEnvironmentOptions
115
+
116
+ const store = storage.getStore()
117
+
118
+ const testSessionSpanContext = this.tracer.extract('text_map', {
119
+ 'x-datadog-trace-id': testSessionId,
120
+ 'x-datadog-parent-id': '0000000000000000'
121
+ })
122
+
123
+ const testSuiteMetadata = getTestSuiteCommonTags(testCommand, this.tracer._version, testSuite)
124
+
125
+ const testSuiteSpan = this.tracer.startSpan('jest.test_suite', {
126
+ childOf: testSessionSpanContext,
127
+ tags: {
128
+ ...this.testEnvironmentMetadata,
129
+ ...testSuiteMetadata
130
+ }
131
+ })
132
+ this.enter(testSuiteSpan, store)
133
+ })
134
+
135
+ this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage }) => {
136
+ if (!this.config.isAgentlessEnabled) {
137
+ return
138
+ }
139
+ const testSuiteSpan = storage.getStore().span
140
+ testSuiteSpan.setTag(TEST_STATUS, status)
141
+ if (errorMessage) {
142
+ testSuiteSpan.setTag('error', new Error(errorMessage))
143
+ }
144
+ testSuiteSpan.finish()
145
+ })
146
+
147
+ this.addSub('ci:jest:test-suite:code-coverage', (coverageFiles) => {
62
148
  if (!this.config.isAgentlessEnabled || !this.config.isIntelligentTestRunnerEnabled) {
63
149
  return
64
150
  }
65
- const testSpan = storage.getStore().span
66
- this.tracer._exporter.exportCoverage({ testSpan, coverageFiles })
151
+ const testSuiteSpan = storage.getStore().span
152
+ this.tracer._exporter.exportCoverage({ span: testSuiteSpan, coverageFiles })
67
153
  })
68
154
 
69
155
  this.addSub('ci:jest:test:start', (test) => {
@@ -96,7 +182,20 @@ class JestPlugin extends Plugin {
96
182
  }
97
183
 
98
184
  startTestSpan (test) {
99
- const { childOf, ...testSpanMetadata } = getTestSpanMetadata(this.tracer, test)
185
+ const suiteTags = {}
186
+ const store = storage.getStore()
187
+ const testSuiteSpan = store ? store.span : undefined
188
+ if (testSuiteSpan) {
189
+ const testSuiteId = testSuiteSpan.context()._spanId.toString(10)
190
+ suiteTags[TEST_SUITE_ID] = testSuiteId
191
+ suiteTags[TEST_SESSION_ID] = testSuiteSpan.context()._traceId.toString(10)
192
+ suiteTags[TEST_COMMAND] = testSuiteSpan.context()._tags[TEST_COMMAND]
193
+ }
194
+
195
+ const {
196
+ childOf,
197
+ ...testSpanMetadata
198
+ } = getTestSpanMetadata(this.tracer, test)
100
199
 
101
200
  const codeOwners = getCodeOwnersForFilename(test.suite, this.codeOwnersEntries)
102
201
 
@@ -109,7 +208,8 @@ class JestPlugin extends Plugin {
109
208
  childOf,
110
209
  tags: {
111
210
  ...this.testEnvironmentMetadata,
112
- ...testSpanMetadata
211
+ ...testSpanMetadata,
212
+ ...suiteTags
113
213
  }
114
214
  })
115
215
 
@@ -47,7 +47,7 @@ class MochaPlugin extends Plugin {
47
47
  constructor (...args) {
48
48
  super(...args)
49
49
 
50
- this._testSuites = new WeakMap()
50
+ this._testSuites = new Map()
51
51
  this._testNameToParams = {}
52
52
  this.testEnvironmentMetadata = getTestEnvironmentMetadata('mocha', this.config)
53
53
  this.sourceRoot = process.cwd()
@@ -75,7 +75,11 @@ class MochaPlugin extends Plugin {
75
75
  return
76
76
  }
77
77
  const store = storage.getStore()
78
- const testSuiteMetadata = getTestSuiteCommonTags(this.command, this.tracer._version, suite.fullTitle())
78
+ const testSuiteMetadata = getTestSuiteCommonTags(
79
+ this.command,
80
+ this.tracer._version,
81
+ getTestSuitePath(suite.file, this.sourceRoot)
82
+ )
79
83
  const testSuiteSpan = this.tracer.startSpan('mocha.test_suite', {
80
84
  childOf: this.testSessionSpan,
81
85
  tags: {
@@ -84,7 +88,7 @@ class MochaPlugin extends Plugin {
84
88
  }
85
89
  })
86
90
  this.enter(testSuiteSpan, store)
87
- this._testSuites.set(suite, testSuiteSpan)
91
+ this._testSuites.set(suite.file, testSuiteSpan)
88
92
  })
89
93
 
90
94
  this.addSub('ci:mocha:test-suite:finish', (status) => {
@@ -92,7 +96,10 @@ class MochaPlugin extends Plugin {
92
96
  return
93
97
  }
94
98
  const span = storage.getStore().span
95
- span.setTag(TEST_STATUS, status)
99
+ // the test status of the suite may have been set in ci:mocha:test-suite:error already
100
+ if (!span.context()._tags[TEST_STATUS]) {
101
+ span.setTag(TEST_STATUS, status)
102
+ }
96
103
  span.finish()
97
104
  })
98
105
 
@@ -102,6 +109,7 @@ class MochaPlugin extends Plugin {
102
109
  }
103
110
  const span = storage.getStore().span
104
111
  span.setTag('error', err)
112
+ span.setTag(TEST_STATUS, 'fail')
105
113
  })
106
114
 
107
115
  this.addSub('ci:mocha:test:start', (test) => {
@@ -158,15 +166,15 @@ class MochaPlugin extends Plugin {
158
166
 
159
167
  startTestSpan (test) {
160
168
  const testSuiteTags = {}
161
- const testSuiteSpan = this._testSuites.get(test.parent)
169
+ const testSuiteSpan = this._testSuites.get(test.parent.file)
162
170
 
163
171
  if (testSuiteSpan) {
164
- const testSuiteId = testSuiteSpan.context()._spanId.toString(16)
172
+ const testSuiteId = testSuiteSpan.context()._spanId.toString(10)
165
173
  testSuiteTags[TEST_SUITE_ID] = testSuiteId
166
174
  }
167
175
 
168
176
  if (this.testSessionSpan) {
169
- const testSessionId = this.testSessionSpan.context()._traceId.toString(16)
177
+ const testSessionId = this.testSessionSpan.context()._traceId.toString(10)
170
178
  testSuiteTags[TEST_SESSION_ID] = testSessionId
171
179
  testSuiteTags[TEST_COMMAND] = this.command
172
180
  }
@@ -1,3 +1,4 @@
1
1
  module.exports = {
2
+ 'WEAK_CIPHER_ANALYZER': require('./weak-cipher-analyzer'),
2
3
  'WEAK_HASH_ANALYZER': require('./weak-hash-analyzer')
3
4
  }
@@ -0,0 +1,27 @@
1
+ 'use strict'
2
+ const Analyzer = require('./vulnerability-analyzer')
3
+
4
+ const INSECURE_CIPHERS = new Set([
5
+ 'des', 'des-cbc', 'des-cfb', 'des-cfb1', 'des-cfb8', 'des-ecb', 'des-ede', 'des-ede-cbc', 'des-ede-cfb',
6
+ 'des-ede-ecb', 'des-ede-ofb', 'des-ede3', 'des-ede3-cbc', 'des-ede3-cfb', 'des-ede3-cfb1', 'des-ede3-cfb8',
7
+ 'des-ede3-ecb', 'des-ede3-ofb', 'des-ofb', 'des3', 'des3-wrap',
8
+ 'rc2', 'rc2-128', 'rc2-40', 'rc2-40-cbc', 'rc2-64', 'rc2-64-cbc', 'rc2-cbc', 'rc2-cfb', 'rc2-ecb', 'rc2-ofb',
9
+ 'blowfish',
10
+ 'rc4', 'rc4-40', 'rc4-hmac-md5'
11
+ ].map(algorithm => algorithm.toLowerCase()))
12
+
13
+ class WeakCipherAnalyzer extends Analyzer {
14
+ constructor () {
15
+ super('WEAK_CIPHER')
16
+ this.addSub('datadog:crypto:cipher:start', ({ algorithm }) => this.analyze(algorithm))
17
+ }
18
+
19
+ _isVulnerable (algorithm) {
20
+ if (algorithm && typeof algorithm === 'string') {
21
+ return INSECURE_CIPHERS.has(algorithm.toLowerCase())
22
+ }
23
+ return false
24
+ }
25
+ }
26
+
27
+ module.exports = new WeakCipherAnalyzer()
@@ -1,6 +1,9 @@
1
1
  const { MANUAL_KEEP } = require('../../../../../ext/tags')
2
+ const LRU = require('lru-cache')
2
3
  const VULNERABILITIES_KEY = 'vulnerabilities'
3
4
  const IAST_JSON_TAG_KEY = '_dd.iast.json'
5
+ const VULNERABILITY_HASHES_MAX_SIZE = 1000
6
+ const VULNERABILITY_HASHES = new LRU({ max: VULNERABILITY_HASHES_MAX_SIZE })
4
7
 
5
8
  function createVulnerability (type, evidence, spanId, location) {
6
9
  if (type && evidence && spanId) {
@@ -94,20 +97,25 @@ function sendVulnerabilities (iastContext) {
94
97
  return IAST_JSON_TAG_KEY
95
98
  }
96
99
 
100
+ function clearCache () { // only for test purposes
101
+ VULNERABILITY_HASHES.clear()
102
+ }
103
+
97
104
  function deduplicateVulnerabilities (vulnerabilities) {
98
- const uniqueVulnerabilities = new Set()
99
- return vulnerabilities.filter((vulnerability) => {
105
+ const deduplicated = vulnerabilities.filter((vulnerability) => {
100
106
  const key = `${vulnerability.type}${vulnerability.hash}`
101
- if (!uniqueVulnerabilities.has(key)) {
102
- uniqueVulnerabilities.add(key)
107
+ if (!VULNERABILITY_HASHES.get(key)) {
108
+ VULNERABILITY_HASHES.set(key, true)
103
109
  return true
104
110
  }
105
111
  return false
106
112
  })
113
+ return deduplicated
107
114
  }
108
115
 
109
116
  module.exports = {
110
117
  createVulnerability,
111
118
  addVulnerability,
112
- sendVulnerabilities
119
+ sendVulnerabilities,
120
+ clearCache
113
121
  }