Skip to content

Commit

Permalink
Moved enforcement of network address rules to Apache client DNS resol…
Browse files Browse the repository at this point in the history
…ver to avoid race condition where rules can be bypassed via successive lookups returning different IP addresses
  • Loading branch information
tomakehurst authored and Mahoney committed Sep 5, 2023
1 parent eac439f commit d9fd0b4
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (C) 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.
* You may obtain a copy of the License at
*
* http:https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.tomakehurst.wiremock.common;

public class ProhibitedNetworkAddressException extends RuntimeException {}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2021 Thomas Akehurst
* Copyright (C) 2011-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,6 +23,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.commons.lang3.StringUtils.isEmpty;

import com.github.tomakehurst.wiremock.common.NetworkAddressRules;
import com.github.tomakehurst.wiremock.common.ProxySettings;
import com.github.tomakehurst.wiremock.common.ssl.KeyStoreSettings;
import com.github.tomakehurst.wiremock.http.ssl.*;
Expand Down Expand Up @@ -64,7 +65,11 @@ public static CloseableHttpClient createClient(
KeyStoreSettings trustStoreSettings,
boolean trustSelfSignedCertificates,
final List<String> trustedHosts,
boolean useSystemProperties) {
boolean useSystemProperties,
NetworkAddressRules networkAddressRules) {

NetworkAddressRulesAdheringDnsResolver dnsResolver =
new NetworkAddressRulesAdheringDnsResolver(networkAddressRules);

HttpClientBuilder builder =
HttpClientBuilder.create()
Expand All @@ -75,6 +80,7 @@ public static CloseableHttpClient createClient(
.disableContentCompression()
.setConnectionManager(
PoolingHttpClientConnectionManagerBuilder.create()
.setDnsResolver(dnsResolver)
.setMaxConnPerRoute(maxConnections)
.setMaxConnTotal(maxConnections)
.setValidateAfterInactivity(TimeValue.ofSeconds(5)) // TODO Verify duration
Expand Down Expand Up @@ -113,6 +119,7 @@ public static CloseableHttpClient createClient(
PoolingHttpClientConnectionManager connectionManager =
PoolingHttpClientConnectionManagerBuilder.create()
.setSSLSocketFactory(sslSocketFactory)
.setDnsResolver(dnsResolver)
.build();
builder.setConnectionManager(connectionManager);

Expand Down Expand Up @@ -168,15 +175,17 @@ public static CloseableHttpClient createClient(
int timeoutMilliseconds,
ProxySettings proxySettings,
KeyStoreSettings trustStoreSettings,
boolean useSystemProperties) {
boolean useSystemProperties,
NetworkAddressRules networkAddressRules) {
return createClient(
maxConnections,
timeoutMilliseconds,
proxySettings,
trustStoreSettings,
true,
Collections.<String>emptyList(),
useSystemProperties);
useSystemProperties,
networkAddressRules);
}

private static SSLContext buildSSLContextWithTrustStore(
Expand Down Expand Up @@ -226,15 +235,27 @@ private static SSLContext buildAllowAnythingSSLContext() {
}

public static CloseableHttpClient createClient(int maxConnections, int timeoutMilliseconds) {
return createClient(maxConnections, timeoutMilliseconds, NO_PROXY, NO_STORE, true);
return createClient(
maxConnections,
timeoutMilliseconds,
NO_PROXY,
NO_STORE,
true,
NetworkAddressRules.ALLOW_ALL);
}

public static CloseableHttpClient createClient(int timeoutMilliseconds) {
return createClient(DEFAULT_MAX_CONNECTIONS, timeoutMilliseconds);
}

public static CloseableHttpClient createClient(ProxySettings proxySettings) {
return createClient(DEFAULT_MAX_CONNECTIONS, DEFAULT_TIMEOUT, proxySettings, NO_STORE, true);
return createClient(
DEFAULT_MAX_CONNECTIONS,
DEFAULT_TIMEOUT,
proxySettings,
NO_STORE,
true,
NetworkAddressRules.ALLOW_ALL);
}

public static CloseableHttpClient createClient() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (C) 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.
* You may obtain a copy of the License at
*
* http:https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.tomakehurst.wiremock.http;

import com.github.tomakehurst.wiremock.common.NetworkAddressRules;
import com.github.tomakehurst.wiremock.common.ProhibitedNetworkAddressException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.stream.Stream;
import org.apache.hc.client5.http.SystemDefaultDnsResolver;

public class NetworkAddressRulesAdheringDnsResolver extends SystemDefaultDnsResolver {

private final NetworkAddressRules networkAddressRules;

public NetworkAddressRulesAdheringDnsResolver(NetworkAddressRules networkAddressRules) {
this.networkAddressRules = networkAddressRules;
}

@Override
public InetAddress[] resolve(String host) throws UnknownHostException {
if (!networkAddressRules.isAllowed(host)) {
throw new ProhibitedNetworkAddressException();
}

final InetAddress[] resolved = super.resolve(host);
if (Stream.of(resolved)
.anyMatch(address -> !networkAddressRules.isAllowed(address.getHostAddress()))) {
throw new ProhibitedNetworkAddressException();
}

return resolved;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,16 @@
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;

import com.github.tomakehurst.wiremock.common.NetworkAddressRules;
import com.github.tomakehurst.wiremock.common.ProhibitedNetworkAddressException;
import com.github.tomakehurst.wiremock.common.ProxySettings;
import com.github.tomakehurst.wiremock.common.ssl.KeyStoreSettings;
import com.github.tomakehurst.wiremock.global.GlobalSettingsHolder;
import com.github.tomakehurst.wiremock.stubbing.ServeEvent;
import com.google.common.collect.ImmutableList;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
Expand Down Expand Up @@ -85,8 +83,9 @@ public ProxyResponseRenderer(
proxySettings,
trustStoreSettings,
true,
Collections.<String>emptyList(),
true);
Collections.emptyList(),
true,
targetAddressRules);
forwardProxyClient =
HttpClientFactory.createClient(
1000,
Expand All @@ -95,7 +94,8 @@ public ProxyResponseRenderer(
trustStoreSettings,
trustAllProxyTargets,
trustAllProxyTargets ? Collections.emptyList() : trustedProxyTargets,
false);
false,
targetAddressRules);

this.preserveHostHeader = preserveHostHeader;
this.hostHeaderValue = hostHeaderValue;
Expand All @@ -106,13 +106,13 @@ public ProxyResponseRenderer(
@Override
public Response render(ServeEvent serveEvent) {
ResponseDefinition responseDefinition = serveEvent.getResponseDefinition();
if (targetAddressProhibited(responseDefinition.getProxyUrl())) {
return response()
.status(500)
.headers(new HttpHeaders(new HttpHeader("Content-Type", "text/plain")))
.body("The target proxy address is denied in WireMock's configuration.")
.build();
}
// if (targetAddressProhibited(responseDefinition.getProxyUrl())) {
// return response()
// .status(500)
// .headers(new HttpHeaders(new HttpHeader("Content-Type", "text/plain")))
// .body("The target proxy address is denied in WireMock's configuration.")
// .build();
// }

HttpUriRequest httpRequest = getHttpRequestFor(responseDefinition);
addRequestHeaders(httpRequest, responseDefinition);
Expand All @@ -136,24 +136,19 @@ public Response render(ServeEvent serveEvent) {
responseDefinition.getDelayDistribution())
.chunkedDribbleDelay(responseDefinition.getChunkedDribbleDelay())
.build();
} catch (ProhibitedNetworkAddressException e) {
return response()
.status(HTTP_INTERNAL_ERROR)
.headers(new HttpHeaders(new HttpHeader("Content-Type", "text/plain")))
.body("The target proxy address is denied in WireMock's configuration.")
.build();
} catch (SSLException e) {
return proxyResponseError("SSL", httpRequest, e);
} catch (IOException e) {
return proxyResponseError("Network", httpRequest, e);
}
}

private boolean targetAddressProhibited(String proxyUrl) {
String host = URI.create(proxyUrl).getHost();
try {
final InetAddress[] resolvedAddresses = InetAddress.getAllByName(host);
return !Arrays.stream(resolvedAddresses)
.allMatch(address -> targetAddressRules.isAllowed(address.getHostAddress()));
} catch (UnknownHostException e) {
return true;
}
}

private Response proxyResponseError(String type, HttpUriRequest request, Exception e) {
return response()
.status(HTTP_INTERNAL_ERROR)
Expand All @@ -169,7 +164,7 @@ private Response proxyResponseError(String type, HttpUriRequest request, Excepti
private static String extractUri(HttpUriRequest request) {
try {
return request.getUri().toString();
} catch (URISyntaxException e1) {
} catch (URISyntaxException ignored) {
}
return request.getRequestUri();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2021 Thomas Akehurst
* Copyright (C) 2020-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 @@ -22,6 +22,7 @@
import static java.util.Collections.emptyList;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.common.NetworkAddressRules;
import com.github.tomakehurst.wiremock.common.ssl.KeyStoreSettings;
import com.github.tomakehurst.wiremock.crypto.CertificateSpecification;
import com.github.tomakehurst.wiremock.crypto.InMemoryKeyStore;
Expand Down Expand Up @@ -53,11 +54,11 @@ public void startServerAndBuildClient(

CertificateSpecification certificateSpecification =
new X509CertificateSpecification(
/* version = */ V3,
/* subject = */ "CN=" + certificateCN,
/* issuer = */ "CN=wiremock.org",
/* notBefore = */ new Date(),
/* notAfter = */ new Date(System.currentTimeMillis() + (365L * 24 * 60 * 60 * 1000)));
/* version= */ V3,
/* subject= */ "CN=" + certificateCN,
/* issuer= */ "CN=wiremock.org",
/* notBefore= */ new Date(),
/* notAfter= */ new Date(System.currentTimeMillis() + (365L * 24 * 60 * 60 * 1000)));

Certificate certificate = certificateSpecification.certificateFor(keyPair);

Expand Down Expand Up @@ -90,9 +91,10 @@ public void startServerAndBuildClient(
5 * 1000 * 60,
NO_PROXY,
clientTrustStoreSettings,
/* trustSelfSignedCertificates = */ false,
/* trustSelfSignedCertificates= */ false,
trustedHosts,
false);
false,
NetworkAddressRules.ALLOW_ALL);
}

@AfterEach
Expand Down

0 comments on commit d9fd0b4

Please sign in to comment.