Skip to content

Commit eac439f

Browse files
committed
Prevent webhook calling forbidden endpoints
Allows configuring network address rules to permit / deny webhook usage. (cherry picked from commit 14ac072)
1 parent 9ba86d6 commit eac439f

File tree

3 files changed

+80
-6
lines changed

3 files changed

+80
-6
lines changed

wiremock-webhooks-extension/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ dependencies {
3939
testImplementation "org.junit.jupiter:junit-jupiter"
4040
testImplementation "org.hamcrest:hamcrest-core:2.2"
4141
testImplementation "org.hamcrest:hamcrest-library:2.2"
42+
testImplementation 'org.awaitility:awaitility:4.2.0'
4243
}
4344

4445
shadowJar {

wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/Webhooks.java

+37-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2021 Thomas Akehurst
2+
* Copyright (C) 2021-2023 Thomas Akehurst
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import static java.util.stream.Collectors.toList;
2121

2222
import com.fasterxml.jackson.annotation.JsonCreator;
23+
import com.github.tomakehurst.wiremock.common.NetworkAddressRules;
2324
import com.github.tomakehurst.wiremock.common.Notifier;
2425
import com.github.tomakehurst.wiremock.core.Admin;
2526
import com.github.tomakehurst.wiremock.extension.Parameters;
@@ -28,6 +29,9 @@
2829
import com.github.tomakehurst.wiremock.extension.responsetemplating.TemplateEngine;
2930
import com.github.tomakehurst.wiremock.http.HttpHeader;
3031
import com.github.tomakehurst.wiremock.stubbing.ServeEvent;
32+
import java.net.InetAddress;
33+
import java.net.URI;
34+
import java.net.UnknownHostException;
3135
import java.util.*;
3236
import java.util.concurrent.Executors;
3337
import java.util.concurrent.ScheduledExecutorService;
@@ -50,25 +54,37 @@ public class Webhooks extends PostServeAction {
5054
private final CloseableHttpClient httpClient;
5155
private final List<WebhookTransformer> transformers;
5256
private final TemplateEngine templateEngine;
57+
private final NetworkAddressRules targetAddressRules;
5358

5459
private Webhooks(
5560
ScheduledExecutorService scheduler,
5661
CloseableHttpClient httpClient,
57-
List<WebhookTransformer> transformers) {
62+
List<WebhookTransformer> transformers,
63+
NetworkAddressRules targetAddressRules) {
5864
this.scheduler = scheduler;
5965
this.httpClient = httpClient;
6066
this.transformers = transformers;
6167

6268
this.templateEngine = new TemplateEngine(Collections.emptyMap(), null, Collections.emptySet());
69+
this.targetAddressRules = targetAddressRules;
70+
}
71+
72+
private Webhooks(List<WebhookTransformer> transformers, NetworkAddressRules targetAddressRules) {
73+
this(
74+
Executors.newScheduledThreadPool(10), createHttpClient(), transformers, targetAddressRules);
75+
}
76+
77+
public Webhooks(NetworkAddressRules targetAddressRules) {
78+
this(new ArrayList<>(), targetAddressRules);
6379
}
6480

6581
@JsonCreator
6682
public Webhooks() {
67-
this(Executors.newScheduledThreadPool(10), createHttpClient(), new ArrayList<>());
83+
this(NetworkAddressRules.ALLOW_ALL);
6884
}
6985

7086
public Webhooks(WebhookTransformer... transformers) {
71-
this(Executors.newScheduledThreadPool(10), createHttpClient(), Arrays.asList(transformers));
87+
this(Arrays.asList(transformers), NetworkAddressRules.ALLOW_ALL);
7288
}
7389

7490
private static CloseableHttpClient createHttpClient() {
@@ -109,6 +125,10 @@ public void doAction(
109125
definition = transformer.transform(serveEvent, definition);
110126
}
111127
definition = applyTemplating(definition, serveEvent);
128+
if (targetAddressProhibited(definition.getUrl())) {
129+
notifier().error("The target webhook address is denied in WireMock's configuration.");
130+
return;
131+
}
112132
request = buildRequest(definition);
113133
} catch (Exception e) {
114134
notifier().error("Exception thrown while configuring webhook", e);
@@ -195,6 +215,19 @@ private static ClassicHttpRequest buildRequest(WebhookDefinition definition) {
195215
return requestBuilder.build();
196216
}
197217

218+
// TODO this is duplicated in com.github.tomakehurst.wiremock.http.ProxyResponseRenderer - should
219+
// it be on NetworkAddressRules ?
220+
private boolean targetAddressProhibited(String url) {
221+
String host = URI.create(url).getHost();
222+
try {
223+
final InetAddress[] resolvedAddresses = InetAddress.getAllByName(host);
224+
return !Arrays.stream(resolvedAddresses)
225+
.allMatch(address -> targetAddressRules.isAllowed(address.getHostAddress()));
226+
} catch (UnknownHostException e) {
227+
return true;
228+
}
229+
}
230+
198231
public static WebhookDefinition webhook() {
199232
return new WebhookDefinition();
200233
}

wiremock-webhooks-extension/src/test/java/functional/WebhooksAcceptanceTest.java

+42-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2021 Thomas Akehurst
2+
* Copyright (C) 2021-2023 Thomas Akehurst
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,13 +23,15 @@
2323
import static java.util.concurrent.TimeUnit.MILLISECONDS;
2424
import static java.util.concurrent.TimeUnit.SECONDS;
2525
import static org.apache.hc.core5.http.ContentType.TEXT_PLAIN;
26+
import static org.awaitility.Awaitility.await;
2627
import static org.hamcrest.MatcherAssert.assertThat;
2728
import static org.hamcrest.Matchers.*;
2829
import static org.junit.jupiter.api.Assertions.assertTrue;
2930
import static org.wiremock.webhooks.Webhooks.webhook;
3031

3132
import com.github.tomakehurst.wiremock.client.WireMock;
3233
import com.github.tomakehurst.wiremock.common.ConsoleNotifier;
34+
import com.github.tomakehurst.wiremock.common.NetworkAddressRules;
3335
import com.github.tomakehurst.wiremock.core.Admin;
3436
import com.github.tomakehurst.wiremock.extension.PostServeAction;
3537
import com.github.tomakehurst.wiremock.http.RequestMethod;
@@ -84,7 +86,15 @@ public String getName() {
8486
@RegisterExtension
8587
public WireMockExtension rule =
8688
WireMockExtension.newInstance()
87-
.options(options().dynamicPort().notifier(notifier).extensions(new Webhooks()))
89+
.options(
90+
options()
91+
.dynamicPort()
92+
.notifier(notifier)
93+
.extensions(
94+
new Webhooks(
95+
NetworkAddressRules.builder()
96+
.deny("169.254.0.0-169.254.255.255")
97+
.build())))
8898
.configureStaticDsl(true)
8999
.build();
90100

@@ -355,6 +365,36 @@ public void addsRandomDelayViaJSON() throws Exception {
355365
verify(1, getRequestedFor(urlEqualTo("/callback")));
356366
}
357367

368+
@Test
369+
public void doesNotFireAWebhookWhenRequestedForDeniedTarget() throws Exception {
370+
rule.stubFor(
371+
post(urlPathEqualTo("/webhook"))
372+
.willReturn(aResponse().withStatus(200))
373+
.withPostServeAction(
374+
"webhook",
375+
webhook()
376+
.withMethod(POST)
377+
.withUrl("http://169.254.2.34/foo")
378+
.withHeader("Content-Type", "application/json")
379+
.withHeader("X-Multi", "one", "two")
380+
.withBody("{ \"result\": \"SUCCESS\" }")));
381+
382+
client.post("/webhook", new StringEntity("", TEXT_PLAIN));
383+
384+
System.out.println(
385+
"All info notifications:\n"
386+
+ testNotifier.getInfoMessages().stream()
387+
.map(message -> message.replace("\n", "\n>>> "))
388+
.collect(Collectors.joining("\n>>> ")));
389+
390+
await()
391+
.until(
392+
() -> testNotifier.getErrorMessages(),
393+
hasItem(
394+
containsString(
395+
"The target webhook address is denied in WireMock's configuration.")));
396+
}
397+
358398
private void waitForRequestToTargetServer() throws Exception {
359399
assertTrue(
360400
latch.await(20, SECONDS), "Timed out waiting for target server to receive a request");

0 commit comments

Comments
 (0)