Skip to content

Commit 817c341

Browse files
tooryxcopybara-github
authored andcommitted
Add support for workflows using the callback server in unit tests.
This change requires: - Delaying the initialization of detectors to when the test runs rather than when it is being created; - Change to the test interface: instead of providing the detector tested, each test is now only provided with the name of the detector. It is now the responsibility of each test to initialize the detectors depending on their environment; This change might make unit tests slightly slower, especially as the list of plugin grows, but provide a lot more flexibility for testing hermetically. PiperOrigin-RevId: 725549365 Change-Id: I58f2a6f1f8955615b8ecbd1abe1c97aa1d62499d
1 parent 64c5541 commit 817c341

File tree

3 files changed

+195
-29
lines changed

3 files changed

+195
-29
lines changed

templated/templateddetector/proto/templated_plugin_tests.proto

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,29 @@ message TemplatedPluginTests {
1818
}
1919

2020
message Test {
21+
message MockCallbackConfig {
22+
// Whether the callback server should be mocked.
23+
// Disabled by default.
24+
bool enabled = 1;
25+
26+
// The return value for the callback server. That is, whether the callback
27+
// server reports the secret having been interracted with or not.
28+
// Only used if enabled is true.
29+
bool has_interaction = 2;
30+
}
31+
2132
// The name of the test.
2233
string name = 1;
2334

2435
// Whether this test ensure that the vulnerability is found or not.
2536
bool expect_vulnerability = 2;
2637

38+
// Configuration for the mocking the callback server behavior.
39+
MockCallbackConfig mock_callback_server = 3;
40+
2741
// The action being tested.
2842
oneof anyAction {
29-
HttpTestAction mock_http_server = 3;
43+
HttpTestAction mock_http_server = 4;
3044
}
3145
}
3246

templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetectorDynamicTest.java

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import com.google.tsunami.common.time.testing.FakeUtcClock;
3636
import com.google.tsunami.common.time.testing.FakeUtcClockModule;
3737
import com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule;
38+
import com.google.tsunami.plugin.payload.testing.PayloadTestHelper;
3839
import com.google.tsunami.proto.NetworkService;
3940
import com.google.tsunami.proto.TargetInfo;
4041
import com.google.tsunami.proto.TransportProtocol;
@@ -62,6 +63,7 @@ public final class TemplatedDetectorDynamicTest {
6263

6364
private Environment environment;
6465
private MockWebServer mockWebServer;
66+
private MockWebServer mockCallbackServer;
6567

6668
private static final FakeUtcClock fakeUtcClock =
6769
FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z"));
@@ -74,21 +76,33 @@ public void nextBytes(byte[] bytes) {
7476
};
7577

7678
@Before
77-
public void setupMockWebServer() throws IOException {
79+
public void setupMockServers() throws IOException {
7880
environment = new Environment(false);
7981
mockWebServer = new MockWebServer();
82+
mockCallbackServer = new MockWebServer();
8083
var baseUrl = "http://" + mockWebServer.getHostName() + ":" + mockWebServer.getPort() + "/";
8184
environment.set("T_NS_BASEURL", baseUrl);
8285
}
8386

8487
@After
8588
public void tearDown() throws IOException {
8689
mockWebServer.shutdown();
90+
mockCallbackServer.shutdown();
8791
}
8892

8993
@Test
9094
@TestParameters(valuesProvider = TestProvider.class)
91-
public void runTest(TemplatedDetector detector, TemplatedPluginTests.Test testCase) {
95+
public void runTest(String pluginName, TemplatedPluginTests.Test testCase) {
96+
var detectors = getDetectorsForCase(testCase);
97+
98+
if (!detectors.containsKey(pluginName)) {
99+
throw new IllegalArgumentException(
100+
"Plugin '"
101+
+ pluginName
102+
+ "' not found (ensure the tested_plugin field is set correctly).");
103+
}
104+
105+
var detector = detectors.get(pluginName);
92106
switch (testCase.getAnyActionCase()) {
93107
case MOCK_HTTP_SERVER:
94108
forHttpAction(detector, testCase);
@@ -98,6 +112,38 @@ public void runTest(TemplatedDetector detector, TemplatedPluginTests.Test testCa
98112
}
99113
}
100114

115+
private final ImmutableMap<String, TemplatedDetector> getDetectorsForCase(
116+
TemplatedPluginTests.Test testCase) {
117+
// Inject the adequate callback server configuration.
118+
FakePayloadGeneratorModule.Builder payloadGeneratorModuleBuilder =
119+
FakePayloadGeneratorModule.builder().setSecureRng(testSecureRandom);
120+
121+
if (testCase.getMockCallbackServer().getEnabled()) {
122+
payloadGeneratorModuleBuilder.setCallbackServer(mockCallbackServer);
123+
124+
try {
125+
if (testCase.getMockCallbackServer().getHasInteraction()) {
126+
mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse());
127+
} else {
128+
mockCallbackServer.enqueue(PayloadTestHelper.generateMockUnsuccessfulCallbackResponse());
129+
}
130+
} catch (IOException e) {
131+
throw new RuntimeException(e);
132+
}
133+
}
134+
135+
// Inject dependencies and get the detectors.
136+
var bootstrap = new TemplatedDetectorBootstrapModule();
137+
bootstrap.setForceLoadDetectors(true);
138+
Guice.createInjector(
139+
new FakeUtcClockModule(fakeUtcClock),
140+
new HttpClientModule.Builder().build(),
141+
payloadGeneratorModuleBuilder.build(),
142+
bootstrap);
143+
144+
return bootstrap.getDetectors();
145+
}
146+
101147
private final void forHttpAction(TemplatedDetector detector, TemplatedPluginTests.Test testCase) {
102148
ImmutableList<NetworkService> httpServices =
103149
ImmutableList.of(
@@ -199,34 +245,21 @@ private final MockResponse createResponse(HttpTestAction.MockResponse testRespon
199245
static final class TestProvider extends TestParametersValuesProvider {
200246
@Override
201247
public ImmutableList<TestParametersValues> provideValues(Context context) {
202-
var detectors = getDetectors();
203248
return getResourceNames().stream()
204249
.map(TestProvider::loadPlugin)
205250
.filter(plugin -> plugin != null)
206-
.flatMap(plugin -> parametersForPlugin(plugin, detectors).stream())
251+
.flatMap(plugin -> parametersForPlugin(plugin).stream())
207252
.collect(toImmutableList());
208253
}
209254

210-
private static final ImmutableMap<String, TemplatedDetector> getDetectors() {
211-
var bootstrap = new TemplatedDetectorBootstrapModule();
212-
bootstrap.setForceLoadDetectors(true);
213-
Guice.createInjector(
214-
new FakeUtcClockModule(fakeUtcClock),
215-
new HttpClientModule.Builder().build(),
216-
FakePayloadGeneratorModule.builder().setSecureRng(testSecureRandom).build(),
217-
bootstrap);
218-
return bootstrap.getDetectors();
219-
}
220-
221-
private static ImmutableList<TestParametersValues> generateCommonTests(
222-
TemplatedDetector detector) {
255+
private static ImmutableList<TestParametersValues> generateCommonTests(String pluginName) {
223256
// Echo server test: plugins should never return a vulnerability when the response just
224257
// contains the request.
225-
var testName = detector.getName() + ", autogenerated_whenEchoServer_returnsFalse";
258+
var testName = pluginName + ", autogenerated_whenEchoServer_returnsFalse";
226259
return ImmutableList.of(
227260
TestParametersValues.builder()
228261
.name(testName)
229-
.addParameter("detector", detector)
262+
.addParameter("pluginName", pluginName)
230263
.addParameter(
231264
"testCase",
232265
TemplatedPluginTests.Test.newBuilder()
@@ -242,31 +275,25 @@ private static ImmutableList<TestParametersValues> generateCommonTests(
242275
}
243276

244277
private static ImmutableList<TestParametersValues> parametersForPlugin(
245-
TemplatedPluginTests pluginTests, ImmutableMap<String, TemplatedDetector> detectors) {
278+
TemplatedPluginTests pluginTests) {
246279
var pluginName = pluginTests.getConfig().getTestedPlugin();
247280

248281
if (pluginTests.getConfig().getDisabled()) {
249282
logger.atWarning().log("Plugin '%s' tests are disabled.", pluginName);
250283
return ImmutableList.of();
251284
}
252285

253-
if (!detectors.containsKey(pluginName)) {
254-
logger.atWarning().log("Plugin '%s' not found or disabled. Skipping test.", pluginName);
255-
return ImmutableList.of();
256-
}
257-
258-
var detector = detectors.get(pluginName);
259286
var testsBuilder = ImmutableList.<TestParametersValues>builder();
260287
// Inject tests that are common to all plugins.
261-
testsBuilder.addAll(generateCommonTests(detector));
288+
testsBuilder.addAll(generateCommonTests(pluginName));
262289

263290
// Tests defined in the plugin test file.
264291
pluginTests.getTestsList().stream()
265292
.map(
266293
t ->
267294
TestParametersValues.builder()
268295
.name(pluginName + ", " + t.getName())
269-
.addParameter("detector", detector)
296+
.addParameter("pluginName", pluginName)
270297
.addParameter("testCase", t)
271298
.build())
272299
.forEach(testsBuilder::add);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package com.google.tsunami.plugins.detectors.templateddetector.actions;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
5+
import com.google.inject.Guice;
6+
import com.google.tsunami.common.net.http.HttpClientModule;
7+
import com.google.tsunami.plugin.TcsClient;
8+
import com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule;
9+
import com.google.tsunami.plugin.payload.testing.PayloadTestHelper;
10+
import com.google.tsunami.plugins.detectors.templateddetector.Environment;
11+
import com.google.tsunami.proto.NetworkService;
12+
import com.google.tsunami.templatedplugin.proto.CallbackServerAction;
13+
import com.google.tsunami.templatedplugin.proto.PluginAction;
14+
import java.io.IOException;
15+
import java.security.SecureRandom;
16+
import java.util.Arrays;
17+
import javax.inject.Inject;
18+
import okhttp3.mockwebserver.MockWebServer;
19+
import org.junit.After;
20+
import org.junit.Before;
21+
import org.junit.Test;
22+
import org.junit.runner.RunWith;
23+
import org.junit.runners.JUnit4;
24+
25+
@RunWith(JUnit4.class)
26+
public final class CallbackServerActionRunnerTest {
27+
private CallbackServerActionRunner runner;
28+
private Environment environment;
29+
private MockWebServer mockCallbackServer;
30+
private NetworkService service;
31+
32+
@Inject private TcsClient tcsClient;
33+
34+
private static final SecureRandom testSecureRandom =
35+
new SecureRandom() {
36+
@Override
37+
public void nextBytes(byte[] bytes) {
38+
Arrays.fill(bytes, (byte) 0xFF);
39+
}
40+
};
41+
42+
@Before
43+
public void setup() {
44+
this.environment = new Environment(false);
45+
this.environment.set("T_CBS_SECRET", "irrelevant");
46+
this.service = NetworkService.getDefaultInstance();
47+
}
48+
49+
@Before
50+
public void setupMockHttp() {
51+
this.mockCallbackServer = new MockWebServer();
52+
}
53+
54+
@After
55+
public void tearMockHttp() throws IOException {
56+
this.mockCallbackServer.shutdown();
57+
}
58+
59+
@Test
60+
public void checkAction_whenCallbackServerDisabled_returnsFalse() throws IOException {
61+
PluginAction action =
62+
PluginAction.newBuilder()
63+
.setName("action")
64+
.setCallbackServer(
65+
CallbackServerAction.newBuilder()
66+
.setActionType(CallbackServerAction.ActionType.CHECK))
67+
.build();
68+
69+
setupCallbackServer(false, false);
70+
71+
assertThat(runner.run(this.service, action, this.environment)).isFalse();
72+
}
73+
74+
@Test
75+
public void checkAction_whenCallbackServerReturnsFalse_returnsFalse() throws IOException {
76+
PluginAction action =
77+
PluginAction.newBuilder()
78+
.setName("action")
79+
.setCallbackServer(
80+
CallbackServerAction.newBuilder()
81+
.setActionType(CallbackServerAction.ActionType.CHECK))
82+
.build();
83+
84+
setupCallbackServer(true, false);
85+
86+
assertThat(runner.run(this.service, action, this.environment)).isFalse();
87+
assertThat(this.mockCallbackServer.getRequestCount()).isEqualTo(1);
88+
}
89+
90+
@Test
91+
public void checkAction_whenCallbackServerReturnsTrue_returnsTrue() throws IOException {
92+
PluginAction action =
93+
PluginAction.newBuilder()
94+
.setName("action")
95+
.setCallbackServer(
96+
CallbackServerAction.newBuilder()
97+
.setActionType(CallbackServerAction.ActionType.CHECK))
98+
.build();
99+
100+
setupCallbackServer(true, true);
101+
102+
assertThat(runner.run(this.service, action, this.environment)).isTrue();
103+
assertThat(this.mockCallbackServer.getRequestCount()).isEqualTo(1);
104+
}
105+
106+
private final void setupCallbackServer(boolean enabled, boolean response) throws IOException {
107+
FakePayloadGeneratorModule.Builder payloadGeneratorModuleBuilder =
108+
FakePayloadGeneratorModule.builder().setSecureRng(testSecureRandom);
109+
110+
if (enabled) {
111+
payloadGeneratorModuleBuilder.setCallbackServer(mockCallbackServer);
112+
113+
if (response) {
114+
mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse());
115+
} else {
116+
mockCallbackServer.enqueue(PayloadTestHelper.generateMockUnsuccessfulCallbackResponse());
117+
}
118+
}
119+
120+
Guice.createInjector(
121+
new HttpClientModule.Builder().build(), payloadGeneratorModuleBuilder.build())
122+
.injectMembers(this);
123+
this.runner = new CallbackServerActionRunner(tcsClient, false);
124+
}
125+
}

0 commit comments

Comments
 (0)