Skip to content

Commit 1a5dccd

Browse files
committed
Support vertx 5
1 parent 148aef9 commit 1a5dccd

24 files changed

+1630
-242
lines changed

dd-java-agent/instrumentation/vertx-web-4.0/build.gradle

+17-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
ext {
33
// vertx-web doesn't support Java 17 until v4.2
44
maxJavaVersionForTests = JavaVersion.VERSION_15
5-
latestDepTestMaxJavaVersionForTests = JavaVersion.VERSION_17
5+
// unbound it for latest
6+
latestDepTestMinJavaVersionForTests = JavaVersion.VERSION_11
7+
latestDepForkedTestMinJavaVersionForTests = JavaVersion.VERSION_11
8+
latestDepTestMaxJavaVersionForTests = JavaVersion.VERSION_25
9+
latestDepForkedTestMaxJavaVersionForTests = JavaVersion.VERSION_25
610
}
711

812
apply from: "$rootDir/gradle/java.gradle"
@@ -11,12 +15,13 @@ muzzle {
1115
pass {
1216
group = 'io.vertx'
1317
module = "vertx-web"
14-
versions = "[4.0.0,5)"
18+
versions = "[4.0.0,)"
1519
assertInverse = true
1620
}
1721
}
1822

19-
addTestSuiteForDir('latestDepTest', 'test')
23+
addTestSuiteForDir('latestDepTest', 'latestDepTest')
24+
addTestSuiteExtendingForDir('latestDepForkedTest', 'latestDepTest', 'latestDepTest')
2025

