Skip to content

Commit 5d0a056

Browse files
author
Tom Akehurst
committed
Closes #2007 - added support for preventing proxying to certain addresses via allow/deny lists
1 parent 1b94a94 commit 5d0a056

File tree

13 files changed

+487
-25
lines changed

13 files changed

+487
-25
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright (C) 2022 Thomas Akehurst
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.github.tomakehurst.wiremock.common;
17+
18+
import com.google.common.net.InetAddresses;
19+
import java.math.BigInteger;
20+
import java.net.InetAddress;
21+
import java.net.UnknownHostException;
22+
import java.util.regex.Pattern;
23+
24+
public abstract class NetworkAddressRange {
25+
26+
public static final NetworkAddressRange ALL = new All();
27+
28+
private static final Pattern SINGLE_IP =
29+
Pattern.compile("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
30+
private static final Pattern IP_RANGE =
31+
Pattern.compile(
32+
"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}-\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
33+
34+
public static NetworkAddressRange of(String value) {
35+
if (SINGLE_IP.matcher(value).matches()) {
36+
return new SingleIp(value);
37+
}
38+
39+
if (IP_RANGE.matcher(value).matches()) {
40+
return new IpRange(value);
41+
}
42+
43+
return new DomainNameWildcard(value);
44+
}
45+
46+
public abstract boolean isIncluded(String testValue);
47+
48+
private static class SingleIp extends NetworkAddressRange {
49+
50+
private final InetAddress inetAddress;
51+
52+
private SingleIp(String ipAddress) {
53+
this.inetAddress = parseIpAddress(ipAddress);
54+
}
55+
56+
@Override
57+
public boolean isIncluded(String testValue) {
58+
return lookup(testValue).equals(inetAddress);
59+
}
60+
}
61+
62+
private static class IpRange extends NetworkAddressRange {
63+
64+
private final BigInteger start;
65+
private final BigInteger end;
66+
67+
private IpRange(String ipRange) {
68+
String[] parts = ipRange.split("-");
69+
if (parts.length != 2) {
70+
throw new InvalidInputException(Errors.single(18, ipRange + " is not a valid IP range"));
71+
}
72+
this.start = InetAddresses.toBigInteger(parseIpAddress(parts[0]));
73+
this.end = InetAddresses.toBigInteger(parseIpAddress(parts[1]));
74+
}
75+
76+
@Override
77+
public boolean isIncluded(String testValue) {
78+
InetAddress testValueAddress = lookup(testValue);
79+
BigInteger intVal = InetAddresses.toBigInteger(testValueAddress);
80+
return intVal.compareTo(start) >= 0 && intVal.compareTo(end) <= 0;
81+
}
82+
}
83+
84+
private static class DomainNameWildcard extends NetworkAddressRange {
85+
86+
private final Pattern namePattern;
87+
88+
private DomainNameWildcard(String namePattern) {
89+
String nameRegex = namePattern.replace(".", "\\.").replace("*", ".+");
90+
this.namePattern = Pattern.compile(nameRegex);
91+
}
92+
93+
@Override
94+
public boolean isIncluded(String testValue) {
95+
return namePattern.matcher(testValue).matches();
96+
}
97+
}
98+
99+
private static class All extends NetworkAddressRange {
100+
101+
@Override
102+
public boolean isIncluded(String testValue) {
103+
return true;
104+
}
105+
}
106+
107+
private static InetAddress parseIpAddress(String ipAddress) {
108+
if (!InetAddresses.isInetAddress(ipAddress)) {
109+
throw new InvalidInputException(Errors.single(16, ipAddress + " is not a valid IP address"));
110+
}
111+
112+
return lookup(ipAddress);
113+
}
114+
115+
private static InetAddress lookup(String host) {
116+
try {
117+
return InetAddress.getByName(host);
118+
} catch (UnknownHostException e) {
119+
throw new InvalidInputException(
120+
e, Errors.single(17, host + " is not a valid network address"));
121+
}
122+
}
123+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (C) 2022 Thomas Akehurst
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.github.tomakehurst.wiremock.common;
17+
18+
import static com.github.tomakehurst.wiremock.common.NetworkAddressRange.ALL;
19+
import static java.util.Collections.emptySet;
20+
21+
import com.google.common.collect.ImmutableSet;
22+
import java.util.Set;
23+
24+
public class NetworkAddressRules {
25+
26+
public static Builder builder() {
27+
return new Builder();
28+
}
29+
30+
private final Set<NetworkAddressRange> allowed;
31+
private final Set<NetworkAddressRange> denied;
32+
33+
public static NetworkAddressRules ALLOW_ALL =
34+
new NetworkAddressRules(ImmutableSet.of(ALL), emptySet());
35+
36+
public NetworkAddressRules(Set<NetworkAddressRange> allowed, Set<NetworkAddressRange> denied) {
37+
this.allowed = allowed;
38+
this.denied = denied;
39+
}
40+
41+
public boolean isAllowed(String testValue) {
42+
return allowed.stream().anyMatch(rule -> rule.isIncluded(testValue))
43+
&& denied.stream().noneMatch(rule -> rule.isIncluded(testValue));
44+
}
45+
46+
public static class Builder {
47+
private final ImmutableSet.Builder<NetworkAddressRange> allowed = ImmutableSet.builder();
48+
private final ImmutableSet.Builder<NetworkAddressRange> denied = ImmutableSet.builder();
49+
50+
public Builder allow(String expression) {
51+
allowed.add(NetworkAddressRange.of(expression));
52+
return this;
53+
}
54+
55+
public Builder deny(String expression) {
56+
denied.add(NetworkAddressRange.of(expression));
57+
return this;
58+
}
59+
60+
public NetworkAddressRules build() {
61+
return new NetworkAddressRules(allowed.build(), denied.build());
62+
}
63+
}
64+
}

src/main/java/com/github/tomakehurst/wiremock/core/Options.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,6 @@ enum ChunkedEncodingPolicy {
111111
boolean getDisableStrictHttpHeaders();
112112

113113
DataTruncationSettings getDataTruncationSettings();
114+
115+
NetworkAddressRules getProxyTargetRules();
114116
}

src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ public StubRequestHandler buildStubRequestHandler() {
177177
globalSettingsHolder,
178178
browserProxySettings.trustAllProxyTargets(),
179179
browserProxySettings.trustedProxyTargets(),
180-
options.getStubCorsEnabled()),
180+
options.getStubCorsEnabled(),
181+
options.getProxyTargetRules()),
181182
ImmutableList.copyOf(options.extensionsOfType(ResponseTransformer.class).values())),
182183
this,
183184
postServeActions,

src/main/java/com/github/tomakehurst/wiremock/core/WireMockConfiguration.java

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ public class WireMockConfiguration implements Options {
121121

122122
private Limit responseBodySizeLimit = UNLIMITED;
123123

124+
private NetworkAddressRules proxyTargetRules = NetworkAddressRules.ALLOW_ALL;
125+
124126
private MappingsSource getMappingsSource() {
125127
if (mappingsSource == null) {
126128
mappingsSource = new JsonFileMappingsSource(filesRoot.child(MAPPINGS_ROOT));
@@ -444,6 +446,22 @@ public WireMockConfiguration trustedProxyTargets(List<String> trustedProxyTarget
444446
return this;
445447
}
446448

449+
public WireMockConfiguration disableOptimizeXmlFactoriesLoading(
450+
boolean disableOptimizeXmlFactoriesLoading) {
451+
this.disableOptimizeXmlFactoriesLoading = disableOptimizeXmlFactoriesLoading;
452+
return this;
453+
}
454+
455+
public WireMockConfiguration maxLoggedResponseSize(int maxSize) {
456+
this.responseBodySizeLimit = new Limit(maxSize);
457+
return this;
458+
}
459+
460+
public WireMockConfiguration limitProxyTargets(NetworkAddressRules proxyTargetRules) {
461+
this.proxyTargetRules = proxyTargetRules;
462+
return this;
463+
}
464+
447465
@Override
448466
public int portNumber() {
449467
return portNumber;
@@ -619,12 +637,6 @@ public boolean getDisableOptimizeXmlFactoriesLoading() {
619637
return disableOptimizeXmlFactoriesLoading;
620638
}
621639

622-
public WireMockConfiguration disableOptimizeXmlFactoriesLoading(
623-
boolean disableOptimizeXmlFactoriesLoading) {
624-
this.disableOptimizeXmlFactoriesLoading = disableOptimizeXmlFactoriesLoading;
625-
return this;
626-
}
627-
628640
@Override
629641
public boolean getDisableStrictHttpHeaders() {
630642
return disableStrictHttpHeaders;
@@ -657,8 +669,8 @@ public BrowserProxySettings browserProxySettings() {
657669
.build();
658670
}
659671

660-
public WireMockConfiguration maxLoggedResponseSize(int maxSize) {
661-
this.responseBodySizeLimit = new Limit(maxSize);
662-
return this;
672+
@Override
673+
public NetworkAddressRules getProxyTargetRules() {
674+
return proxyTargetRules;
663675
}
664676
}

src/main/java/com/github/tomakehurst/wiremock/http/ProxyResponseRenderer.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,19 @@
1919
import static com.github.tomakehurst.wiremock.http.Response.response;
2020
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
2121

22+
import com.github.tomakehurst.wiremock.common.NetworkAddressRules;
2223
import com.github.tomakehurst.wiremock.common.ProxySettings;
2324
import com.github.tomakehurst.wiremock.common.ssl.KeyStoreSettings;
2425
import com.github.tomakehurst.wiremock.global.GlobalSettingsHolder;
2526
import com.github.tomakehurst.wiremock.stubbing.ServeEvent;
2627
import com.google.common.collect.ImmutableList;
2728
import java.io.ByteArrayInputStream;
2829
import java.io.IOException;
30+
import java.net.InetAddress;
2931
import java.net.URI;
3032
import java.net.URISyntaxException;
33+
import java.net.UnknownHostException;
34+
import java.util.Arrays;
3135
import java.util.Collections;
3236
import java.util.LinkedList;
3337
import java.util.List;
@@ -61,6 +65,8 @@ public class ProxyResponseRenderer implements ResponseRenderer {
6165
private final GlobalSettingsHolder globalSettingsHolder;
6266
private final boolean stubCorsEnabled;
6367

68+
private final NetworkAddressRules targetAddressRules;
69+
6470
public ProxyResponseRenderer(
6571
ProxySettings proxySettings,
6672
KeyStoreSettings trustStoreSettings,
@@ -69,7 +75,8 @@ public ProxyResponseRenderer(
6975
GlobalSettingsHolder globalSettingsHolder,
7076
boolean trustAllProxyTargets,
7177
List<String> trustedProxyTargets,
72-
boolean stubCorsEnabled) {
78+
boolean stubCorsEnabled,
79+
NetworkAddressRules targetAddressRules) {
7380
this.globalSettingsHolder = globalSettingsHolder;
7481
reverseProxyClient =
7582
HttpClientFactory.createClient(
@@ -93,11 +100,20 @@ public ProxyResponseRenderer(
93100
this.preserveHostHeader = preserveHostHeader;
94101
this.hostHeaderValue = hostHeaderValue;
95102
this.stubCorsEnabled = stubCorsEnabled;
103+
this.targetAddressRules = targetAddressRules;
96104
}
97105

98106
@Override
99107
public Response render(ServeEvent serveEvent) {
100108
ResponseDefinition responseDefinition = serveEvent.getResponseDefinition();
109+
if (targetAddressProhibited(responseDefinition.getProxyUrl())) {
110+
return response()
111+
.status(500)
112+
.headers(new HttpHeaders(new HttpHeader("Content-Type", "text/plain")))
113+
.body("The target proxy address is denied in WireMock's configuration.")
114+
.build();
115+
}
116+
101117
HttpUriRequest httpRequest = getHttpRequestFor(responseDefinition);
102118
addRequestHeaders(httpRequest, responseDefinition);
103119

@@ -127,6 +143,17 @@ public Response render(ServeEvent serveEvent) {
127143
}
128144
}
129145

146+
private boolean targetAddressProhibited(String proxyUrl) {
147+
String host = URI.create(proxyUrl).getHost();
148+
try {
149+
final InetAddress[] resolvedAddresses = InetAddress.getAllByName(host);
150+
return !Arrays.stream(resolvedAddresses)
151+
.allMatch(address -> targetAddressRules.isAllowed(address.getHostAddress()));
152+
} catch (UnknownHostException e) {
153+
return true;
154+
}
155+
}
156+
130157
private Response proxyResponseError(String type, HttpUriRequest request, Exception e) {
131158
return response()
132159
.status(HTTP_INTERNAL_ERROR)

src/main/java/com/github/tomakehurst/wiremock/servlet/WarConfiguration.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ public DataTruncationSettings getDataTruncationSettings() {
219219
return DataTruncationSettings.DEFAULTS;
220220
}
221221

222+
@Override
223+
public NetworkAddressRules getProxyTargetRules() {
224+
return NetworkAddressRules.ALLOW_ALL;
225+
}
226+
222227
@Override
223228
public BrowserProxySettings browserProxySettings() {
224229
return BrowserProxySettings.DISABLED;

0 commit comments

Comments
 (0)