Skip to content

Commit

Permalink
fix: allowing for the usage of authenticated http proxies for https
Browse files Browse the repository at this point in the history
closes: fabric8io#6350

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
  • Loading branch information
shawkins committed Sep 19, 2024
1 parent 8d8cd46 commit ee40cf0
Show file tree
Hide file tree
Showing 23 changed files with 370 additions and 74 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* Fix #6247: Support for proxy authentication from proxy URL user info
* Fix #6281: use GitHub binary repo for Kube API Tests
* Fix #6282: Allow annotated types with Pattern, Min, and Max with Lists and Maps and CRD generation
* Fix #6350: Allowing authenticated http proxy usage with Jetty, vertx, and JDK for https endpoints
* Fix #5480: Move `io.fabric8:zjsonpatch` to KubernetesClient project

#### Dependency Upgrade
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,20 @@
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.Socks4Proxy;
import org.eclipse.jetty.client.Socks5Proxy;
import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.client.util.BasicAuthentication;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.client.http.ClientConnectionFactoryOverHTTP2;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.client.WebSocketClient;

import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.Optional;
import java.util.stream.Stream;
Expand Down Expand Up @@ -91,11 +96,20 @@ public JettyHttpClient build() {
case SOCKS4:
sharedHttpClient.getProxyConfiguration().addProxy(new Socks4Proxy(address, false));
break;
case SOCKS5:
sharedHttpClient.getProxyConfiguration().addProxy(new Socks5Proxy(address, false));
break;
default:
throw new KubernetesClientException("Unsupported proxy type");
}
sharedHttpClient.getProxyConfiguration().addProxy(new HttpProxy(address, false));
addProxyAuthInterceptor();
URI proxyUri;
try {
proxyUri = new URI("http://" + proxyAddress.getHostString() + ":" + proxyAddress.getPort());
} catch (URISyntaxException e) {
throw KubernetesClientException.launderThrowable(e);
}
sharedHttpClient.getAuthenticationStore()
.addAuthentication(new BasicAuthentication(proxyUri, Authentication.ANY_REALM, proxyUsername, proxyPassword));
}
clientFactory.additionalConfig(sharedHttpClient, sharedWebSocketClient);
return new JettyHttpClient(this, sharedHttpClient, sharedWebSocketClient);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* 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://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 io.fabric8.kubernetes.client.jetty;

import io.fabric8.kubernetes.client.http.AbstractHttpClientProxyHttpsTest;
import io.fabric8.kubernetes.client.http.HttpClient;
import okhttp3.mockwebserver.SocketPolicy;

