Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allowing for the usage of authenticated http proxies for https endpoints #6352

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #5632 this is removed since it's not necessary for the Vert.x server.

Is this required? I'm not sure how to port these changes to the other PR once we merge this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this required?

As far as I can tell that is needed to make the https tunnel actually work.

The test doesn't need to fully succeed to establish the tunnel - I've updated it to capture if the authentication header was sent we could just check for that and let the request fail fast.

}

@Override
Expand Down
Loading
Loading