Skip to content

Commit

Permalink
Prevent webhook calling forbidden endpoints
Browse files Browse the repository at this point in the history
Allows configuring network address rules to permit / deny webhook usage.

(cherry picked from commit 14ac072)
  • Loading branch information
Mahoney committed Sep 5, 2023
1 parent 9ba86d6 commit eac439f
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 6 deletions.
1 change: 1 addition & 0 deletions wiremock-webhooks-extension/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dependencies {
testImplementation "org.junit.jupiter:junit-jupiter"
testImplementation "org.hamcrest:hamcrest-core:2.2"
testImplementation "org.hamcrest:hamcrest-library:2.2"
testImplementation 'org.awaitility:awaitility:4.2.0'
}

shadowJar {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Thomas Akehurst
* Copyright (C) 2021-2023 Thomas Akehurst
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,6 +20,7 @@
import static java.util.stream.Collectors.toList;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.github.tomakehurst.wiremock.common.NetworkAddressRules;
import com.github.tomakehurst.wiremock.common.Notifier;
import com.github.tomakehurst.wiremock.core.Admin;
import com.github.tomakehurst.wiremock.extension.Parameters;
Expand All @@ -28,6 +29,9 @@
import com.github.tomakehurst.wiremock.extension.responsetemplating.TemplateEngine;
import com.github.tomakehurst.wiremock.http.HttpHeader;
import com.github.tomakehurst.wiremock.stubbing.ServeEvent;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
Expand All @@ -50,25 +54,37 @@ public class Webhooks extends PostServeAction {
private final CloseableHttpClient httpClient;
private final List<WebhookTransformer> transformers;
private final TemplateEngine templateEngine;
private final NetworkAddressRules targetAddressRules;

private Webhooks(
ScheduledExecutorService scheduler,
CloseableHttpClient httpClient,
List<WebhookTransformer> transformers) {
List<WebhookTransformer> transformers,
NetworkAddressRules targetAddressRules) {
this.scheduler = scheduler;
this.httpClient = httpClient;
this.transformers = transformers;

this.templateEngine = new TemplateEngine(Collections.emptyMap(), null, Collections.emptySet());
this.targetAddressRules = targetAddressRules;
}

private Webhooks(List<WebhookTransformer> transformers, NetworkAddressRules targetAddressRules) {
this(
Executors.newScheduledThreadPool(10), createHttpClient(), transformers, targetAddressRules);
}

public Webhooks(NetworkAddressRules targetAddressRules) {
this(new ArrayList<>(), targetAddressRules);
}

@JsonCreator
public Webhooks() {
this(Executors.newScheduledThreadPool(10), createHttpClient(), new ArrayList<>());
this(NetworkAddressRules.ALLOW_ALL);
}

public Webhooks(WebhookTransformer... transformers) {
this(Executors.newScheduledThreadPool(10), createHttpClient(), Arrays.asList(transformers));
this(Arrays.asList(transformers), NetworkAddressRules.ALLOW_ALL);
}

private static CloseableHttpClient createHttpClient() {
Expand Down Expand Up @@ -109,6 +125,10 @@ public void doAction(
definition = transformer.transform(serveEvent, definition);
}
definition = applyTemplating(definition, serveEvent);
if (targetAddressProhibited(definition.getUrl())) {
notifier().error("The target webhook address is denied in WireMock's configuration.");
return;
}
request = buildRequest(definition);
} catch (Exception e) {
notifier().error("Exception thrown while configuring webhook", e);
Expand Down Expand Up @@ -195,6 +215,19 @@ private static ClassicHttpRequest buildRequest(WebhookDefinition definition) {
return requestBuilder.build();
}

// TODO this is duplicated in com.github.tomakehurst.wiremock.http.ProxyResponseRenderer - should
// it be on NetworkAddressRules ?
private boolean targetAddressProhibited(String url) {
String host = URI.create(url).getHost();
try {
final InetAddress[] resolvedAddresses = InetAddress.getAllByName(host);
return !Arrays.stream(resolvedAddresses)
.allMatch(address -> targetAddressRules.isAllowed(address.getHostAddress()));
} catch (UnknownHostException e) {
return true;
}
}

public static WebhookDefinition webhook() {
return new WebhookDefinition();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Thomas Akehurst
* Copyright (C) 2021-2023 Thomas Akehurst
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -23,13 +23,15 @@
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.hc.core5.http.ContentType.TEXT_PLAIN;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.wiremock.webhooks.Webhooks.webhook;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.common.ConsoleNotifier;
import com.github.tomakehurst.wiremock.common.NetworkAddressRules;
import com.github.tomakehurst.wiremock.core.Admin;
import com.github.tomakehurst.wiremock.extension.PostServeAction;
import com.github.tomakehurst.wiremock.http.RequestMethod;
Expand Down Expand Up @@ -84,7 +86,15 @@ public String getName() {
@RegisterExtension
public WireMockExtension rule =
WireMockExtension.newInstance()
.options(options().dynamicPort().notifier(notifier).extensions(new Webhooks()))
.options(
options()
.dynamicPort()
.notifier(notifier)
.extensions(
new Webhooks(
NetworkAddressRules.builder()
.deny("169.254.0.0-169.254.255.255")
.build())))
.configureStaticDsl(true)
.build();

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

@Test
public void doesNotFireAWebhookWhenRequestedForDeniedTarget() throws Exception {
rule.stubFor(
post(urlPathEqualTo("/webhook"))
.willReturn(aResponse().withStatus(200))
.withPostServeAction(
"webhook",
webhook()
.withMethod(POST)
.withUrl("http://169.254.2.34/foo")
.withHeader("Content-Type", "application/json")
.withHeader("X-Multi", "one", "two")
.withBody("{ \"result\": \"SUCCESS\" }")));

client.post("/webhook", new StringEntity("", TEXT_PLAIN));

System.out.println(
"All info notifications:\n"
+ testNotifier.getInfoMessages().stream()
.map(message -> message.replace("\n", "\n>>> "))
.collect(Collectors.joining("\n>>> ")));

await()
.until(
() -> testNotifier.getErrorMessages(),
hasItem(
containsString(
"The target webhook address is denied in WireMock's configuration.")));
}

private void waitForRequestToTargetServer() throws Exception {
assertTrue(
latch.await(20, SECONDS), "Timed out waiting for target server to receive a request");
Expand Down

0 comments on commit eac439f

Please sign in to comment.