@SuppressWarnings("java:S2187")
public class JettyHttpClientProxyHttpsTest extends AbstractHttpClientProxyHttpsTest {
@Override
protected HttpClient.Factory getHttpClientFactory() {
server.setDefaultSocketPolicy(SocketPolicy.KEEP_OPEN); // we need a challenge before switching to ssl
return new JettyHttpClientFactory();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.fabric8.kubernetes.client.http.HttpClient.ProxyType;
import io.fabric8.kubernetes.client.http.StandardHttpClientBuilder;
import io.fabric8.kubernetes.client.http.StandardHttpHeaders;
import io.fabric8.kubernetes.client.utils.HttpClientUtils;
import okhttp3.Authenticator;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
Expand Down Expand Up @@ -74,10 +75,11 @@ public OkHttpClientImpl initialBuild(okhttp3.OkHttpClient.Builder builder) {
builder.proxy(Proxy.NO_PROXY);
} else if (proxyAddress != null) {
builder.proxy(new Proxy(convertProxyType(), proxyAddress));
if (proxyAuthorization != null) {
if (proxyUsername != null && proxyPassword != null) {
String auth = HttpClientUtils.basicCredentials(proxyUsername, proxyPassword);
builder.proxyAuthenticator(
(route, response) -> response.request().newBuilder()
.header(StandardHttpHeaders.PROXY_AUTHORIZATION, proxyAuthorization).build());
.header(StandardHttpHeaders.PROXY_AUTHORIZATION, auth).build());
}
}
if (tlsVersions != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* 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://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 io.fabric8.kubernetes.client.okhttp;

import io.fabric8.kubernetes.client.http.AbstractHttpClientProxyHttpsTest;
import io.fabric8.kubernetes.client.http.HttpClient;
import okhttp3.OkHttpClient.Builder;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;

@SuppressWarnings("java:S2187")
public class OkHttpClientProxyHttpsTest extends AbstractHttpClientProxyHttpsTest {
@Override
protected HttpClient.Factory getHttpClientFactory() {
return new OkHttpClientFactory() {
@Override
protected Builder newOkHttpClientBuilder() {
Builder builder = super.newOkHttpClientBuilder();
builder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
return builder;
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,4 @@ protected HttpClient.Factory getHttpClientFactory() {
return new OkHttpClientFactory();
}

@Override
protected void proxyConfigurationAddsRequiredHeaders() {
// NO-OP
// OkHttp uses a response intercept to add the auth proxy headers in case the original response failed
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ public VertxHttpClient<F> build() {
ProxyOptions proxyOptions = new ProxyOptions()
.setHost(this.proxyAddress.getHostName())
.setPort(this.proxyAddress.getPort())
.setUsername(this.proxyUsername)
.setPassword(this.proxyPassword)
.setType(convertProxyType());
options.setProxyOptions(proxyOptions);
addProxyAuthInterceptor();
}

final String[] protocols;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* 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://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 io.fabric8.kubernetes.client.vertx;

import io.fabric8.kubernetes.client.http.AbstractHttpClientProxyHttpsTest;
import io.fabric8.kubernetes.client.http.HttpClient;

@SuppressWarnings("java:S2187")
public class VertxHttpClientProxyHttpsTest extends AbstractHttpClientProxyHttpsTest {
@Override
protected HttpClient.Factory getHttpClientFactory() {
return new VertxHttpClientFactory();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import okhttp3.mockwebserver.SocketPolicy;

import java.io.IOException;
import java.net.InetAddress;
Expand All @@ -38,13 +39,15 @@ public class DefaultMockServer implements MockServer {

private final Context context;
private final boolean useHttps;
private boolean tunnelProxy;
private final MockWebServer server;
private final Map<ServerRequest, Queue<ServerResponse>> responses;
private final AtomicInteger lastRequestCount;
private final AtomicReference<RecordedRequest> lastRequest;

private final AtomicBoolean initialized = new AtomicBoolean();
private final AtomicBoolean shutdown = new AtomicBoolean();
private Dispatcher dispatcher;

public DefaultMockServer() {
this(new Context(), new MockWebServer(), new HashMap<>(), false);
Expand Down Expand Up @@ -72,17 +75,34 @@ public DefaultMockServer(Context context, MockWebServer server, Map<ServerReques
this.lastRequest = new AtomicReference<>();
this.lastRequestCount = new AtomicInteger(0);
this.server.setDispatcher(dispatcher);
this.dispatcher = dispatcher;
}

public void setHttpsTunnelProxy(boolean tunnelProxy) {
this.tunnelProxy = tunnelProxy;
}

private void startInternal() {
if (initialized.compareAndSet(false, true)) {
if (useHttps) {
server.useHttps(MockSSLContextFactory.create().getSocketFactory(), false);
server.useHttps(MockSSLContextFactory.create().getSocketFactory(), tunnelProxy);
if (tunnelProxy) {
setDefaultSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END);
}
}
onStart();
}
}

/**
* Only works when using the default {@link MockDispatcher} type
*
* @param socketPolicy to be returned when peeking at the dispatcher
*/
public void setDefaultSocketPolicy(SocketPolicy socketPolicy) {
((MockDispatcher) dispatcher).setDefaultSocketPolicy(socketPolicy);
}

private void shutdownInternal() {
if (shutdown.compareAndSet(false, true)) {
onShutdown();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ public interface HttpMethodable<T> {

T patch();

T connect();
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@

public interface Replyable<T> {

T andReply(int statusCode, BodyProvider<Object> contentSupplier);
T andReply(int statusCode, BodyProvider<?> contentSupplier);

T andReply(ResponseProvider<Object> contentSupplier);
T andReply(ResponseProvider<?> contentSupplier);

T andReplyChunked(int statusCode, BodyProvider<List<Object>> content);
T andReplyChunked(int statusCode, BodyProvider<List<?>> content);

T andReplyChunked(ResponseProvider<List<Object>> content);
T andReplyChunked(ResponseProvider<List<?>> content);

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,19 @@ public class MockDispatcher extends Dispatcher {

private final Map<ServerRequest, Queue<ServerResponse>> responses;
private final Collection<WebSocketSession> webSocketSessions = new ConcurrentLinkedQueue<>();
private SocketPolicy defaultSocketPolicy = SocketPolicy.EXPECT_CONTINUE;

public MockDispatcher(Map<ServerRequest, Queue<ServerResponse>> responses) {
this.responses = responses;
}

public void setDefaultSocketPolicy(SocketPolicy defaultSocketPolicy) {
this.defaultSocketPolicy = defaultSocketPolicy;
}

@Override
public MockResponse peek() {
return new MockResponse().setSocketPolicy(SocketPolicy.EXPECT_CONTINUE);
return new MockResponse().setSocketPolicy(defaultSocketPolicy);
}

@Override
Expand Down
Loading

0 comments on commit ee40cf0

Please sign in to comment.