Skip to content

Commit 2fd53d8

Browse files
Added AppSec smoke test to validate WAF
1 parent b8c2efc commit 2fd53d8

13 files changed

Lines changed: 213 additions & 5 deletions

File tree

dd-java-agent/appsec/src/main/java/com/datadog/appsec/report/ReportServiceImpl.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.datadog.appsec.report.raw.dtos.intake.IntakeBatch;
44
import com.datadog.appsec.report.raw.events.AppSecEvent100;
55
import com.datadog.appsec.util.StandardizedLogging;
6+
import datadog.trace.api.Config;
67
import datadog.trace.util.AgentTaskScheduler;
78
import java.util.ArrayList;
89
import java.util.List;
@@ -88,9 +89,12 @@ private void lazyStartTask() {
8889
return;
8990
}
9091

92+
int initialDelay = Config.get().getAppSecReportMinTimeout();
93+
int period = Config.get().getAppSecReportMaxTimeout();
94+
9195
scheduledTask =
9296
taskScheduler.scheduleAtFixedRate(
93-
PeriodicFlush.INSTANCE, this, 5 /* initial delay */, 30 /* period */, TimeUnit.SECONDS);
97+
PeriodicFlush.INSTANCE, this, initialDelay, period, TimeUnit.SECONDS);
9498
}
9599

96100
@Override

dd-java-agent/appsec/src/main/java/com/datadog/appsec/report/ReportStrategy.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import com.datadog.appsec.report.raw.events.AppSecEvent100;
44
import com.datadog.appsec.util.JvmTime;
5+
import datadog.trace.api.Config;
6+
import java.util.concurrent.TimeUnit;
57
import javax.annotation.Nonnull;
68

