Skip to content

Commit

Permalink
fix fabric8io#5086: adding support for socks proxies
Browse files Browse the repository at this point in the history
  • Loading branch information
shawkins committed May 26, 2023
1 parent 7c8f017 commit cfb20eb
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 79 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
* Fix #5164: [java-generator] handle more special characters in field names

#### Improvements
* Fix #1335: HttpClient Factory additionalConfig consistently applied for all client types

#### Dependency Upgrade
* Fix #4989: Upgrade Tekton Model to v0.47.0
* Fix #5107: The Camel-k extension has been deprecated in favor of the official release of the generated one

#### New Features
* Fix #5086: Allow for supporting socks proxies via proxy urls with the socks4 or socks5 protocol. Note that the JDK client does not support socks, and the Jetty client does not support socks5

#### _**Note**_: Breaking changes
* Fix #4911: Config/RequestConfig.scaleTimeout has been deprecated along with Scalable.scale(count, wait) and DeployableScalableResource.deployLatest(wait). withTimeout may be called before the operation to control the timeout.
Expand All @@ -25,6 +27,7 @@
* Fix #4662: deprecated Helper.getAnnotationValue, use HasMetadata methods instead.
* Fix #5125: support for TLSv1.3 is now enabled by default
* Fix #5125: usage of TlsVersion.TLS_1_1, TLS_1_0, and SSL_3_0 have been deprecated
* Fix #1335: The JDK and OkHttp clients will default to using the VM's standard configuration for proxies if an applicalbe proxy configuration is not found in the Kubernetes client Config

### 6.6.2 (2023-05-15)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package io.fabric8.kubernetes.client.jdkhttp;

