diff --git a/buildSrc/src/main/groovy/MuzzlePlugin.groovy b/buildSrc/src/main/groovy/MuzzlePlugin.groovy index 5bd62fb0453..81a3bb28c2d 100644 --- a/buildSrc/src/main/groovy/MuzzlePlugin.groovy +++ b/buildSrc/src/main/groovy/MuzzlePlugin.groovy @@ -338,7 +338,7 @@ class MuzzlePlugin implements Plugin { */ private static Set muzzleDirectiveToArtifacts(MuzzleDirective muzzleDirective, VersionRangeResult rangeResult) { - final Set versions = filterAndLimitVersions(rangeResult, muzzleDirective.skipVersions) + final Set versions = filterAndLimitVersions(rangeResult, muzzleDirective.skipVersions, muzzleDirective.includeSnapshots) final Set allVersionArtifacts = versions.collect { version -> new DefaultArtifact(muzzleDirective.group, muzzleDirective.module, muzzleDirective.classifier ?: "", "jar", version.toString()) @@ -371,7 +371,7 @@ class MuzzlePlugin implements Plugin { final Set versions = rangeResult.versions.toSet() allRangeResult.versions.removeAll(versions) - return filterAndLimitVersions(allRangeResult, muzzleDirective.skipVersions).collect { version -> + return filterAndLimitVersions(allRangeResult, muzzleDirective.skipVersions, muzzleDirective.includeSnapshots).collect { version -> final MuzzleDirective inverseDirective = new MuzzleDirective() inverseDirective.name = muzzleDirective.name inverseDirective.group = muzzleDirective.group @@ -379,12 +379,13 @@ class MuzzlePlugin implements Plugin { inverseDirective.versions = "$version" inverseDirective.assertPass = !muzzleDirective.assertPass inverseDirective.excludedDependencies = muzzleDirective.excludedDependencies + inverseDirective.includeSnapshots = muzzleDirective.includeSnapshots inverseDirective }.toSet() } - private static Set filterAndLimitVersions(VersionRangeResult result, Set skipVersions) { - return limitLargeRanges(result, filterVersion(result.versions.toSet(), skipVersions), skipVersions) + private static Set filterAndLimitVersions(VersionRangeResult result, Set skipVersions, boolean includeSnapshots) { + return limitLargeRanges(result, filterVersion(result.versions.toSet(), skipVersions, includeSnapshots), skipVersions) } private static Set limitLargeRanges(VersionRangeResult result, Set versions, Set skipVersions) { @@ -506,26 +507,30 @@ class MuzzlePlugin implements Plugin { /** * Filter out snapshot-type builds from versions list. */ - private static filterVersion(Set list, Set skipVersions) { + private static filterVersion(Set list, Set skipVersions, boolean includeSnapshots) { list.removeIf { def version = it.toString().toLowerCase(Locale.ROOT) - return version.endsWith("-snapshot") || - version.contains("rc") || - version.contains(".cr") || - version.contains("alpha") || - version.contains("beta") || - version.contains("-b") || - version.contains(".m") || - version.contains("-m") || - version.contains("-dev") || - version.contains("-ea") || - version.contains("-atlassian-") || - version.contains("public_draft") || - version.contains("-cr") || - version.contains("-preview") || - skipVersions.contains(version) || - version.matches(END_NMN_PATTERN) || - version.matches(GIT_SHA_PATTERN) + if (includeSnapshots) { + return skipVersions.contains(version) + } else { + return version.endsWith("-snapshot") || + version.contains("rc") || + version.contains(".cr") || + version.contains("alpha") || + version.contains("beta") || + version.contains("-b") || + version.contains(".m") || + version.contains("-m") || + version.contains("-dev") || + version.contains("-ea") || + version.contains("-atlassian-") || + version.contains("public_draft") || + version.contains("-cr") || + version.contains("-preview") || + skipVersions.contains(version) || + version.matches(END_NMN_PATTERN) || + version.matches(GIT_SHA_PATTERN) + } } return list } @@ -559,6 +564,7 @@ class MuzzleDirective { boolean assertInverse = false boolean skipFromReport = false boolean coreJdk = false + boolean includeSnapshots = false String javaVersion void coreJdk(version = null) { diff --git a/dd-java-agent/instrumentation/vertx-web-4.0/build.gradle b/dd-java-agent/instrumentation/vertx-web-4.0/build.gradle index fed9c29d6f8..b18d87758bb 100644 --- a/dd-java-agent/instrumentation/vertx-web-4.0/build.gradle +++ b/dd-java-agent/instrumentation/vertx-web-4.0/build.gradle @@ -5,8 +5,6 @@ ext { // unbound it for latest latestDepTestMinJavaVersionForTests = JavaVersion.VERSION_11 latestDepForkedTestMinJavaVersionForTests = JavaVersion.VERSION_11 - latest4xTestMaxJavaVersionForTests = JavaVersion.VERSION_25 - latest4xForkedTestMaxJavaVersionForTests = JavaVersion.VERSION_25 latestDepTestMaxJavaVersionForTests = JavaVersion.VERSION_25 latestDepForkedTestMaxJavaVersionForTests = JavaVersion.VERSION_25 } @@ -17,15 +15,13 @@ muzzle { pass { group = 'io.vertx' module = "vertx-web" - versions = "[4.0.0,)" + versions = "[4.0.0,5)" assertInverse = true } } addTestSuiteForDir('latestDepTest', 'latestDepTest') addTestSuiteExtendingForDir('latestDepForkedTest', 'latestDepTest', 'latestDepTest') -addTestSuiteForDir('latest4xTest', 'test') -addTestSuiteExtendingForDir('latest4xForkedTest', 'latest4xTest', 'test') configurations { testArtifacts @@ -54,11 +50,8 @@ dependencies { testRuntimeOnly project(':dd-java-agent:instrumentation:jackson-core') testRuntimeOnly project(':dd-java-agent:instrumentation:netty-buffer-4') - latest4xTestImplementation group: 'io.vertx', name: 'vertx-web', version: '4.+' - latest4xTestImplementation group: 'io.vertx', name: 'vertx-web-client', version: '4.+' - - latestDepTestImplementation group: 'io.vertx', name: 'vertx-web', version: '+' - latestDepTestImplementation group: 'io.vertx', name: 'vertx-web-client', version: '+' + latestDepTestImplementation group: 'io.vertx', name: 'vertx-web', version: '4.+' + latestDepTestImplementation group: 'io.vertx', name: 'vertx-web-client', version: '4.+' } [compileLatestDepTestJava, compileLatestDepForkedTestJava].each { @@ -67,6 +60,3 @@ dependencies { [compileLatestDepForkedTestGroovy, compileLatestDepTestGroovy].each { it.javaLauncher = getJavaLauncherFor(11) } -[latest4xForkedTest, latest4xTest].each { - it.jvmArgs += '-Dtest.dd.latest4xTest=true' -} diff --git a/dd-java-agent/instrumentation/vertx-web-4.0/gradle.lockfile b/dd-java-agent/instrumentation/vertx-web-4.0/gradle.lockfile index 8ed6510cbbd..ae8aa192983 100644 --- a/dd-java-agent/instrumentation/vertx-web-4.0/gradle.lockfile +++ b/dd-java-agent/instrumentation/vertx-web-4.0/gradle.lockfile @@ -51,69 +51,64 @@ commons-io:commons-io:2.11.0=latest4xForkedTestCompileClasspath,latest4xForkedTe de.thetaphi:forbiddenapis:3.8=compileClasspath info.picocli:picocli:4.6.3=latest4xForkedTestRuntimeClasspath,latest4xTestRuntimeClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath io.netty:netty-buffer:4.1.115.Final=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath +io.netty:netty-buffer:4.1.117.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-buffer:4.1.49.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-buffer:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -io.netty:netty-codec-base:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -io.netty:netty-codec-compression:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-codec-dns:4.1.115.Final=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath +io.netty:netty-codec-dns:4.1.117.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-codec-dns:4.1.49.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-codec-dns:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-codec-http2:4.1.115.Final=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath +io.netty:netty-codec-http2:4.1.117.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-codec-http2:4.1.49.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-codec-http2:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-codec-http:4.1.115.Final=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath +io.netty:netty-codec-http:4.1.117.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-codec-http:4.1.49.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-codec-http:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -io.netty:netty-codec-marshalling:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -io.netty:netty-codec-protobuf:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-codec-socks:4.1.115.Final=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath +io.netty:netty-codec-socks:4.1.117.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-codec-socks:4.1.49.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-codec-socks:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-codec:4.1.115.Final=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath +io.netty:netty-codec:4.1.117.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-codec:4.1.49.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-codec:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-common:4.1.115.Final=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath +io.netty:netty-common:4.1.117.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-common:4.1.49.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-common:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-handler-proxy:4.1.115.Final=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath +io.netty:netty-handler-proxy:4.1.117.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-handler-proxy:4.1.49.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-handler-proxy:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-handler:4.1.115.Final=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath +io.netty:netty-handler:4.1.117.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-handler:4.1.49.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-handler:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-resolver-dns:4.1.115.Final=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath +io.netty:netty-resolver-dns:4.1.117.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-resolver-dns:4.1.49.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-resolver-dns:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-resolver:4.1.115.Final=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath +io.netty:netty-resolver:4.1.117.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-resolver:4.1.49.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-resolver:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-transport-native-unix-common:4.1.115.Final=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath -io.netty:netty-transport-native-unix-common:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +io.netty:netty-transport-native-unix-common:4.1.117.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-transport:4.1.115.Final=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath +io.netty:netty-transport:4.1.117.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.netty:netty-transport:4.1.49.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport:4.2.0.RC1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.sqreen:libsqreen:11.2.0=latest4xForkedTestRuntimeClasspath,latest4xTestRuntimeClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath io.vertx:vertx-auth-common:4.0.0=compileClasspath,testCompileClasspath,testRuntimeClasspath io.vertx:vertx-auth-common:4.5.11=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath -io.vertx:vertx-auth-common:5.0.0.CR3=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +io.vertx:vertx-auth-common:4.5.12=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.vertx:vertx-bridge-common:4.0.0=compileClasspath,testCompileClasspath,testRuntimeClasspath io.vertx:vertx-bridge-common:4.5.11=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath -io.vertx:vertx-bridge-common:5.0.0.CR3=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -io.vertx:vertx-core-logging:5.0.0.CR3=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +io.vertx:vertx-bridge-common:4.5.12=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.vertx:vertx-core:4.0.0=compileClasspath,testCompileClasspath,testRuntimeClasspath io.vertx:vertx-core:4.5.11=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath -io.vertx:vertx-core:5.0.0.CR3=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +io.vertx:vertx-core:4.5.12=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.vertx:vertx-uri-template:4.5.11=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath -io.vertx:vertx-uri-template:5.0.0.CR3=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +io.vertx:vertx-uri-template:4.5.12=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.vertx:vertx-web-client:4.0.0=testCompileClasspath,testRuntimeClasspath io.vertx:vertx-web-client:4.5.11=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath -io.vertx:vertx-web-client:5.0.0.CR3=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +io.vertx:vertx-web-client:4.5.12=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.vertx:vertx-web-common:4.0.0=compileClasspath,testCompileClasspath,testRuntimeClasspath io.vertx:vertx-web-common:4.5.11=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath -io.vertx:vertx-web-common:5.0.0.CR3=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +io.vertx:vertx-web-common:4.5.12=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath io.vertx:vertx-web:4.0.0=compileClasspath,testCompileClasspath,testRuntimeClasspath io.vertx:vertx-web:4.5.11=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath -io.vertx:vertx-web:5.0.0.CR3=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +io.vertx:vertx-web:4.5.12=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath javax.servlet:javax.servlet-api:3.1.0=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath jaxen:jaxen:1.2.0=spotbugs jline:jline:2.14.6=latest4xForkedTestRuntimeClasspath,latest4xTestRuntimeClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath @@ -189,7 +184,6 @@ org.junit.platform:junit-platform-launcher:1.9.2=latest4xForkedTestRuntimeClassp org.junit.platform:junit-platform-runner:1.9.2=latest4xForkedTestRuntimeClasspath,latest4xTestRuntimeClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath org.junit.platform:junit-platform-suite-api:1.9.2=latest4xForkedTestRuntimeClasspath,latest4xTestRuntimeClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath org.junit.platform:junit-platform-suite-commons:1.9.2=latest4xForkedTestRuntimeClasspath,latest4xTestRuntimeClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.junit:junit-bom:5.9.1=spotbugs org.junit:junit-bom:5.9.2=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.objenesis:objenesis:3.3=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.2.0=latest4xForkedTestCompileClasspath,latest4xForkedTestRuntimeClasspath,latest4xTestCompileClasspath,latest4xTestRuntimeClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath diff --git a/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/client/VertxHttpClientForkedTest.groovy b/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/client/VertxHttpClientForkedTest.groovy index 0e528befe84..18abd3841a6 100644 --- a/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/client/VertxHttpClientForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/client/VertxHttpClientForkedTest.groovy @@ -46,12 +46,12 @@ class VertxHttpClientForkedTest extends HttpClientTest implements TestingNettyHt 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 { + request.sendBuffer(Buffer.buffer(body)) { asyncResult -> + if (asyncResult.succeeded()) { callback?.call() - future.complete(response) - } catch (Exception e) { - future.completeExceptionally(e) + future.complete(asyncResult.result()) + } else { + future.completeExceptionally(asyncResult.cause()) } } diff --git a/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/IastVertxHttpServerTest.groovy b/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/IastVertxHttpServerTest.groovy index bf2f6491823..65e75ed549d 100644 --- a/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/IastVertxHttpServerTest.groovy +++ b/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/IastVertxHttpServerTest.groovy @@ -23,12 +23,6 @@ abstract class IastVertxHttpServerTest extends IastSourcesTest false } - //FIXME: does not work with latestDep - @Override - protected boolean ignoreParameters() { - true - } - void 'test event bus'() { when: final url = "${address}/iast/sources/eventBus" @@ -58,7 +52,11 @@ abstract class IastVertxHttpServerTest extends IastSourcesTest final deployment = new DeploymentOptions() .setInstances(1) .setConfig(new JsonObject().put('https', isHttps())) - server.deployVerticle('server.IastSourcesVerticle', deployment).await() + server.deployVerticle('server.IastSourcesVerticle', deployment) { res -> + if (!res.succeeded()) { + throw new RuntimeException("Cannot deploy server Verticle", res.cause()) + } + } future.get() } diff --git a/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/IastVertxSinksTest.groovy b/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/IastVertxSinksTest.groovy index 3ec89d86398..4af6c6c49b7 100644 --- a/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/IastVertxSinksTest.groovy +++ b/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/IastVertxSinksTest.groovy @@ -6,7 +6,7 @@ import datadog.trace.api.iast.InstrumentationBridge import datadog.trace.api.iast.sink.UnvalidatedRedirectModule import io.vertx.core.DeploymentOptions import io.vertx.core.Vertx -import io.vertx.core.internal.VertxInternal +import io.vertx.core.impl.VertxInternal import okhttp3.Request import java.util.concurrent.CompletableFuture @@ -78,7 +78,11 @@ class IastVertxSinksTest extends IastHttpServerTest { }) final deployment = new DeploymentOptions() .setInstances(1) - server.deployVerticle('server.IastSinksVerticle', deployment).await() + server.deployVerticle('server.IastSinksVerticle', deployment) { res -> + if (!res.succeeded()) { + throw new RuntimeException("Cannot deploy server Verticle", res.cause()) + } + } future.get() } diff --git a/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy index f442d7093b3..2c09d35d353 100644 --- a/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy @@ -64,14 +64,12 @@ class VertxHttpServerForkedTest extends HttpServerTest { @Override boolean testRequestBody() { - //FIXME: it does not work with latestDep - false + true } @Override boolean testBodyUrlencoded() { - //FIXME: it does not work with latestDep - false + true } @Override @@ -81,8 +79,7 @@ class VertxHttpServerForkedTest extends HttpServerTest { @Override boolean testBodyJson() { - //FIXME: it does not work with latestDep - false + true } @Override @@ -132,6 +129,12 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } + @Override + boolean testBlockingErrorTypeSet() { + //FIXME: error type is not set in netty + false + } + @Override Serializable expectedServerSpanRoute(ServerEndpoint endpoint) { switch (endpoint) { @@ -174,12 +177,4 @@ class VertxHttpServerWorkerForkedTest extends VertxHttpServerForkedTest { HttpServer server() { return new VertxServer(verticle(), routerBasePath(), true) } - - @Override - boolean testBlocking() { - //FIXME: ASM - // on the worker the requests are dispatched through a queue. - // Despite the blocking works, we fails recording that blocking exception - false - } } diff --git a/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/VertxServer.groovy b/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/VertxServer.groovy index eb7b78343fe..3bad13d17a1 100644 --- a/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/VertxServer.groovy +++ b/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/VertxServer.groovy @@ -5,7 +5,7 @@ import io.vertx.core.AbstractVerticle import io.vertx.core.DeploymentOptions import io.vertx.core.ThreadingModel import io.vertx.core.Vertx -import io.vertx.core.internal.VertxInternal +import io.vertx.core.impl.VertxInternal import io.vertx.core.json.JsonObject import java.util.concurrent.CompletableFuture @@ -42,8 +42,11 @@ class VertxServer implements HttpServer { if (useWorker) { deployOptions = deployOptions.setWorkerPoolSize(1).setThreadingModel(ThreadingModel.WORKER) } - server.deployVerticle(verticle.name, deployOptions).await() - + server.deployVerticle(verticle.name, deployOptions) { res -> + if (!res.succeeded()) { + throw new RuntimeException("Cannot deploy server Verticle", res.cause()) + } + } future.get() } diff --git a/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/VertxSubrouterForkedTest.groovy b/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/VertxSubrouterForkedTest.groovy index b01e94c4065..26f5b27d894 100644 --- a/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/VertxSubrouterForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx-web-4.0/src/latestDepTest/groovy/server/VertxSubrouterForkedTest.groovy @@ -13,10 +13,4 @@ class VertxSubrouterForkedTest extends VertxHttpServerForkedTest { String routerBasePath() { return "/sub/" } - - @Override - boolean testEncodedQuery() { - // FIXME: test instrumentation gateway callback ... is failing for latest - false - } } diff --git a/dd-java-agent/instrumentation/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/core/AbstractHttpServerRequestInstrumentation.java b/dd-java-agent/instrumentation/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/core/AbstractHttpServerRequestInstrumentation.java index 5621d3a7412..2ec23dd3203 100644 --- a/dd-java-agent/instrumentation/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/core/AbstractHttpServerRequestInstrumentation.java +++ b/dd-java-agent/instrumentation/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/core/AbstractHttpServerRequestInstrumentation.java @@ -48,7 +48,10 @@ public void methodAdvice(final MethodTransformer transformer) { isPublic().and(isMethod()).and(named("headers")).and(takesNoArguments()), className + "$HeadersAdvice"); transformer.applyAdvice( - isPublic().and(isMethod()).and(named("params")).and(takesNoArguments()), + isPublic() + .and(isMethod()) + .and(named("params")) + .and(takesNoArguments().or(takesArguments(boolean.class))), className + "$ParamsAdvice"); transformer.applyAdvice( isMethod().and(takesNoArguments()).and(attributesFilter()), diff --git a/dd-java-agent/instrumentation/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RequestBodyInstrumentation.java b/dd-java-agent/instrumentation/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RequestBodyInstrumentation.java new file mode 100644 index 00000000000..09e41dd01d4 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RequestBodyInstrumentation.java @@ -0,0 +1,39 @@ +package datadog.trace.instrumentation.vertx_4_0.server; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.agent.tooling.muzzle.Reference; + +@AutoService(InstrumenterModule.class) +public class RequestBodyInstrumentation extends InstrumenterModule.AppSec + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + public RequestBodyInstrumentation() { + super("vertx", "vertx-4.0"); + } + + @Override + public Reference[] additionalMuzzleReferences() { + return new Reference[] {VertxVersionMatcher.HTTP_1X_SERVER_RESPONSE}; + } + + @Override + public String instrumentedType() { + return "io.vertx.ext.web.impl.RequestBodyImpl"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + named("asJsonObject") + .or(named("asJsonArray")) + .and(takesArguments(1)) + .and(takesArgument(0, int.class)), + packageName + ".RoutingContextJsonAdvice"); + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RouteMatchesAdvice.java b/dd-java-agent/instrumentation/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RouteMatchesAdvice.java index 80ab5bed5a4..f52ed53ece3 100644 --- a/dd-java-agent/instrumentation/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RouteMatchesAdvice.java +++ b/dd-java-agent/instrumentation/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RouteMatchesAdvice.java @@ -20,6 +20,10 @@ static void after( if (params.isEmpty()) { return; } + if (params.size() == 1 && params.containsKey("*")) { + // vert.x 5 removes the entry after our advice so we must ignore it + return; + } Throwable throwable = PathParameterPublishingHelper.publishParams(params); t = throwable; @@ -39,6 +43,10 @@ static void after( if (params.isEmpty()) { return; } + if (params.size() == 1 && params.containsKey("*")) { + // vert.x 5 removes the entry after our advice so we must ignore it + return; + } Throwable throwable = PathParameterPublishingHelper.publishParams(params); t = throwable; diff --git a/dd-java-agent/instrumentation/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextJsonAdvice.java b/dd-java-agent/instrumentation/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextJsonAdvice.java index ac48d8d77b8..4d6209fa458 100644 --- a/dd-java-agent/instrumentation/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextJsonAdvice.java +++ b/dd-java-agent/instrumentation/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextJsonAdvice.java @@ -10,18 +10,33 @@ import datadog.trace.api.gateway.Flow; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.gateway.RequestContextSlot; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.RoutingContext; import java.util.function.BiFunction; import net.bytebuddy.asm.Advice; @RequiresRequestContext(RequestContextSlot.APPSEC) class RoutingContextJsonAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + static void before() { + CallDepthThreadLocalMap.incrementCallDepth(RoutingContext.class); + } + @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class) static void after( @Advice.Return Object obj_, @ActiveRequestContext RequestContext reqCtx, @Advice.Thrown(readOnly = false) Throwable throwable) { + + // in newer versions of vert.x rc.getBodyAsJson() calls internally rc.body().asJsonObject() + // so we need to prevent sending the body twice to the WAF + if (CallDepthThreadLocalMap.decrementCallDepth(RoutingContext.class) != 0) { + return; + } + if (obj_ == null) { return; } diff --git a/dd-java-agent/instrumentation/vertx-web-4.0/src/test/groovy/server/IastVertxHttpServerTest.groovy b/dd-java-agent/instrumentation/vertx-web-4.0/src/test/groovy/server/IastVertxHttpServerTest.groovy index c35221ff780..65e75ed549d 100644 --- a/dd-java-agent/instrumentation/vertx-web-4.0/src/test/groovy/server/IastVertxHttpServerTest.groovy +++ b/dd-java-agent/instrumentation/vertx-web-4.0/src/test/groovy/server/IastVertxHttpServerTest.groovy @@ -9,26 +9,16 @@ import io.vertx.core.json.JsonObject import okhttp3.MediaType import okhttp3.Request import okhttp3.RequestBody -import spock.lang.Shared import java.util.concurrent.CompletableFuture abstract class IastVertxHttpServerTest extends IastSourcesTest { - @Shared - boolean isVertxLatest4x = Boolean.getBoolean('test.dd.latest4xTest') - @Override HttpServer server() { return new IastVertxServer() } - @Override - protected boolean ignoreParameters() { - //FIXME: ASM - return isVertxLatest4x - } - boolean isHttps() { false } diff --git a/dd-java-agent/instrumentation/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy index acc0465979e..db268592aef 100644 --- a/dd-java-agent/instrumentation/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -16,11 +16,8 @@ import datadog.trace.instrumentation.netty41.server.NettyHttpServerDecorator import datadog.trace.instrumentation.vertx_4_0.server.VertxDecorator import io.vertx.core.AbstractVerticle import io.vertx.core.Vertx -import spock.lang.Shared class VertxHttpServerForkedTest extends HttpServerTest { - @Shared - boolean isVertxLatest4x = Boolean.getBoolean('test.dd.latest4xTest') @Override HttpServer server() { @@ -68,8 +65,7 @@ class VertxHttpServerForkedTest extends HttpServerTest { @Override boolean testRequestBody() { - //FIXME: not working on 4.x latest - !isVertxLatest4x + true } @Override @@ -84,20 +80,17 @@ class VertxHttpServerForkedTest extends HttpServerTest { @Override boolean testBodyJson() { - //FIXME: not working on 4.x latest - !isVertxLatest4x + true } @Override boolean testBlocking() { - //FIXME: not working on 4.x latest - !isVertxLatest4x + true } @Override boolean testEncodedQuery() { - //FIXME: not working on 4.x latest - !isVertxLatest4x + true } @Override @@ -184,12 +177,4 @@ class VertxHttpServerWorkerForkedTest extends VertxHttpServerForkedTest { HttpServer server() { return new VertxServer(verticle(), routerBasePath(), true) } - - @Override - boolean testBlocking() { - //FIXME: ASM - // on the worker the requests are dispatched through a queue. - // Despite the blocking works, we fails recording that blocking exception - false - } } diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/build.gradle b/dd-java-agent/instrumentation/vertx-web-5.0/build.gradle new file mode 100644 index 00000000000..c11c4b7ab13 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/build.gradle @@ -0,0 +1,60 @@ +// Set properties before any plugins get loaded +ext { + minJavaVersionForTests = JavaVersion.VERSION_11 +} + +apply from: "$rootDir/gradle/java.gradle" + +muzzle { + pass { + group = 'io.vertx' + module = 'vertx-web' + versions = '[5.0.0.CR1,)' + javaVersion = '11' + includeSnapshots = true // to remove when vert.x 5 is finally released + } +} + +addTestSuiteForDir('latestDepTest', 'test') +addTestSuiteExtendingForDir('latestDepForkedTest', 'latestDepTest', 'test') + +configurations { + testArtifacts +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + +// Create test artifacts so vertx-rx can reuse the server test instrumentation and base class +artifacts { + testArtifacts testJar +} + +final vertxVersion = '5.0.0.CR3' + +dependencies { + api project(':dd-java-agent:instrumentation:netty-4.1-shared') + + compileOnly group: 'io.vertx', name: 'vertx-web', version: vertxVersion + + testImplementation project(':dd-java-agent:instrumentation:netty-4.1') + testImplementation project(':dd-java-agent:instrumentation:vertx-web-4.0') + + testImplementation project(':dd-java-agent:instrumentation:trace-annotation') + testImplementation(testFixtures(project(':dd-java-agent:agent-iast'))) + testRuntimeOnly project(':dd-java-agent:instrumentation:iast-instrumenter') + + testImplementation group: 'io.vertx', name: 'vertx-web', version: vertxVersion + testImplementation group: 'io.vertx', name: 'vertx-web-client', version: vertxVersion + + testImplementation testFixtures(project(':dd-java-agent:appsec')) + + testRuntimeOnly project(':dd-java-agent:instrumentation:jackson-core') + testRuntimeOnly project(':dd-java-agent:instrumentation:netty-buffer-4') + + latestDepTestImplementation group: 'io.vertx', name: 'vertx-web', version: '+' + latestDepTestImplementation group: 'io.vertx', name: 'vertx-web-client', version: '+' +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/HttpServerRequestInstrumentation.java b/dd-java-agent/instrumentation/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/HttpServerRequestInstrumentation.java new file mode 100644 index 00000000000..d303079ba50 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/HttpServerRequestInstrumentation.java @@ -0,0 +1,61 @@ +package datadog.trace.instrumentation.vertx_5_0.server; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.agent.tooling.muzzle.Reference; +import io.vertx.core.Handler; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpServerRequest; +import net.bytebuddy.asm.Advice; + +@AutoService(InstrumenterModule.class) +public class HttpServerRequestInstrumentation extends InstrumenterModule.AppSec + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + public HttpServerRequestInstrumentation() { + super("vertx", "vertx-5.0"); + } + + @Override + public String instrumentedType() { + return "io.vertx.core.http.HttpServerRequest"; + } + + @Override + public Reference[] additionalMuzzleReferences() { + return new Reference[] {VertxVersionMatcher.HTTP_HEADERS_INTERNAL}; + } + + @Override + public String[] helperClassNames() { + return new String[] { + "datadog.trace.instrumentation.vertx_5_0.server.WafPublishingBodyHandler", + "datadog.trace.instrumentation.vertx_5_0.server.WafPublishingBodyHandler$BufferWrapper", + }; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isPublic() + .and(named("bodyHandler")) + .and(takesArguments(1)) + .and(takesArgument(0, named("io.vertx.core.Handler"))), + HttpServerRequestInstrumentation.class.getName() + "$BodyHandlerAdvice"); + } + + /** @see HttpServerRequest#bodyHandler(Handler) */ + static class BodyHandlerAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + static void after(@Advice.Argument(value = 0, readOnly = false) Handler h) { + if (h != null) { + h = new WafPublishingBodyHandler(h); + } + } + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/VertxVersionMatcher.java b/dd-java-agent/instrumentation/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/VertxVersionMatcher.java new file mode 100644 index 00000000000..3eef7d7bb3f --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/VertxVersionMatcher.java @@ -0,0 +1,11 @@ +package datadog.trace.instrumentation.vertx_5_0.server; + +import datadog.trace.agent.tooling.muzzle.Reference; + +// checks for vertx > 5 +public class VertxVersionMatcher { + + // added in 5.0 + public static final Reference HTTP_HEADERS_INTERNAL = + new Reference.Builder("io.vertx.core.internal.http.HttpHeadersInternal").build(); +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/WafPublishingBodyHandler.java b/dd-java-agent/instrumentation/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/WafPublishingBodyHandler.java new file mode 100644 index 00000000000..39163525098 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/WafPublishingBodyHandler.java @@ -0,0 +1,537 @@ +package datadog.trace.instrumentation.vertx_5_0.server; + +import static datadog.trace.api.gateway.Events.EVENTS; + +import datadog.appsec.api.blocking.BlockingException; +import datadog.trace.api.gateway.BlockResponseFunction; +import datadog.trace.api.gateway.CallbackProvider; +import datadog.trace.api.gateway.Flow; +import datadog.trace.api.gateway.RequestContext; +import datadog.trace.api.gateway.RequestContextSlot; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import io.vertx.core.Handler; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiFunction; + +public class WafPublishingBodyHandler implements Handler { + private final Handler delegate; + + public WafPublishingBodyHandler(Handler delegate) { + this.delegate = delegate; + } + + @Override + public void handle(Buffer buffer) { + this.delegate.handle(new BufferWrapper(buffer)); + } + + static class BufferWrapper implements Buffer { + private final Buffer delegate; + private final AtomicBoolean hasPublished = new AtomicBoolean(); + + BufferWrapper(Buffer delegate) { + this.delegate = delegate; + } + + private void publishRequestBody(Object body) { + if (!hasPublished.compareAndSet(false, true)) { + return; + } + + AgentSpan span = AgentTracer.activeSpan(); + if (span == null) { + return; + } + + CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC); + BiFunction> callback = + cbp.getCallback(EVENTS.requestBodyProcessed()); + if (callback == null) { + return; + } + + RequestContext reqCtx = span.getRequestContext(); + if (reqCtx == null) { + return; + } + + Flow flow = callback.apply(reqCtx, body); + Flow.Action action = flow.getAction(); + if (action instanceof Flow.Action.RequestBlockingAction) { + BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction(); + if (blockResponseFunction == null) { + return; + } + Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action; + blockResponseFunction.tryCommitBlockingResponse( + reqCtx.getTraceSegment(), + rba.getStatusCode(), + rba.getBlockingContentType(), + rba.getExtraHeaders()); + throw new BlockingException( + "Blocked request (for Buffer/toString or Buffer/toJson{Object,Array})"); + } + } + + @Override + public String toString() { + String s = delegate.toString(); + publishRequestBody(s); + return s; + } + + @Override + public String toString(String enc) { + String s = delegate.toString(enc); + publishRequestBody(s); + return s; + } + + @Override + public String toString(Charset enc) { + String s = delegate.toString(enc); + publishRequestBody(s); + return s; + } + + @Override + public JsonObject toJsonObject() { + JsonObject jo = delegate.toJsonObject(); + publishRequestBody(jo.getMap()); + return jo; + } + + @Override + public JsonArray toJsonArray() { + JsonArray ja = delegate.toJsonArray(); + publishRequestBody(ja.getList()); + return ja; + } + + @Override + public byte getByte(int pos) { + return delegate.getByte(pos); + } + + @Override + public short getUnsignedByte(int pos) { + return delegate.getUnsignedByte(pos); + } + + @Override + public int getInt(int pos) { + return delegate.getInt(pos); + } + + @Override + public int getIntLE(int pos) { + return delegate.getIntLE(pos); + } + + @Override + public long getUnsignedInt(int pos) { + return delegate.getUnsignedInt(pos); + } + + @Override + public long getUnsignedIntLE(int pos) { + return delegate.getUnsignedIntLE(pos); + } + + @Override + public long getLong(int pos) { + return delegate.getLong(pos); + } + + @Override + public long getLongLE(int pos) { + return delegate.getLongLE(pos); + } + + @Override + public double getDouble(int pos) { + return delegate.getDouble(pos); + } + + @Override + public double getDoubleLE(int i) { + return delegate.getDoubleLE(i); + } + + @Override + public float getFloatLE(int i) { + return delegate.getFloatLE(i); + } + + @Override + public Buffer appendFloatLE(float v) { + return delegate.appendFloatLE(v); + } + + @Override + public Buffer appendDoubleLE(double v) { + return delegate.appendDoubleLE(v); + } + + @Override + public Buffer setDoubleLE(int i, double v) { + return delegate.setDoubleLE(i, v); + } + + @Override + public Buffer setFloatLE(int i, float v) { + return delegate.setFloatLE(i, v); + } + + @Override + public float getFloat(int pos) { + return delegate.getFloat(pos); + } + + @Override + public short getShort(int pos) { + return delegate.getShort(pos); + } + + @Override + public short getShortLE(int pos) { + return delegate.getShortLE(pos); + } + + @Override + public int getUnsignedShort(int pos) { + return delegate.getUnsignedShort(pos); + } + + @Override + public int getUnsignedShortLE(int pos) { + return delegate.getUnsignedShortLE(pos); + } + + @Override + public int getMedium(int pos) { + return delegate.getMedium(pos); + } + + @Override + public int getMediumLE(int pos) { + return delegate.getMediumLE(pos); + } + + @Override + public int getUnsignedMedium(int pos) { + return delegate.getUnsignedMedium(pos); + } + + @Override + public int getUnsignedMediumLE(int pos) { + return delegate.getUnsignedMediumLE(pos); + } + + @Override + public byte[] getBytes() { + return delegate.getBytes(); + } + + @Override + public byte[] getBytes(int start, int end) { + return delegate.getBytes(start, end); + } + + @Override + public Buffer getBytes(byte[] dst) { + return delegate.getBytes(dst); + } + + @Override + public Buffer getBytes(byte[] dst, int dstIndex) { + return delegate.getBytes(dst, dstIndex); + } + + @Override + public Buffer getBytes(int start, int end, byte[] dst) { + return delegate.getBytes(start, end, dst); + } + + @Override + public Buffer getBytes(int start, int end, byte[] dst, int dstIndex) { + return delegate.getBytes(start, end, dst, dstIndex); + } + + @Override + public Buffer getBuffer(int start, int end) { + return delegate.getBuffer(start, end); + } + + @Override + public String getString(int start, int end, String enc) { + return delegate.getString(start, end, enc); + } + + @Override + public String getString(int start, int end) { + return delegate.getString(start, end); + } + + @Override + public Buffer appendBuffer(Buffer buff) { + return delegate.appendBuffer(buff); + } + + @Override + public Buffer appendBuffer(Buffer buff, int offset, int len) { + return delegate.appendBuffer(buff, offset, len); + } + + @Override + public Buffer appendBytes(byte[] bytes) { + return delegate.appendBytes(bytes); + } + + @Override + public Buffer appendBytes(byte[] bytes, int offset, int len) { + return delegate.appendBytes(bytes, offset, len); + } + + @Override + public Buffer appendByte(byte b) { + return delegate.appendByte(b); + } + + @Override + public Buffer appendUnsignedByte(short b) { + return delegate.appendUnsignedByte(b); + } + + @Override + public Buffer appendInt(int i) { + return delegate.appendInt(i); + } + + @Override + public Buffer appendIntLE(int i) { + return delegate.appendIntLE(i); + } + + @Override + public Buffer appendUnsignedInt(long i) { + return delegate.appendUnsignedInt(i); + } + + @Override + public Buffer appendUnsignedIntLE(long i) { + return delegate.appendUnsignedIntLE(i); + } + + @Override + public Buffer appendMedium(int i) { + return delegate.appendMedium(i); + } + + @Override + public Buffer appendMediumLE(int i) { + return delegate.appendMediumLE(i); + } + + @Override + public Buffer appendLong(long l) { + return delegate.appendLong(l); + } + + @Override + public Buffer appendLongLE(long l) { + return delegate.appendLongLE(l); + } + + @Override + public Buffer appendShort(short s) { + return delegate.appendShort(s); + } + + @Override + public Buffer appendShortLE(short s) { + return delegate.appendShortLE(s); + } + + @Override + public Buffer appendUnsignedShort(int s) { + return delegate.appendUnsignedShort(s); + } + + @Override + public Buffer appendUnsignedShortLE(int s) { + return delegate.appendUnsignedShortLE(s); + } + + @Override + public Buffer appendFloat(float f) { + return delegate.appendFloat(f); + } + + @Override + public Buffer appendDouble(double d) { + return delegate.appendDouble(d); + } + + @Override + public Buffer appendString(String str, String enc) { + return delegate.appendString(str, enc); + } + + @Override + public Buffer appendString(String str) { + return delegate.appendString(str); + } + + @Override + public Buffer setByte(int pos, byte b) { + return delegate.setByte(pos, b); + } + + @Override + public Buffer setUnsignedByte(int pos, short b) { + return delegate.setUnsignedByte(pos, b); + } + + @Override + public Buffer setInt(int pos, int i) { + return delegate.setInt(pos, i); + } + + @Override + public Buffer setIntLE(int pos, int i) { + return delegate.setIntLE(pos, i); + } + + @Override + public Buffer setUnsignedInt(int pos, long i) { + return delegate.setUnsignedInt(pos, i); + } + + @Override + public Buffer setUnsignedIntLE(int pos, long i) { + return delegate.setUnsignedIntLE(pos, i); + } + + @Override + public Buffer setMedium(int pos, int i) { + return delegate.setMedium(pos, i); + } + + @Override + public Buffer setMediumLE(int pos, int i) { + return delegate.setMediumLE(pos, i); + } + + @Override + public Buffer setLong(int pos, long l) { + return delegate.setLong(pos, l); + } + + @Override + public Buffer setLongLE(int pos, long l) { + return delegate.setLongLE(pos, l); + } + + @Override + public Buffer setDouble(int pos, double d) { + return delegate.setDouble(pos, d); + } + + @Override + public Buffer setFloat(int pos, float f) { + return delegate.setFloat(pos, f); + } + + @Override + public Buffer setShort(int pos, short s) { + return delegate.setShort(pos, s); + } + + @Override + public Buffer setShortLE(int pos, short s) { + return delegate.setShortLE(pos, s); + } + + @Override + public Buffer setUnsignedShort(int pos, int s) { + return delegate.setUnsignedShort(pos, s); + } + + @Override + public Buffer setUnsignedShortLE(int pos, int s) { + return delegate.setUnsignedShortLE(pos, s); + } + + @Override + public Buffer setBuffer(int pos, Buffer b) { + return delegate.setBuffer(pos, b); + } + + @Override + public Buffer setBuffer(int pos, Buffer b, int offset, int len) { + return delegate.setBuffer(pos, b, offset, len); + } + + @Override + public Buffer setBytes(int pos, ByteBuffer b) { + return delegate.setBytes(pos, b); + } + + @Override + public Buffer setBytes(int pos, byte[] b) { + return delegate.setBytes(pos, b); + } + + @Override + public Buffer setBytes(int pos, byte[] b, int offset, int len) { + return delegate.setBytes(pos, b, offset, len); + } + + @Override + public Buffer setString(int pos, String str) { + return delegate.setString(pos, str); + } + + @Override + public Buffer setString(int pos, String str, String enc) { + return delegate.setString(pos, str, enc); + } + + @Override + public int length() { + return delegate.length(); + } + + @Override + public Buffer copy() { + return delegate.copy(); + } + + @Override + public Buffer slice() { + return delegate.slice(); + } + + @Override + public Buffer slice(int start, int end) { + return delegate.slice(start, end); + } + + @Override + public void writeToBuffer(Buffer buffer) { + delegate.writeToBuffer(buffer); + } + + @Override + public int readFromBuffer(int pos, Buffer buffer) { + return delegate.readFromBuffer(pos, buffer); + } + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/client/VertxHttpClientForkedTest.groovy b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/client/VertxHttpClientForkedTest.groovy new file mode 100644 index 00000000000..0e528befe84 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/client/VertxHttpClientForkedTest.groovy @@ -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 headers, String body, Closure callback) { + return doRequest(method, uri, headers, body, callback, -1) + } + + int doRequest(String method, URI uri, Map headers, String body, Closure callback, long timeout) { + CompletableFuture 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 + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/core/BufferInstrumentationTest.groovy b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/core/BufferInstrumentationTest.groovy new file mode 100644 index 00000000000..9d7992d9e37 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/core/BufferInstrumentationTest.groovy @@ -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 + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/core/MultiMapInstrumentationTest.groovy b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/core/MultiMapInstrumentationTest.groovy new file mode 100644 index 00000000000..69223b107a1 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/core/MultiMapInstrumentationTest.groovy @@ -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 runUnderIastTrace(Closure 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 multiMaps() { + return [ + new HeadersMultiMap(), + new HeadersAdaptor(new DefaultHttpHeaders()), + new Http2HeadersAdaptor(new DefaultHttp2Headers()) + ] + } + + private static void addAll(final Map map, final MultiMap headers) { + map.each { key, value -> headers.add(key, value) } + } + + private static void addAll(final List> list, final MultiMap headers) { + list.each { addAll(it, headers) } + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/IastVertxHttp1ServerTest.groovy b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/IastVertxHttp1ServerTest.groovy new file mode 100644 index 00000000000..2d35d6c9634 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/IastVertxHttp1ServerTest.groovy @@ -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() +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/IastVertxHttp2ServerTest.groovy b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/IastVertxHttp2ServerTest.groovy new file mode 100644 index 00000000000..5f08aa82f86 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/IastVertxHttp2ServerTest.groovy @@ -0,0 +1,64 @@ +package server + +import datadog.trace.agent.test.utils.OkHttpUtils +import spock.lang.Ignore + +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSession +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager +import java.security.SecureRandom +import java.security.cert.CertificateException +import java.security.cert.X509Certificate + +// http2 seems to be not supported from the tracer +@Ignore +class IastVertxHttp2ServerTest extends IastVertxHttpServerTest { + + def setupSpec() { + final trustManager = trustManager() + client = OkHttpUtils.clientBuilder() + .sslSocketFactory(socketFactory(trustManager), trustManager) + .hostnameVerifier(hostnameVerifier()) + .build() + } + + @Override + boolean isHttps() { + true + } + + private static HostnameVerifier hostnameVerifier() { + return new HostnameVerifier() { + @Override + boolean verify(String s, SSLSession sslSession) { + true + } + } + } + + private static X509TrustManager trustManager() { + return new X509TrustManager() { + @Override + void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + } + + @Override + void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + } + + @Override + X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0] + } + } + } + + private static SSLSocketFactory socketFactory(final X509TrustManager trustManager) { + final sslContext = SSLContext.getInstance("TLSv1.2") + sslContext.init(null, [trustManager].toArray(new TrustManager[0]), new SecureRandom()) + return sslContext.getSocketFactory() + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/IastVertxHttpServerTest.groovy b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/IastVertxHttpServerTest.groovy new file mode 100644 index 00000000000..ddfeea74da9 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/IastVertxHttpServerTest.groovy @@ -0,0 +1,76 @@ +package server + +import com.datadog.iast.test.IastSourcesTest +import datadog.trace.agent.test.base.HttpServer +import io.vertx.core.DeploymentOptions +import io.vertx.core.Vertx +import io.vertx.core.http.HttpServerRequest +import io.vertx.core.json.JsonObject +import okhttp3.MediaType +import okhttp3.Request +import okhttp3.RequestBody + +import java.util.concurrent.CompletableFuture + +abstract class IastVertxHttpServerTest extends IastSourcesTest { + + @Override + HttpServer server() { + return new IastVertxServer() + } + + boolean isHttps() { + false + } + + void 'test event bus'() { + when: + final url = "${address}/iast/sources/eventBus" + final body = RequestBody.create(MediaType.get('application/json'), '{ "name": "value" }') + final request = new Request.Builder().url(url).post(body).build() + final response = client.newCall(request).execute() + + then: + response.code() == 200 + response.body().string() == 'OK' + } + + class IastVertxServer implements HttpServer { + private Vertx server + private port + + @Override + void start() { + server = Vertx.vertx() + final future = new CompletableFuture<>() + server.eventBus().localConsumer('PORT_DATA') + .handler({ message -> + port = message.body() + message.reply(null) + future.complete(null) + }) + final deployment = new DeploymentOptions() + .setInstances(1) + .setConfig(new JsonObject().put('https', isHttps())) + server.deployVerticle('server.IastSourcesVerticle', deployment).await() + future.get() + } + + @Override + void stop() { + server.close() + } + + @Override + URI address() { + return new URI("http${https ? 's' : ''}://localhost:$port/") + } + } + + // Cookies not supported in Vert.x 4.0.0 + @Override + protected boolean ignoreCookies() { + final hasCookies = HttpServerRequest.declaredMethods.any { it.name == 'cookies' } + return !hasCookies + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/IastVertxSinksTest.groovy b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/IastVertxSinksTest.groovy new file mode 100644 index 00000000000..3ec89d86398 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/IastVertxSinksTest.groovy @@ -0,0 +1,95 @@ +package server + +import com.datadog.iast.test.IastHttpServerTest +import datadog.trace.agent.test.base.HttpServer +import datadog.trace.api.iast.InstrumentationBridge +import datadog.trace.api.iast.sink.UnvalidatedRedirectModule +import io.vertx.core.DeploymentOptions +import io.vertx.core.Vertx +import io.vertx.core.internal.VertxInternal +import okhttp3.Request + +import java.util.concurrent.CompletableFuture + +class IastVertxSinksTest extends IastHttpServerTest { + + @Override + HttpServer server() { + return new Vertx40Server() + } + + + + void 'test unvalidated redirect reroute1'() { + given: + final module = Mock(UnvalidatedRedirectModule) + InstrumentationBridge.registerIastModule(module) + final url = "${address}/iast/sinks/reroute1?path=rerouted" + final request = new Request.Builder().url(url).build() + + when: + client.newCall(request).execute() + + then: + 1 * module.onRedirect("rerouted") + } + + void 'test unvalidated redirect reroute2'() { + given: + final module = Mock(UnvalidatedRedirectModule) + InstrumentationBridge.registerIastModule(module) + final url = "${address}/iast/sinks/reroute2?path=rerouted" + final request = new Request.Builder().url(url).build() + + when: + client.newCall(request).execute() + + then: + 1 * module.onRedirect("rerouted") + } + + void 'test unvalidated redirect location header'() { + given: + final module = Mock(UnvalidatedRedirectModule) + InstrumentationBridge.registerIastModule(module) + final url = "${address}/iast/sinks/redirectheader?name=Location&value=path" + final request = new Request.Builder().url(url).build() + + when: + client.newCall(request).execute() + + then: + 1 * module.onHeader("Location", "path") + } + + private class Vertx40Server implements HttpServer { + private VertxInternal server + private int port = 0 + + @Override + void start() { + server = Vertx.vertx() + final future = new CompletableFuture<>() + server.eventBus().localConsumer('PORT_DATA') + .handler({ message -> + port = message.body() + message.reply(null) + future.complete(null) + }) + final deployment = new DeploymentOptions() + .setInstances(1) + server.deployVerticle('server.IastSinksVerticle', deployment).await() + future.get() + } + + @Override + void stop() { + server.close() + } + + @Override + URI address() { + return new URI("http://localhost:$port/") + } + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy new file mode 100644 index 00000000000..c1111f61ecc --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -0,0 +1,174 @@ +package server + +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.LOGIN +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS + +import datadog.trace.agent.test.asserts.TraceAssert +import datadog.trace.agent.test.base.HttpServer +import datadog.trace.agent.test.base.HttpServerTest +import datadog.trace.api.DDSpanTypes +import datadog.trace.bootstrap.instrumentation.api.Tags +import datadog.trace.instrumentation.netty41.server.NettyHttpServerDecorator +import datadog.trace.instrumentation.vertx_4_0.server.VertxDecorator +import io.vertx.core.AbstractVerticle +import io.vertx.core.Vertx + +class VertxHttpServerForkedTest extends HttpServerTest { + @Override + HttpServer server() { + return new VertxServer(verticle(), routerBasePath()) + } + + protected Class verticle() { + VertxTestServer + } + + String routerBasePath() { + return "/" + } + + @Override + String component() { + return NettyHttpServerDecorator.DECORATE.component() + } + + @Override + String expectedOperationName() { + "netty.request" + } + + @Override + protected boolean enabledFinishTimingChecks() { + true + } + + @Override + String testPathParam() { + routerBasePath() + "path/:id/param" + } + + @Override + boolean testExceptionBody() { + // Vertx wraps the exception + false + } + + @Override + Map expectedIGPathParams() { + [id: '123'] + } + + @Override + boolean testRequestBody() { + true + } + + @Override + boolean testBodyUrlencoded() { + true + } + + @Override + boolean testBodyMultipart() { + true + } + + @Override + boolean testBodyJson() { + true + } + + @Override + boolean testBlocking() { + true + } + + @Override + boolean testBlockingOnResponse() { + true + } + + @Override + boolean isRequestBodyNoStreaming() { + true + } + + @Override + Class expectedExceptionType() { + return RuntimeException + } + + boolean testExceptionTag() { + true + } + + @Override + boolean hasDecodedResource() { + return false + } + + @Override + int spanCount(ServerEndpoint endpoint) { + if (endpoint == NOT_FOUND) { + return super.spanCount(endpoint) - 1 + } + return super.spanCount(endpoint) + } + + @Override + boolean hasHandlerSpan() { + true + } + + @Override + boolean testSessionId() { + true + } + + @Override + Serializable expectedServerSpanRoute(ServerEndpoint endpoint) { + switch (endpoint) { + case LOGIN: + case NOT_FOUND: + return null + case PATH_PARAM: + return testPathParam() + default: + return routerBasePath() + endpoint.relativePath() + } + } + + @Override + void handlerSpan(TraceAssert trace, ServerEndpoint endpoint = SUCCESS) { + if (endpoint == NOT_FOUND) { + return + } + trace.span { + serviceName expectedServiceName() + operationName "vertx.route-handler" + spanType DDSpanTypes.HTTP_SERVER + errored endpoint == ERROR || endpoint == EXCEPTION + childOfPrevious() + tags { + "$Tags.COMPONENT" VertxDecorator.DECORATE.component() + "$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER + "$Tags.HTTP_STATUS" Integer + if (endpoint == EXCEPTION && this.testExceptionTag()) { + errorTags(RuntimeException, EXCEPTION.body) + } + defaultTags() + } + } + } +} + +class VertxHttpServerWorkerForkedTest extends VertxHttpServerForkedTest { + @Override + HttpServer server() { + return new VertxServer(verticle(), routerBasePath(), true) + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/VertxInactiveAppSecTest.groovy b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/VertxInactiveAppSecTest.groovy new file mode 100644 index 00000000000..31009d5c33c --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/VertxInactiveAppSecTest.groovy @@ -0,0 +1,15 @@ +package server + +import com.datadog.appsec.AppSecInactiveHttpServerTest +import datadog.trace.agent.test.base.HttpServer + +class VertxInactiveAppSecTest extends AppSecInactiveHttpServerTest { + @Override + boolean isTestPathParam() { + true + } + + HttpServer server() { + new VertxServer(VertxTestServer, '/') + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/VertxMiddlewareHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/VertxMiddlewareHttpServerForkedTest.groovy new file mode 100644 index 00000000000..ca82d1aa6be --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/VertxMiddlewareHttpServerForkedTest.groovy @@ -0,0 +1,44 @@ +package server + +import datadog.trace.agent.test.asserts.TraceAssert +import datadog.trace.api.DDSpanTypes +import datadog.trace.bootstrap.instrumentation.api.Tags +import datadog.trace.instrumentation.vertx_4_0.server.VertxDecorator +import io.vertx.core.AbstractVerticle + +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS + +class VertxMiddlewareHttpServerForkedTest extends VertxHttpServerForkedTest { + @Override + protected Class verticle() { + VertxMiddlewareTestServer + } + + @Override + int spanCount(ServerEndpoint endpoint) { + return 2 + (hasHandlerSpan() ? 1 : 0) + (hasResponseSpan(endpoint) ? 1 : 0) + } + + @Override + void handlerSpan(TraceAssert trace, ServerEndpoint endpoint = SUCCESS) { + trace.span { + serviceName expectedServiceName() + operationName "vertx.route-handler" + spanType DDSpanTypes.HTTP_SERVER + errored endpoint == ERROR || endpoint == EXCEPTION + childOfPrevious() + tags { + "$Tags.COMPONENT" VertxDecorator.DECORATE.component() + "$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER + "$Tags.HTTP_STATUS" Integer + "before" true + if (endpoint == EXCEPTION && this.testExceptionTag()) { + errorTags(RuntimeException, EXCEPTION.body) + } + defaultTags() + } + } + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/VertxServer.groovy b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/VertxServer.groovy new file mode 100644 index 00000000000..eb7b78343fe --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/VertxServer.groovy @@ -0,0 +1,59 @@ +package server + +import datadog.trace.agent.test.base.HttpServer +import io.vertx.core.AbstractVerticle +import io.vertx.core.DeploymentOptions +import io.vertx.core.ThreadingModel +import io.vertx.core.Vertx +import io.vertx.core.internal.VertxInternal +import io.vertx.core.json.JsonObject + +import java.util.concurrent.CompletableFuture + +class VertxServer implements HttpServer { + private VertxInternal server + private String routerBasePath + private port + private boolean useWorker + Class verticle + + VertxServer(Class verticle, String routerBasePath, boolean useWorker = false) { + this.routerBasePath = routerBasePath + this.verticle = verticle + this.useWorker = useWorker + } + + @Override + void start() { + server = Vertx.vertx() + + final CompletableFuture future = new CompletableFuture<>() + server.eventBus().localConsumer("PORT_DATA") + .handler({ message -> + port = message.body() + message.reply(null) + future.complete(null) + }) + + def deployOptions = new DeploymentOptions() + .setConfig(new JsonObject().put(VertxTestServer.CONFIG_HTTP_SERVER_PORT, 0)) + .setInstances(1) + + if (useWorker) { + deployOptions = deployOptions.setWorkerPoolSize(1).setThreadingModel(ThreadingModel.WORKER) + } + server.deployVerticle(verticle.name, deployOptions).await() + + future.get() + } + + @Override + void stop() { + server.close() + } + + @Override + URI address() { + return new URI("http://localhost:$port$routerBasePath") + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/VertxSubrouterForkedTest.groovy b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/VertxSubrouterForkedTest.groovy new file mode 100644 index 00000000000..26f5b27d894 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/groovy/server/VertxSubrouterForkedTest.groovy @@ -0,0 +1,16 @@ +package server + + +import io.vertx.core.AbstractVerticle + +class VertxSubrouterForkedTest extends VertxHttpServerForkedTest { + @Override + protected Class verticle() { + VertxSubrouterTestServer + } + + @Override + String routerBasePath() { + return "/sub/" + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/java/server/IastSinksVerticle.java b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/java/server/IastSinksVerticle.java new file mode 100644 index 00000000000..a518d274e99 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/java/server/IastSinksVerticle.java @@ -0,0 +1,61 @@ +package server; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import io.vertx.core.eventbus.EventBus; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.HttpServerOptions; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; + +public class IastSinksVerticle extends AbstractVerticle { + + @Override + public void start(final Promise startPromise) throws Exception { + final Router router = Router.router(vertx); + router.route().handler(BodyHandler.create()); + router + .route("/iast/sinks/reroute1") + .handler( + rc -> { + final String path = rc.request().getParam("path"); + rc.reroute(path); + }); + router + .route("/iast/sinks/reroute2") + .handler( + rc -> { + final String path = rc.request().getParam("path"); + rc.reroute(HttpMethod.GET, path); + }); + router + .route("/iast/sinks/redirectheader") + .handler( + rc -> { + final String name = rc.request().getParam("name"); + final String value = rc.request().getParam("value"); + rc.response().putHeader(name, value).end(); + }); + + final EventBus eventBus = vertx.eventBus(); + final HttpServerOptions serverOptions = new HttpServerOptions(); + serverOptions.setHandle100ContinueAutomatically(true); + vertx + .createHttpServer(serverOptions) + .requestHandler(router) + .listen(0) + .onSuccess( + server -> + eventBus + .request("PORT_DATA", server.actualPort()) + .andThen( + ar -> { + if (ar.succeeded()) { + startPromise.complete(); + } else { + startPromise.fail(ar.cause()); + } + })) + .onFailure(startPromise::fail); + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/java/server/IastSourcesVerticle.java b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/java/server/IastSourcesVerticle.java new file mode 100644 index 00000000000..35cef8bc955 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/java/server/IastSourcesVerticle.java @@ -0,0 +1,179 @@ +package server; + +import com.datadog.iast.taint.TaintedObjects; +import datadog.trace.api.iast.IastContext; +import io.vertx.core.AbstractVerticle; +import io.vertx.core.MultiMap; +import io.vertx.core.Promise; +import io.vertx.core.eventbus.EventBus; +import io.vertx.core.http.Cookie; +import io.vertx.core.http.HttpServerOptions; +import io.vertx.core.json.JsonObject; +import io.vertx.core.net.SelfSignedCertificate; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class IastSourcesVerticle extends AbstractVerticle { + + private static final String EVENT_BUS_ENDPOINT = "EVENT_BUS"; + + private static final Logger LOGGER = LoggerFactory.getLogger(IastSourcesVerticle.class); + + @Override + public void start(final Promise startPromise) throws Exception { + final Router router = Router.router(vertx); + router.route().handler(BodyHandler.create()); + router + .route("/iast/sources/header") + .handler( + rc -> { + final String value = rc.request().getHeader("name"); + rc.response().end("Received " + value); + }); + router + .route("/iast/sources/headers") + .handler( + rc -> { + final MultiMap value = rc.request().headers(); + rc.response().end("Received " + value.get("name")); + }); + router + .route("/iast/sources/cookie") + .handler( + rc -> { + final Cookie cookie = rc.request().getCookie("name"); + rc.response().end("Received " + cookie.getName() + " " + cookie.getValue()); + }); + router + .route("/iast/sources/path/:name") + .handler( + rc -> { + final String value = rc.pathParam("name"); + rc.response().end("Received " + value); + }); + router + .route("/iast/sources/parameter") + .handler( + rc -> { + final String value = rc.request().getParam("name"); + rc.response().end("Received " + value); + }); + router + .route("/iast/sources/parameters") + .handler( + rc -> { + final MultiMap value = rc.request().params(); + rc.response().end("Received " + value.get("name")); + }); + router + .route("/iast/sources/form") + .handler( + rc -> { + final String value = rc.request().getFormAttribute("name"); + rc.response().end("Received " + value); + }); + router + .route("/iast/sources/body/string") + .handler( + rc -> { + final String encoding = rc.request().getParam("encoding"); + if (encoding != null) { + rc.response().end("Received " + rc.body().asString(encoding)); + } else { + rc.response().end("Received " + rc.body().asString()); + } + }); + router + .route("/iast/sources/body/json") + .handler( + rc -> { + rc.response().end("Received " + rc.body().asJsonObject()); + }); + router + .route("/body/jsonArray") + .handler( + rc -> { + rc.response().end("Received " + rc.body().asJsonArray()); + }); + + router + .route("/iast/sources/eventBus") + .handler( + rc -> { + final JsonObject target = rc.body().asJsonObject(); + rc.vertx() + .eventBus() + .request(EVENT_BUS_ENDPOINT, target) + .andThen( + reply -> { + if (reply.succeeded()) { + rc.response().end(reply.result().body().toString()); + } else { + rc.fail(reply.cause()); + } + }); + }); + router + .route("/iast/vulnerabilities/insecureCookie") + .handler( + rc -> { + final String cookieName = rc.request().getParam("name"); + final String cookieValue = rc.request().getParam("value"); + final String secure = rc.request().getParam("secure"); + Cookie cookie = Cookie.cookie(cookieName, cookieValue); + if ("true".equals(secure)) { + cookie.setSecure(true); + } + rc.response().addCookie(cookie).end("Cookie Set"); + }); + + final EventBus eventBus = vertx.eventBus(); + eventBus.consumer( + EVENT_BUS_ENDPOINT, + message -> { + final JsonObject payload = (JsonObject) message.body(); + final String name = payload.getString("name"); + try { + final IastContext ctx = IastContext.Provider.get(); + if (ctx == null) { + throw new IllegalStateException("No IAST context present"); + } + final TaintedObjects to = ctx.getTaintedObjects(); + final boolean tainted = to.get(name) != null; + message.reply(tainted ? "OK" : "NO_OK"); + } catch (Throwable e) { + LOGGER.error("Failed to handle event bus message", e); + message.reply("NO_OK"); + } + }); + + final HttpServerOptions serverOptions = new HttpServerOptions(); + if (config().getBoolean("https")) { + final SelfSignedCertificate certificate = SelfSignedCertificate.create(); + serverOptions.setSsl(true); + serverOptions.setUseAlpn(true); + serverOptions.setTrustOptions(certificate.trustOptions()); + serverOptions.setKeyCertOptions(certificate.keyCertOptions()); + } + serverOptions.setHandle100ContinueAutomatically(true); + vertx + .createHttpServer(serverOptions) + .requestHandler(router) + .listen(0) + .onSuccess( + server -> + eventBus + .request("PORT_DATA", server.actualPort()) + .andThen( + ar -> { + if (ar.succeeded()) { + startPromise.complete(); + } else { + startPromise.fail(ar.cause()); + } + })) + .onFailure(startPromise::fail); + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/java/server/VertxMiddlewareTestServer.java b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/java/server/VertxMiddlewareTestServer.java new file mode 100644 index 00000000000..e77f1b6fb65 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/java/server/VertxMiddlewareTestServer.java @@ -0,0 +1,17 @@ +package server; + +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; + +public class VertxMiddlewareTestServer extends VertxTestServer { + @Override + protected void customizeBeforeRoutes(Router router) { + router.route().handler(VertxMiddlewareTestServer::firstHandler); + } + + private static void firstHandler(final RoutingContext ctx) { + AgentTracer.activeSpan().setTag("before", true); + ctx.next(); + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/java/server/VertxSubrouterTestServer.java b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/java/server/VertxSubrouterTestServer.java new file mode 100644 index 00000000000..36f1814b1c6 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/java/server/VertxSubrouterTestServer.java @@ -0,0 +1,12 @@ +package server; + +import io.vertx.ext.web.Router; + +public class VertxSubrouterTestServer extends VertxTestServer { + @Override + protected Router customizeAfterRoutes(Router configured) { + Router router = Router.router(vertx); + router.route("/sub/*").subRouter(configured); + return router; + } +} diff --git a/dd-java-agent/instrumentation/vertx-web-5.0/src/test/java/server/VertxTestServer.java b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/java/server/VertxTestServer.java new file mode 100644 index 00000000000..10dc9526df0 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx-web-5.0/src/test/java/server/VertxTestServer.java @@ -0,0 +1,284 @@ +package server; + +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_JSON; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_MULTIPART; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_URLENCODED; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.CREATED; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.FORWARDED; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_ENCODED_BOTH; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_ENCODED_QUERY; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SESSION_ID; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.UNKNOWN; +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.USER_BLOCK; +import static datadog.trace.agent.test.utils.TraceUtils.runnableUnderTraceAsync; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeScope; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; + +import datadog.appsec.api.blocking.Blocking; +import datadog.trace.agent.test.base.HttpServerTest; +import datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint; +import io.vertx.core.AbstractVerticle; +import io.vertx.core.MultiMap; +import io.vertx.core.Promise; +import io.vertx.core.http.HttpServerOptions; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.ext.web.handler.SessionHandler; +import io.vertx.ext.web.sstore.LocalSessionStore; + +public class VertxTestServer extends AbstractVerticle { + public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port"; + public static final String PORT_DATA_ADDRESS = "PORT_DATA"; + + @Override + public void start(final Promise startPromise) { + final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT); + Router router = Router.router(vertx); + + customizeBeforeRoutes(router); + + router + .route(SUCCESS.getPath()) + .handler( + ctx -> + controller( + ctx, + SUCCESS, + () -> + ctx.response().setStatusCode(SUCCESS.getStatus()).end(SUCCESS.getBody()))); + router + .route(FORWARDED.getPath()) + .handler( + ctx -> + controller( + ctx, + FORWARDED, + () -> + ctx.response() + .setStatusCode(FORWARDED.getStatus()) + .end(ctx.request().getHeader("x-forwarded-for")))); + router + .route(CREATED.getPath()) + .handler( + ctx -> + controller( + ctx, + CREATED, + () -> + ctx.request() + .bodyHandler( + body -> + ctx.response() + .setStatusCode(CREATED.getStatus()) + .end(CREATED.getBody() + ": " + body.toString())))); + router.route(BODY_URLENCODED.getPath()).handler(BodyHandler.create()); + router + .route(BODY_URLENCODED.getPath()) + .handler( + ctx -> + controller( + ctx, + BODY_URLENCODED, + () -> { + String res = convertFormAttributes(ctx); + ctx.response().setStatusCode(BODY_URLENCODED.getStatus()).end(res); + })); + router + .route(BODY_MULTIPART.getPath()) + .handler( + ctx -> + controller( + ctx, + BODY_MULTIPART, + () -> { + ctx.request().setExpectMultipart(true); + ctx.request() + .endHandler( + (_void) -> { + String res = convertFormAttributes(ctx); + ctx.response().setStatusCode(BODY_MULTIPART.getStatus()).end(res); + }); + })); + router.route(BODY_JSON.getPath()).handler(BodyHandler.create()); + router + .route(BODY_JSON.getPath()) + .handler( + ctx -> + controller( + ctx, + BODY_JSON, + () -> { + JsonObject json = ctx.body().asJsonObject(); + ctx.response().setStatusCode(BODY_JSON.getStatus()).end(json.toString()); + })); + router + .route(QUERY_ENCODED_BOTH.getRawPath()) + .handler( + ctx -> + controller( + ctx, + QUERY_ENCODED_BOTH, + () -> + ctx.response() + .setStatusCode(QUERY_ENCODED_BOTH.getStatus()) + .end(QUERY_ENCODED_BOTH.bodyForQuery(ctx.request().query())))); + router + .route(QUERY_ENCODED_QUERY.getPath()) + .handler( + ctx -> + controller( + ctx, + QUERY_ENCODED_QUERY, + () -> + ctx.response() + .setStatusCode(QUERY_ENCODED_QUERY.getStatus()) + .end(QUERY_ENCODED_QUERY.bodyForQuery(ctx.request().query())))); + router + .route(QUERY_PARAM.getPath()) + .handler( + ctx -> + controller( + ctx, + QUERY_PARAM, + () -> + ctx.response() + .setStatusCode(QUERY_PARAM.getStatus()) + .end(ctx.request().query()))); + + router + .route(USER_BLOCK.getPath()) + .handler( + ctx -> + controller( + ctx, + USER_BLOCK, + () -> { + Blocking.forUser("user-to-block").blockIfMatch(); + ctx.response().end("Should not be reached"); + })); + + router + .route("/path/:id/param") + .handler( + ctx -> + controller( + ctx, + PATH_PARAM, + () -> + ctx.response() + .setStatusCode(PATH_PARAM.getStatus()) + .end(ctx.request().getParam("id")))); + router + .route(REDIRECT.getPath()) + .handler( + ctx -> + controller( + ctx, + REDIRECT, + () -> + ctx.response() + .setStatusCode(REDIRECT.getStatus()) + .putHeader("location", REDIRECT.getBody()) + .end())); + router + .route(ERROR.getPath()) + .handler( + ctx -> + controller( + ctx, + ERROR, + () -> ctx.response().setStatusCode(ERROR.getStatus()).end(ERROR.getBody()))); + router + .route(EXCEPTION.getPath()) + .handler(ctx -> controller(ctx, EXCEPTION, VertxTestServer::exception)); + + router + .route(SESSION_ID.getPath()) + .handler(SessionHandler.create(LocalSessionStore.create(vertx))); + router + .route(SESSION_ID.getPath()) + .handler( + ctx -> ctx.response().setStatusCode(SESSION_ID.getStatus()).end(ctx.session().id())); + + router = customizeAfterRoutes(router); + + vertx + .createHttpServer(new HttpServerOptions().setHandle100ContinueAutomatically(true)) + .requestHandler(router) + .listen(port) + .andThen( + event -> { + // send this though event bus and succeed deploy after successful response + int actualPort = event.result().actualPort(); + vertx + .eventBus() + .request(PORT_DATA_ADDRESS, actualPort) + .andThen( + ar -> { + if (ar.succeeded()) { + startPromise.complete(); + } else { + startPromise.fail(ar.cause()); + } + }); + }); + } + + private static String convertFormAttributes(RoutingContext ctx) { + String res = "["; + MultiMap entries = ctx.request().formAttributes(); + for (String name : entries.names()) { + if (name.equals("ignore")) { + continue; + } + if (res.length() > 1) { + res += ", "; + } + res += name; + res += ":["; + int i = 0; + for (String s : entries.getAll(name)) { + if (i++ > 0) { + res += ", "; + } + res += s; + } + res += ']'; + } + res += ']'; + return res; + } + + protected void customizeBeforeRoutes(Router router) {} + + protected Router customizeAfterRoutes(final Router router) { + return router; + } + + private static void exception() { + throw new RuntimeException(EXCEPTION.getBody()); + } + + private static void controller( + RoutingContext ctx, final ServerEndpoint endpoint, final Runnable runnable) { + assert activeSpan() != null : "Controller should have a parent span."; + assert activeScope().isAsyncPropagating() : "Scope should be propagating async."; + ctx.response() + .putHeader( + HttpServerTest.getIG_RESPONSE_HEADER(), HttpServerTest.getIG_RESPONSE_HEADER_VALUE()); + if (endpoint == NOT_FOUND || endpoint == UNKNOWN) { + runnable.run(); + return; + } + runnableUnderTraceAsync("controller", runnable); + } +} diff --git a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy index 0a917bbd469..176fddfa697 100644 --- a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy +++ b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy @@ -354,6 +354,10 @@ abstract class HttpServerTest extends WithHttpServer { false } + boolean testBlockingErrorTypeSet() { + true + } + /** Tomcat 5.5 can't seem to handle the encoded URIs */ boolean testEncodedPath() { true @@ -1708,9 +1712,11 @@ abstract class HttpServerTest extends WithHttpServer { List spans = TEST_WRITER.flatten() spans.find { it.tags['http.status_code'] == 413 } != null spans.find { it.tags['appsec.blocked'] == 'true' } != null - spans.find { - it.error && - it.tags['error.type'] == BlockingException.name } != null + if (testBlockingErrorTypeSet()) { + spans.find { + it.error && + it.tags['error.type'] == BlockingException.name } != null + } and: if (isDataStreamsEnabled()) { diff --git a/settings.gradle b/settings.gradle index ba530788e46..719f7d1efaf 100644 --- a/settings.gradle +++ b/settings.gradle @@ -505,6 +505,7 @@ include ':dd-java-agent:instrumentation:vertx-web-3.4' include ':dd-java-agent:instrumentation:vertx-web-3.5' include ':dd-java-agent:instrumentation:vertx-web-3.9' include ':dd-java-agent:instrumentation:vertx-web-4.0' +include ':dd-java-agent:instrumentation:vertx-web-5.0' include ':dd-java-agent:instrumentation:redisson' include ':dd-java-agent:instrumentation:redisson:redisson-2.0.0' include ':dd-java-agent:instrumentation:redisson:redisson-2.3.0'