2126
configurations {
2227
testArtifacts
@@ -45,7 +50,13 @@ dependencies {
4550
testRuntimeOnly project(':dd-java-agent:instrumentation:jackson-core')
4651
testRuntimeOnly project(':dd-java-agent:instrumentation:netty-buffer-4')
4752

48-
// TODO support v>=4.5
49-
latestDepTestImplementation group: 'io.vertx', name: 'vertx-web', version: '4.4.+'
50-
latestDepTestImplementation group: 'io.vertx', name: 'vertx-web-client', version: '4.4.+'
53+
latestDepTestImplementation group: 'io.vertx', name: 'vertx-web', version: '+'
54+
latestDepTestImplementation group: 'io.vertx', name: 'vertx-web-client', version: '+'
55+
}
56+
57+
[compileLatestDepTestJava, compileLatestDepForkedTestJava].each {
58+
setJavaVersion(it, 11)
59+
}
60+
[compileLatestDepForkedTestGroovy, compileLatestDepTestGroovy].each {
61+
it.javaLauncher = getJavaLauncherFor(11)
5162
}

dd-java-agent/instrumentation/vertx-web-4.0/gradle.lockfile

+159-174
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package client
2+
3+
import datadog.trace.agent.test.base.HttpClientTest
4+
import datadog.trace.agent.test.naming.TestingNettyHttpNamingConventions
5+
import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator
6+
import io.vertx.core.Vertx
7+
import io.vertx.core.VertxOptions
8+
import io.vertx.core.buffer.Buffer
9+
import io.vertx.core.http.HttpMethod
10+
import io.vertx.ext.web.client.HttpResponse
11+
import io.vertx.ext.web.client.WebClient
12+
import io.vertx.ext.web.client.WebClientOptions
13+
import spock.lang.AutoCleanup
14+
import spock.lang.Shared
15+
16+
import java.util.concurrent.CompletableFuture
17+
import java.util.concurrent.TimeUnit
18+
19+
class VertxHttpClientForkedTest extends HttpClientTest implements TestingNettyHttpNamingConventions.ClientV0 {
20+
@Override
21+
boolean useStrictTraceWrites() {
22+
return false
23+
}
24+
25+
@AutoCleanup
26+
@Shared
27+
def vertx = Vertx.vertx(new VertxOptions())
28+
29+
@Shared
30+
def clientOptions = new WebClientOptions()
31+
// vertx default is in seconds
32+
.setConnectTimeout(TimeUnit.SECONDS.toSeconds(3) as int)
33+
.setIdleTimeout(TimeUnit.SECONDS.toSeconds(5) as int)
34+
35+
@AutoCleanup
36+
@Shared
37+
def httpClient = WebClient.create(vertx, clientOptions)
38+
39+
@Override
40+
int doRequest(String method, URI uri, Map<String, String> headers, String body, Closure callback) {
41+
return doRequest(method, uri, headers, body, callback, -1)
42+
}
43+
44+
int doRequest(String method, URI uri, Map<String, String> headers, String body, Closure callback, long timeout) {
45+
CompletableFuture<HttpResponse> future = new CompletableFuture<>()
46+
47+
def request = httpClient.request(HttpMethod.valueOf(method), uri.getPort(), uri.getHost(), uri.toString())
48+
headers.each { request.putHeader(it.key, it.value) }
49+
request.sendBuffer(Buffer.buffer(body)).onSuccess { response ->
50+
try {
51+
callback?.call()
52+
future.complete(response)
53+
} catch (Exception e) {
54+
future.completeExceptionally(e)
55+
}
56+
}
57+
58+
def response = future.get(10, TimeUnit.SECONDS)
59+
return response == null ? 0 : response.statusCode()
60+
}
61+
62+
@Override
63+
CharSequence component() {
64+
return NettyHttpClientDecorator.DECORATE.component()
65+
}
66+
67+
@Override
68+
boolean testRedirects() {
69+
false
70+
}
71+
72+
@Override
73+
boolean testConnectionFailure() {
74+
false
75+
}
76+
77+
boolean testRemoteConnection() {
78+
// FIXME: figure out how to configure timeouts.
79+
false
80+
}
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package core
2+
3+
import datadog.trace.agent.test.AgentTestRunner
4+
import datadog.trace.api.iast.InstrumentationBridge
5+
import datadog.trace.api.iast.SourceTypes
6+
import datadog.trace.api.iast.Taintable
7+
import datadog.trace.api.iast.propagation.PropagationModule
8+
import groovy.transform.CompileDynamic
9+
import io.vertx.core.buffer.Buffer
10+
import io.vertx.core.buffer.impl.BufferImpl
11+
12+
@CompileDynamic
13+
class BufferInstrumentationTest extends AgentTestRunner {
14+
15+
@Override
16+
protected void configurePreAgent() {
17+
injectSysConfig('dd.iast.enabled', 'true')
18+
}
19+
20+
void 'test that Buffer.#methodName is instrumented'() {
21+
given:
22+
final module = Mock(PropagationModule)
23+
InstrumentationBridge.registerIastModule(module)
24+
final buffer = taintedInstance(SourceTypes.REQUEST_BODY)
25+
26+
when:
27+
method.call(buffer)
28+
29+
then:
30+
1 * module.taintStringIfTainted(_, buffer)
31+
32+
where:
33+
methodName | method
34+
'toString()' | { Buffer b -> b.toString() }
35+
'toString(String)' | { Buffer b -> b.toString('UTF-8') }
36+
}
37+
38+
void 'test that Buffer.#methodName is instrumented'() {
39+
given:
40+
final module = Mock(PropagationModule)
41+
InstrumentationBridge.registerIastModule(module)
42+
final buffer = new BufferImpl()
43+
final tainted = taintedInstance(SourceTypes.REQUEST_BODY)
44+
45+
when:
46+
method.call(buffer, tainted)
47+
48+
then:
49+
1 * module.taintObjectIfTainted(buffer, tainted)
50+
51+
where:
52+
methodName | method
53+
'appendBuffer(Buffer)' | { Buffer b, Buffer taint -> b.appendBuffer(taint) }
54+
'appendBuffer(buffer, int, int)' | { Buffer b, Buffer taint -> b.appendBuffer(taint, 0, taint.length()) }
55+
}
56+
57+
private Buffer taintedInstance(final byte origin) {
58+
final buffer = new BufferImpl('Hello World!')
59+
if (buffer instanceof Taintable) {
60+
final source = Mock(Taintable.Source) {
61+
getOrigin() >> origin
62+
}
63+
(buffer as Taintable).$$DD$setSource(source)
64+
}
65+
return buffer
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package core
2+
3+
import datadog.trace.agent.test.AgentTestRunner
4+
import datadog.trace.api.iast.IastContext
5+
import datadog.trace.api.iast.InstrumentationBridge
6+
import datadog.trace.api.iast.SourceTypes
7+
import datadog.trace.api.iast.Taintable
8+
import datadog.trace.api.iast.propagation.PropagationModule
9+
import datadog.trace.bootstrap.instrumentation.api.AgentTracer
10+
import datadog.trace.bootstrap.instrumentation.api.TagContext
11+
import groovy.transform.CompileDynamic
12+
import io.netty.handler.codec.http.DefaultHttpHeaders
13+
import io.netty.handler.codec.http2.DefaultHttp2Headers
14+
import io.vertx.core.MultiMap
15+
import io.vertx.core.http.impl.headers.HeadersAdaptor
16+
import io.vertx.core.http.impl.headers.HeadersMultiMap
17+
import io.vertx.core.http.impl.headers.Http2HeadersAdaptor
18+
import spock.lang.IgnoreIf
19+
20+
import static datadog.trace.api.iast.SourceTypes.namedSource
21+
22+
@CompileDynamic
23+
class MultiMapInstrumentationTest extends AgentTestRunner {
24+
25+
private Object iastCtx
26+
27+
@Override
28+
protected void configurePreAgent() {
29+
injectSysConfig('dd.iast.enabled', 'true')
30+
}
31+
32+
void setup() {
33+
iastCtx = Stub(IastContext)
34+
}
35+
36+
void 'test that #name get() is instrumented'() {
37+
given:
38+
final origin = SourceTypes.REQUEST_PARAMETER_VALUE
39+
addAll([key: 'value'], instance)
40+
final module = Mock(PropagationModule)
41+
InstrumentationBridge.registerIastModule(module)
42+
43+
when:
44+
runUnderIastTrace { instance.get('key') }
45+
46+
then:
47+
1 * module.findSource(iastCtx, instance) >> { null }
48+
0 * _
49+
50+
when:
51+
runUnderIastTrace { instance.get('key') }
52+
53+
then:
54+
1 * module.findSource(iastCtx, instance) >> { mockedSource(origin) }
55+
1 * module.taintString(iastCtx, 'value', origin, 'key')
56+
57+
where:
58+
instance << multiMaps()
59+
name = instance.getClass().simpleName
60+
}
61+
62+
void 'test that #name getAll() is instrumented'() {
63+
given:
64+
final origin = SourceTypes.REQUEST_PARAMETER_VALUE
65+
addAll([[key: 'value1'], [key: 'value2']], instance)
66+
final module = Mock(PropagationModule)
67+
InstrumentationBridge.registerIastModule(module)
68+
69+
when:
70+
runUnderIastTrace { instance.getAll('key') }
71+
72+
then:
73+
1 * module.findSource(iastCtx, instance) >> { null }
74+
0 * _
75+
76+
when:
77+
runUnderIastTrace { instance.getAll('key') }
78+
79+
then:
80+
1 * module.findSource(iastCtx, instance) >> { mockedSource(origin) }
81+
1 * module.taintString(iastCtx, 'value1', origin, 'key')
82+
1 * module.taintString(iastCtx, 'value2', origin, 'key')
83+
84+
where:
85+
instance << multiMaps()
86+
name = instance.getClass().simpleName
87+
}
88+
89+
void 'test that #name names() is instrumented'() {
90+
given:
91+
final origin = SourceTypes.REQUEST_PARAMETER_VALUE
92+
addAll([[key: 'value1'], [key: 'value2']], instance)
93+
final module = Mock(PropagationModule)
94+
InstrumentationBridge.registerIastModule(module)
95+
96+
when:
97+
runUnderIastTrace { instance.names() }
98+
99+
then:
100+
1 * module.findSource(iastCtx, instance) >> { null }
101+
0 * _
102+
103+
when:
104+
runUnderIastTrace { instance.names() }
105+
106+
then:
107+
1 * module.findSource(iastCtx, instance) >> { mockedSource(origin) }
108+
1 * module.taintString(iastCtx, 'key', namedSource(origin), 'key')
109+
110+
where:
111+
instance << multiMaps()
112+
name = instance.getClass().simpleName
113+
}
114+
115+
// some implementations do not override the entries() method so we will lose propagation in those cases
116+
@IgnoreIf({ !MultiMapInstrumentationTest.hasMethod(data['instance'].class, 'entries')})
117+
void 'test that #name entries() is instrumented'() {
118+
given:
119+
final origin = SourceTypes.REQUEST_PARAMETER_VALUE
120+
addAll([[key: 'value1'], [key: 'value2']], instance)
121+
final module = Mock(PropagationModule)
122+
InstrumentationBridge.registerIastModule(module)
123+
124+
when:
125+
runUnderIastTrace { instance.entries() }
126+
127+
then:
128+
1 * module.findSource(iastCtx, instance) >> { null }
129+
0 * _
130+
131+
when:
132+
runUnderIastTrace { instance.entries() }
133+
134+
then:
135+
1 * module.findSource(iastCtx, instance) >> { mockedSource(origin) }
136+
1 * module.taintString(iastCtx, 'key', namedSource(origin), 'key')
137+
1 * module.taintString(iastCtx, 'value1', origin, 'key')
138+
1 * module.taintString(iastCtx, 'value2', origin, 'key')
139+
140+
where:
141+
instance << multiMaps()
142+
name = instance.getClass().simpleName
143+
}
144+
145+
protected <E> E runUnderIastTrace(Closure<E> cl) {
146+
final ddctx = new TagContext().withRequestContextDataIast(iastCtx)
147+
final span = TEST_TRACER.startSpan("test", "test-iast-span", ddctx)
148+
try {
149+
return AgentTracer.activateSpan(span).withCloseable(cl)
150+
} finally {
151+
span.finish()
152+
}
153+
}
154+
155+
private mockedSource(final byte origin) {
156+
return Mock(Taintable.Source) {
157+
getOrigin() >> origin
158+
}
159+
}
160+
161+
private static boolean hasMethod(final Class<?> target, final String name) {
162+
try {
163+
return target.getDeclaredMethods().any { it.name == name }
164+
} catch (Throwable e) {
165+
return false
166+
}
167+
}
168+
169+
private List<MultiMap> multiMaps() {
170+
return [
171+
new HeadersMultiMap(),
172+
new HeadersAdaptor(new DefaultHttpHeaders()),
173+
new Http2HeadersAdaptor(new DefaultHttp2Headers())
174+
]
175+
}
176+
177+
private static void addAll(final Map<String, String> map, final MultiMap headers) {
178+
map.each { key, value -> headers.add(key, value) }
179+
}
180+
181+
private static void addAll(final List<Map<String, String>> list, final MultiMap headers) {
182+
list.each { addAll(it, headers) }
183+
}
184+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package server
2+
3+
import datadog.trace.agent.test.utils.OkHttpUtils
4+
import okhttp3.OkHttpClient
5+
import okhttp3.Protocol
6+
7+
class IastVertxHttp1ServerTest extends IastVertxHttpServerTest {
8+
9+
OkHttpClient client = OkHttpUtils.clientBuilder().protocols([Protocol.HTTP_1_1]).build()
10+
}

0 commit comments

Comments
 (0)