import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.http.HttpClient;
import io.fabric8.kubernetes.client.http.StandardHttpClientBuilder;
import io.fabric8.kubernetes.client.http.TlsVersion;
Expand Down Expand Up @@ -59,11 +60,15 @@ public HttpClient build() {
if (followRedirects) {
builder.followRedirects(Redirect.ALWAYS);
}
if (proxyAddress != null) {
if (proxyType == HttpClient.ProxyType.DIRECT) {
builder.proxy(java.net.http.HttpClient.Builder.NO_PROXY);
} else if (proxyAddress != null) {
if (proxyType != HttpClient.ProxyType.HTTP) {
// https://bugs.openjdk.org/browse/JDK-8214516
throw new KubernetesClientException("JDK HttpClient only support HTTP proxies");
}
builder.proxy(ProxySelector.of(proxyAddress));
addProxyAuthInterceptor();
} else {
builder.proxy(java.net.http.HttpClient.Builder.NO_PROXY);
}
if (preferHttp11) {
builder.version(Version.HTTP_1_1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
*/
package io.fabric8.kubernetes.client.jetty;

import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.http.HttpClient.ProxyType;
import io.fabric8.kubernetes.client.http.StandardHttpClientBuilder;
import io.fabric8.kubernetes.client.http.TlsVersion;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpClientTransport;
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.Socks4Proxy;
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
Expand Down Expand Up @@ -77,9 +80,21 @@ public JettyHttpClient build() {
// the work that can be done
sharedHttpClient.setMaxConnectionsPerDestination(MAX_CONNECTIONS);
sharedWebSocketClient.getHttpClient().setMaxConnectionsPerDestination(MAX_CONNECTIONS);
if (proxyAddress != null) {
sharedHttpClient.getProxyConfiguration()
.addProxy(new HttpProxy(new Origin.Address(proxyAddress.getHostString(), proxyAddress.getPort()), false));
if (proxyType != ProxyType.DIRECT && proxyAddress != null) {
Origin.Address address = new Origin.Address(proxyAddress.getHostString(), proxyAddress.getPort());
// Jetty allows for the differentiation of proxy being secure separately from the destination,
// but we'll always set that flag to false
switch (proxyType) {
case HTTP:
sharedHttpClient.getProxyConfiguration().addProxy(new HttpProxy(address, false));
break;
case SOCKS4:
sharedHttpClient.getProxyConfiguration().addProxy(new Socks4Proxy(address, false));
break;
default:
throw new KubernetesClientException("Unsupported proxy type");
}
sharedHttpClient.getProxyConfiguration().addProxy(new HttpProxy(address, false));
addProxyAuthInterceptor();
}
clientFactory.additionalConfig(sharedHttpClient, sharedWebSocketClient);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package io.fabric8.kubernetes.client.okhttp;

import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.http.HttpClient.ProxyType;
import io.fabric8.kubernetes.client.http.StandardHttpClientBuilder;
import io.fabric8.kubernetes.client.http.StandardHttpHeaders;
import okhttp3.Authenticator;
Expand Down Expand Up @@ -68,10 +70,10 @@ public OkHttpClientImpl initialBuild(okhttp3.OkHttpClient.Builder builder) {
if (followRedirects) {
builder.followRedirects(true).followSslRedirects(true);
}
if (proxyAddress == null) {
if (proxyType == ProxyType.DIRECT) {
builder.proxy(Proxy.NO_PROXY);
} else {
builder.proxy(new Proxy(Proxy.Type.HTTP, proxyAddress));
} else if (proxyAddress != null) {
builder.proxy(new Proxy(convertProxyType(), proxyAddress));
if (proxyAuthorization != null) {
builder.proxyAuthenticator(
(route, response) -> response.request().newBuilder()
Expand All @@ -92,6 +94,20 @@ public OkHttpClientImpl initialBuild(okhttp3.OkHttpClient.Builder builder) {
return completeBuild(builder, false);
}

private Proxy.Type convertProxyType() {
switch (proxyType) {
case DIRECT:
return Proxy.Type.DIRECT;
case HTTP:
return Proxy.Type.HTTP;
case SOCKS4:
case SOCKS5:
return Proxy.Type.SOCKS;
default:
throw new KubernetesClientException("Unsupported proxy type");
}
}

private OkHttpClientImpl completeBuild(okhttp3.OkHttpClient.Builder builder, boolean derived) {
if (authenticatorNone) {
builder.authenticator(Authenticator.NONE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.fabric8.kubernetes.client.vertx;

import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.http.HttpClient;
import io.fabric8.kubernetes.client.http.StandardHttpClientBuilder;
import io.fabric8.kubernetes.client.http.TlsVersion;
Expand Down Expand Up @@ -67,11 +68,11 @@ public VertxHttpClient<F> build() {
options.setFollowRedirects(followRedirects);
}

if (this.proxyAddress != null) {
if (this.proxyType != HttpClient.ProxyType.DIRECT && this.proxyAddress != null) {
ProxyOptions proxyOptions = new ProxyOptions()
.setHost(this.proxyAddress.getHostName())
.setPort(this.proxyAddress.getPort())
.setType(ProxyType.HTTP);
.setType(convertProxyType());
options.setProxyOptions(proxyOptions);
addProxyAuthInterceptor();
}
Expand Down Expand Up @@ -124,4 +125,17 @@ protected VertxHttpClientBuilder<F> newInstance(F clientFactory) {
return new VertxHttpClientBuilder<>(clientFactory, vertx);
}

private ProxyType convertProxyType() {
switch (proxyType) {
case HTTP:
return ProxyType.HTTP;
case SOCKS4:
return ProxyType.SOCKS4;
case SOCKS5:
return ProxyType.SOCKS5;
default:
throw new KubernetesClientException("Unsupported proxy type");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@

public interface HttpClient extends AutoCloseable {

enum ProxyType {
HTTP,
SOCKS4,
SOCKS5,
DIRECT
}

interface Factory {

/**
Expand Down Expand Up @@ -114,6 +121,8 @@ interface Builder extends DerivedClientBuilder {
Builder tlsVersions(TlsVersion... tlsVersions);

Builder preferHttp11();

Builder proxyType(ProxyType type);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package io.fabric8.kubernetes.client.http;

import io.fabric8.kubernetes.client.http.HttpClient.DerivedClientBuilder;
import io.fabric8.kubernetes.client.http.HttpClient.ProxyType;
import io.fabric8.kubernetes.client.internal.SSLUtils;
import lombok.Getter;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -49,6 +50,7 @@ public abstract class StandardHttpClientBuilder<C extends HttpClient, F extends
protected TrustManager[] trustManagers;
protected KeyManager[] keyManagers;
protected LinkedHashMap<Class<?>, Object> tags = new LinkedHashMap<>();
protected ProxyType proxyType = ProxyType.HTTP; // for backwards compatibility if the builder is manually configured

protected StandardHttpClientBuilder(F clientFactory) {
this.clientFactory = clientFactory;
Expand Down Expand Up @@ -106,6 +108,12 @@ public T proxyAuthorization(String credentials) {
return (T) this;
}

@Override
public T proxyType(ProxyType type) {
this.proxyType = type;
return (T) this;
}

@Override
public T tlsVersions(TlsVersion... tlsVersions) {
this.tlsVersions = tlsVersions;
Expand Down Expand Up @@ -148,6 +156,7 @@ public T copy(C client) {
copy.authenticatorNone = this.authenticatorNone;
copy.client = client;
copy.tags = new LinkedHashMap<>(this.tags);
copy.proxyType = this.proxyType;
return copy;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
Expand All @@ -35,7 +37,6 @@
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
Expand Down Expand Up @@ -79,18 +80,13 @@ public void before(BasicBuilder builder, HttpRequest request, RequestTags tags)
private HttpClientUtils() {
}

public static URL getProxyUrl(Config config) throws MalformedURLException {
URL master = new URL(config.getMasterUrl());
String host = master.getHost();
if (isHostMatchedByNoProxy(host, config.getNoProxy())) {
return null;
}
static URI getProxyUri(URL master, Config config) throws URISyntaxException {
String proxy = config.getHttpsProxy();
if (master.getProtocol().equals("http")) {
proxy = config.getHttpProxy();
}
if (proxy != null) {
URL proxyUrl = new URL(proxy);
URI proxyUrl = new URI(proxy);
if (proxyUrl.getPort() < 0) {
throw new IllegalArgumentException("Failure in creating proxy URL. Proxy port is required!");
}
Expand Down Expand Up @@ -186,42 +182,68 @@ public static void applyCommonConfiguration(Config config, HttpClient.Builder bu
}

try {

// Only check proxy if it's a full URL with protocol
if (config.getMasterUrl().toLowerCase(Locale.ROOT).startsWith(Config.HTTP_PROTOCOL_PREFIX)
|| config.getMasterUrl().startsWith(Config.HTTPS_PROTOCOL_PREFIX)) {
try {
URL proxyUrl = HttpClientUtils.getProxyUrl(config);
if (proxyUrl != null) {
builder.proxyAddress(new InetSocketAddress(proxyUrl.getHost(), proxyUrl.getPort()));

if (config.getProxyUsername() != null) {
builder.proxyAuthorization(basicCredentials(config.getProxyUsername(), config.getProxyPassword()));
}
} else {
builder.proxyAddress(null);
}
} catch (MalformedURLException e) {
throw new KubernetesClientException("Invalid proxy server configuration", e);
}
}
configureProxy(config, builder);

TrustManager[] trustManagers = SSLUtils.trustManagers(config);
KeyManager[] keyManagers = SSLUtils.keyManagers(config);

builder.sslContext(keyManagers, trustManagers);

if (config.getTlsVersions() != null && config.getTlsVersions().length > 0) {
builder.tlsVersions(config.getTlsVersions());
}

} catch (Exception e) {
throw KubernetesClientException.launderThrowable(e);
}

if (config.getTlsVersions() != null && config.getTlsVersions().length > 0) {
builder.tlsVersions(config.getTlsVersions());
}

HttpClientUtils.createApplicableInterceptors(config, factory).forEach(builder::addOrReplaceInterceptor);
}

private static boolean isHostMatchedByNoProxy(String host, String[] noProxies) throws MalformedURLException {
static void configureProxy(Config config, HttpClient.Builder builder)
throws URISyntaxException, MalformedURLException {
URL master;
try {
master = new URL(config.getMasterUrl());
} catch (MalformedURLException e) {
// Only check proxy if it's a full URL with protocol
return;
}
URI proxyUri = HttpClientUtils.getProxyUri(master, config);
if (proxyUri == null) {
// not configured for a proxy
return;
}
String host = master.getHost();
if (isHostMatchedByNoProxy(host, config.getNoProxy())) {
builder.proxyType(HttpClient.ProxyType.DIRECT);
} else {
builder.proxyAddress(new InetSocketAddress(proxyUri.getHost(), proxyUri.getPort()));

if (config.getProxyUsername() != null) {
builder.proxyAuthorization(basicCredentials(config.getProxyUsername(), config.getProxyPassword()));
}

builder.proxyType(toProxyType(proxyUri.getScheme()));
}
}

static HttpClient.ProxyType toProxyType(String scheme) throws MalformedURLException {
if (scheme == null) {
throw new MalformedURLException("No protocol specified on proxy URL");
}
scheme = scheme.toLowerCase();
if (scheme.startsWith("http")) {
return HttpClient.ProxyType.HTTP;
}
if (scheme.equals("socks4")) {
return HttpClient.ProxyType.SOCKS4;
}
if (scheme.equals("socks5")) {
return HttpClient.ProxyType.SOCKS5;
}
throw new MalformedURLException("Unsupported protocol specified on proxy URL");
}

static boolean isHostMatchedByNoProxy(String host, String[] noProxies) throws MalformedURLException {
for (String noProxy : noProxies == null ? new String[0] : noProxies) {
if (INVALID_HOST_PATTERN.matcher(noProxy).find()) {
throw new MalformedURLException("NO_PROXY URL contains invalid entry: '" + noProxy + "'");
Expand Down
Loading

0 comments on commit cfb20eb

Please sign in to comment.