79
public interface ReportStrategy {
@@ -10,8 +12,10 @@ public interface ReportStrategy {
1012
boolean shouldFlush(@Nonnull AppSecEvent100 event);
1113

1214
class Default implements ReportStrategy {
13-
private static final long MIN_INTERVAL_NANOS = 5L * 1000L * 1000L * 1000L;
14-
private static final long MAX_INTERVAL_NANOS = 60L * 1000L * 1000L * 1000L;
15+
private static final long MIN_INTERVAL_NANOS =
16+
TimeUnit.SECONDS.toNanos(Config.get().getAppSecReportMinTimeout());
17+
private static final long MAX_INTERVAL_NANOS =
18+
TimeUnit.SECONDS.toNanos(Config.get().getAppSecReportMaxTimeout());
1519
private static final int MAX_QUEUED_ITEMS = 50;
1620

1721
private final JvmTime jvmTime;

dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/report/ReportServiceImplTests.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class ReportServiceImplTests extends DDSpecification {
4747
testee.reportEvent(event)
4848

4949
then:
50-
1 * scheduler.scheduleAtFixedRate(_, testee, 5, 30, TimeUnit.SECONDS) >>
50+
1 * scheduler.scheduleAtFixedRate(_, testee, 5, 60, TimeUnit.SECONDS) >>
5151
new AgentTaskScheduler.Scheduled(new Object())
5252
1 * api.sendIntakeBatch(
5353
_ as IntakeBatch,
@@ -89,7 +89,7 @@ class ReportServiceImplTests extends DDSpecification {
8989
testee.reportEvent(event)
9090

9191
then:
92-
1 * scheduler.scheduleAtFixedRate(_, { it.is(testee) }, 5, 30, TimeUnit.SECONDS) >>
92+
1 * scheduler.scheduleAtFixedRate(_, { it.is(testee) }, 5, 60, TimeUnit.SECONDS) >>
9393
{ task = it[0]; scheduled }
9494
0 * api._(*_)
9595

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
apply from: "$rootDir/gradle/java.gradle"
2+
3+
description = 'appsec-smoke-tests'
4+
5+
dependencies {
6+
api project(':dd-smoke-tests')
7+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
plugins {
2+
id "com.github.johnrengelman.shadow"
3+
}
4+
5+
ext {
6+
minJavaVersionForTests = JavaVersion.VERSION_1_8
7+
}
8+
9+
apply from: "$rootDir/gradle/java.gradle"
10+
description = 'SpringBoot Smoke Tests.'
11+
12+
// The standard spring-boot plugin doesn't play nice with our project
13+
// so we'll build a fat jar instead
14+
jar {
15+
manifest {
16+
attributes('Main-Class': 'datadog.smoketest.appsec.springboot.SpringbootApplication')
17+
}
18+
}
19+
20+
dependencies {
21+
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.6.0'
22+
23+
testImplementation project(':dd-smoke-tests:appsec')
24+
}
25+
26+
tasks.withType(Test).configureEach {
27+
dependsOn "shadowJar"
28+
29+
jvmArgs "-Ddatadog.smoketest.appsec.springboot.shadowJar.path=${tasks.shadowJar.archivePath}"
30+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package datadog.smoketest.appsec.springboot;
2+
3+
import java.lang.management.ManagementFactory;
4+
import org.springframework.boot.SpringApplication;
5+
import org.springframework.boot.autoconfigure.SpringBootApplication;
6+
7+
@SpringBootApplication
8+
public class SpringbootApplication {
9+
10+
public static void main(final String[] args) {
11+
SpringApplication.run(SpringbootApplication.class, args);
12+
System.out.println("Started in " + ManagementFactory.getRuntimeMXBean().getUptime() + "ms");
13+
}
14+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package datadog.smoketest.appsec.springboot.controller;
2+
3+
import org.springframework.web.bind.annotation.RequestMapping;
4+
import org.springframework.web.bind.annotation.RestController;
5+
6+
@RestController
7+
public class WebController {
8+
@RequestMapping("/greeting")
9+
public String greeting() {
10+
return "Sup AppSec Dawg";
11+
}
12+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
logging.level.root=WARN
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package datadog.smoketest.appsec
2+
3+
import okhttp3.Request
4+
import spock.util.concurrent.PollingConditions
5+
6+
import java.util.concurrent.TimeUnit
7+
8+
class SpringBootSmokeTest extends AbstractAppSecServerSmokeTest {
9+
10+
// Timeout for individual requests
11+
public static final int REQUEST_TIMEOUT = 5
12+
13+
@Override
14+
ProcessBuilder createProcessBuilder() {
15+
String springBootShadowJar = System.getProperty("datadog.smoketest.appsec.springboot.shadowJar.path")
16+
17+
List<String> command = new ArrayList<>()
18+
command.add(javaPath())
19+
command.addAll(defaultJavaProperties)
20+
command.addAll(defaultAppSecProperties)
21+
command.addAll((String[]) ["-jar", springBootShadowJar, "--server.port=${httpPort}"])
22+
23+
ProcessBuilder processBuilder = new ProcessBuilder(command)
24+
processBuilder.directory(new File(buildDirectory))
25+
}
26+
27+
def "malicious WAF request #n th time"() {
28+
setup:
29+
String url = "http://localhost:${httpPort}/greeting"
30+
def request = new Request.Builder()
31+
.url(url)
32+
. addHeader("User-Agent", "Arachni/v1")
33+
.build()
34+
35+
when:
36+
def response = client.newCall(request).execute()
37+
def conditions = new PollingConditions()
38+
39+
then:
40+
def responseBodyStr = response.body().string()
41+
responseBodyStr != null
42+
responseBodyStr.contains("Sup AppSec Dawg")
43+
response.body().contentType().toString().contains("text/plain")
44+
response.code() == 200
45+
46+
conditions.eventually {
47+
// Expect reported WAF attack event
48+
appSecEvents.poll(REQUEST_TIMEOUT, TimeUnit.SECONDS)?.get("event_type") == "security_scanner"
49+
}
50+
51+
where:
52+
n << (1..200)
53+
}
54+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package datadog.smoketest.appsec
2+
3+
import datadog.smoketest.AbstractServerSmokeTest
4+
import datadog.trace.agent.test.server.http.TestHttpServer
5+
import groovy.json.JsonSlurper
6+
import spock.lang.AutoCleanup
7+
import spock.lang.Shared
8+
9+
import java.util.concurrent.BlockingQueue
10+
import java.util.concurrent.LinkedBlockingQueue
11+
12+
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
13+
14+
abstract class AbstractAppSecServerSmokeTest extends AbstractServerSmokeTest {
15+
16+
@Shared
17+
protected BlockingQueue<Map<String, Object>> appSecEvents = new LinkedBlockingQueue<>()
18+
19+
@AutoCleanup
20+
@Shared
21+
protected TestHttpServer appSecServer = httpServer {
22+
handlers {
23+
prefix("/appsec/proxy/api/v2/appsecevts") {
24+
JsonSlurper jsonParser = new JsonSlurper()
25+
Map<String, Object> attack = jsonParser.parse(request.body) as Map
26+
27+
if (attack.containsKey("events")) {
28+
appSecEvents.addAll(attack.get("events"))
29+
}
30+
31+
response.status(200).send("hello")
32+
}
33+
}
34+
}
35+
36+
@Shared
37+
protected String[] defaultAppSecProperties = [
38+
"-Ddd.appsec.enabled=true",
39+
"-Ddd.appsec.report.timeout=0",
40+
// immediately commit event
41+
"-Ddd.profiling.enabled=false",
42+
"-Ddd.trace.agent.port=${appSecServer.address.port}"
43+
]
44+
45+
def setup() {
46+
appSecEvents.clear()
47+
}
48+
49+
def setupSpec() {
50+
startServer()
51+
}
52+
53+
def cleanupSpec() {
54+
stopServer()
55+
}
56+
57+
def startServer() {
58+
appSecServer.start()
59+
}
60+
61+
def stopServer() {
62+
// do nothing; 'server' is autocleanup
63+
}
64+
}

0 commit comments

Comments
 (0)