Skip to content

Support vertx 5 #8220

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions dd-java-agent/instrumentation/vertx-web-4.0/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
ext {
// vertx-web doesn't support Java 17 until v4.2
maxJavaVersionForTests = JavaVersion.VERSION_15
latestDepTestMaxJavaVersionForTests = JavaVersion.VERSION_17
// unbound it for latest
latestDepTestMinJavaVersionForTests = JavaVersion.VERSION_11
latestDepForkedTestMinJavaVersionForTests = JavaVersion.VERSION_11
latestDepTestMaxJavaVersionForTests = JavaVersion.VERSION_25
latestDepForkedTestMaxJavaVersionForTests = JavaVersion.VERSION_25
}

apply from: "$rootDir/gradle/java.gradle"
Expand All @@ -11,12 +15,13 @@ muzzle {
pass {
group = 'io.vertx'
module = "vertx-web"
versions = "[4.0.0,5)"
versions = "[4.0.0,)"
assertInverse = true
}
}

addTestSuiteForDir('latestDepTest', 'test')
addTestSuiteForDir('latestDepTest', 'latestDepTest')
addTestSuiteExtendingForDir('latestDepForkedTest', 'latestDepTest', 'latestDepTest')

configurations {
testArtifacts
Expand Down Expand Up @@ -45,7 +50,13 @@ dependencies {
testRuntimeOnly project(':dd-java-agent:instrumentation:jackson-core')
testRuntimeOnly project(':dd-java-agent:instrumentation:netty-buffer-4')

// TODO support v>=4.5
latestDepTestImplementation group: 'io.vertx', name: 'vertx-web', version: '4.4.+'
latestDepTestImplementation group: 'io.vertx', name: 'vertx-web-client', version: '4.4.+'
latestDepTestImplementation group: 'io.vertx', name: 'vertx-web', version: '+'
latestDepTestImplementation group: 'io.vertx', name: 'vertx-web-client', version: '+'
}

[compileLatestDepTestJava, compileLatestDepForkedTestJava].each {
setJavaVersion(it, 11)
}
[compileLatestDepForkedTestGroovy, compileLatestDepTestGroovy].each {
it.javaLauncher = getJavaLauncherFor(11)
}
333 changes: 159 additions & 174 deletions dd-java-agent/instrumentation/vertx-web-4.0/gradle.lockfile

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package client

import datadog.trace.agent.test.base.HttpClientTest
import datadog.trace.agent.test.naming.TestingNettyHttpNamingConventions
import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator
import io.vertx.core.Vertx
import io.vertx.core.VertxOptions
import io.vertx.core.buffer.Buffer
import io.vertx.core.http.HttpMethod
import io.vertx.ext.web.client.HttpResponse
import io.vertx.ext.web.client.WebClient
import io.vertx.ext.web.client.WebClientOptions
import spock.lang.AutoCleanup
import spock.lang.Shared

import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit

class VertxHttpClientForkedTest extends HttpClientTest implements TestingNettyHttpNamingConventions.ClientV0 {
@Override
boolean useStrictTraceWrites() {
return false
}

@AutoCleanup
@Shared
def vertx = Vertx.vertx(new VertxOptions())

@Shared
def clientOptions = new WebClientOptions()
// vertx default is in seconds
.setConnectTimeout(TimeUnit.SECONDS.toSeconds(3) as int)
.setIdleTimeout(TimeUnit.SECONDS.toSeconds(5) as int)

@AutoCleanup
@Shared
def httpClient = WebClient.create(vertx, clientOptions)

@Override
int doRequest(String method, URI uri, Map<String, String> headers, String body, Closure callback) {
return doRequest(method, uri, headers, body, callback, -1)
}

int doRequest(String method, URI uri, Map<String, String> headers, String body, Closure callback, long timeout) {
CompletableFuture<HttpResponse> future = new CompletableFuture<>()

def request = httpClient.request(HttpMethod.valueOf(method), uri.getPort(), uri.getHost(), uri.toString())
headers.each { request.putHeader(it.key, it.value) }
request.sendBuffer(Buffer.buffer(body)).onSuccess { response ->
try {
callback?.call()
future.complete(response)
} catch (Exception e) {
future.completeExceptionally(e)
}
}

def response = future.get(10, TimeUnit.SECONDS)
return response == null ? 0 : response.statusCode()
}

@Override
CharSequence component() {
return NettyHttpClientDecorator.DECORATE.component()
}

@Override
boolean testRedirects() {
false
}

@Override
boolean testConnectionFailure() {
false
}

boolean testRemoteConnection() {
// FIXME: figure out how to configure timeouts.
false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package core

import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.api.iast.InstrumentationBridge
import datadog.trace.api.iast.SourceTypes
import datadog.trace.api.iast.Taintable
import datadog.trace.api.iast.propagation.PropagationModule
import groovy.transform.CompileDynamic
import io.vertx.core.buffer.Buffer
import io.vertx.core.buffer.impl.BufferImpl

@CompileDynamic
class BufferInstrumentationTest extends AgentTestRunner {

@Override
protected void configurePreAgent() {
injectSysConfig('dd.iast.enabled', 'true')
}

void 'test that Buffer.#methodName is instrumented'() {
given:
final module = Mock(PropagationModule)
InstrumentationBridge.registerIastModule(module)
final buffer = taintedInstance(SourceTypes.REQUEST_BODY)

when:
method.call(buffer)

then:
1 * module.taintStringIfTainted(_, buffer)

where:
methodName | method
'toString()' | { Buffer b -> b.toString() }
'toString(String)' | { Buffer b -> b.toString('UTF-8') }
}

void 'test that Buffer.#methodName is instrumented'() {
given:
final module = Mock(PropagationModule)
InstrumentationBridge.registerIastModule(module)
final buffer = new BufferImpl()
final tainted = taintedInstance(SourceTypes.REQUEST_BODY)

when:
method.call(buffer, tainted)

then:
1 * module.taintObjectIfTainted(buffer, tainted)

where:
methodName | method
'appendBuffer(Buffer)' | { Buffer b, Buffer taint -> b.appendBuffer(taint) }
'appendBuffer(buffer, int, int)' | { Buffer b, Buffer taint -> b.appendBuffer(taint, 0, taint.length()) }
}

private Buffer taintedInstance(final byte origin) {
final buffer = new BufferImpl('Hello World!')
if (buffer instanceof Taintable) {
final source = Mock(Taintable.Source) {
getOrigin() >> origin
}
(buffer as Taintable).$$DD$setSource(source)
}
return buffer
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package core

import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.api.iast.IastContext
import datadog.trace.api.iast.InstrumentationBridge
import datadog.trace.api.iast.SourceTypes
import datadog.trace.api.iast.Taintable
import datadog.trace.api.iast.propagation.PropagationModule
import datadog.trace.bootstrap.instrumentation.api.AgentTracer
import datadog.trace.bootstrap.instrumentation.api.TagContext
import groovy.transform.CompileDynamic
import io.netty.handler.codec.http.DefaultHttpHeaders
import io.netty.handler.codec.http2.DefaultHttp2Headers
import io.vertx.core.MultiMap
import io.vertx.core.http.impl.headers.HeadersAdaptor
import io.vertx.core.http.impl.headers.HeadersMultiMap
import io.vertx.core.http.impl.headers.Http2HeadersAdaptor
import spock.lang.IgnoreIf

import static datadog.trace.api.iast.SourceTypes.namedSource

@CompileDynamic
class MultiMapInstrumentationTest extends AgentTestRunner {

private Object iastCtx

@Override
protected void configurePreAgent() {
injectSysConfig('dd.iast.enabled', 'true')
}

void setup() {
iastCtx = Stub(IastContext)
}

void 'test that #name get() is instrumented'() {
given:
final origin = SourceTypes.REQUEST_PARAMETER_VALUE
addAll([key: 'value'], instance)
final module = Mock(PropagationModule)
InstrumentationBridge.registerIastModule(module)

when:
runUnderIastTrace { instance.get('key') }

then:
1 * module.findSource(iastCtx, instance) >> { null }
0 * _

when:
runUnderIastTrace { instance.get('key') }

then:
1 * module.findSource(iastCtx, instance) >> { mockedSource(origin) }
1 * module.taintString(iastCtx, 'value', origin, 'key')

where:
instance << multiMaps()
name = instance.getClass().simpleName
}

void 'test that #name getAll() is instrumented'() {
given:
final origin = SourceTypes.REQUEST_PARAMETER_VALUE
addAll([[key: 'value1'], [key: 'value2']], instance)
final module = Mock(PropagationModule)
InstrumentationBridge.registerIastModule(module)

when:
runUnderIastTrace { instance.getAll('key') }

then:
1 * module.findSource(iastCtx, instance) >> { null }
0 * _

when:
runUnderIastTrace { instance.getAll('key') }

then:
1 * module.findSource(iastCtx, instance) >> { mockedSource(origin) }
1 * module.taintString(iastCtx, 'value1', origin, 'key')
1 * module.taintString(iastCtx, 'value2', origin, 'key')

where:
instance << multiMaps()
name = instance.getClass().simpleName
}

void 'test that #name names() is instrumented'() {
given:
final origin = SourceTypes.REQUEST_PARAMETER_VALUE
addAll([[key: 'value1'], [key: 'value2']], instance)
final module = Mock(PropagationModule)
InstrumentationBridge.registerIastModule(module)

when:
runUnderIastTrace { instance.names() }

then:
1 * module.findSource(iastCtx, instance) >> { null }
0 * _

when:
runUnderIastTrace { instance.names() }

then:
1 * module.findSource(iastCtx, instance) >> { mockedSource(origin) }
1 * module.taintString(iastCtx, 'key', namedSource(origin), 'key')

where:
instance << multiMaps()
name = instance.getClass().simpleName
}

// some implementations do not override the entries() method so we will lose propagation in those cases
@IgnoreIf({ !MultiMapInstrumentationTest.hasMethod(data['instance'].class, 'entries')})
void 'test that #name entries() is instrumented'() {
given:
final origin = SourceTypes.REQUEST_PARAMETER_VALUE
addAll([[key: 'value1'], [key: 'value2']], instance)
final module = Mock(PropagationModule)
InstrumentationBridge.registerIastModule(module)

when:
runUnderIastTrace { instance.entries() }

then:
1 * module.findSource(iastCtx, instance) >> { null }
0 * _

when:
runUnderIastTrace { instance.entries() }

then:
1 * module.findSource(iastCtx, instance) >> { mockedSource(origin) }
1 * module.taintString(iastCtx, 'key', namedSource(origin), 'key')
1 * module.taintString(iastCtx, 'value1', origin, 'key')
1 * module.taintString(iastCtx, 'value2', origin, 'key')

where:
instance << multiMaps()
name = instance.getClass().simpleName
}

protected <E> E runUnderIastTrace(Closure<E> cl) {
final ddctx = new TagContext().withRequestContextDataIast(iastCtx)
final span = TEST_TRACER.startSpan("test", "test-iast-span", ddctx)
try {
return AgentTracer.activateSpan(span).withCloseable(cl)
} finally {
span.finish()
}
}

private mockedSource(final byte origin) {
return Mock(Taintable.Source) {
getOrigin() >> origin
}
}

private static boolean hasMethod(final Class<?> target, final String name) {
try {
return target.getDeclaredMethods().any { it.name == name }
} catch (Throwable e) {
return false
}
}

private List<MultiMap> multiMaps() {
return [
new HeadersMultiMap(),
new HeadersAdaptor(new DefaultHttpHeaders()),
new Http2HeadersAdaptor(new DefaultHttp2Headers())
]
}

private static void addAll(final Map<String, String> map, final MultiMap headers) {
map.each { key, value -> headers.add(key, value) }
}

private static void addAll(final List<Map<String, String>> list, final MultiMap headers) {
list.each { addAll(it, headers) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package server

import datadog.trace.agent.test.utils.OkHttpUtils
import okhttp3.OkHttpClient
import okhttp3.Protocol

class IastVertxHttp1ServerTest extends IastVertxHttpServerTest {

OkHttpClient client = OkHttpUtils.clientBuilder().protocols([Protocol.HTTP_1_1]).build()
}
Loading
Loading