Skip to content

Commit 382383c

Browse files
author
Corneil du Plessis
committed
Change from Apache Http Client to Reactor Netty.
Provide followRedirect to remove Authorization. Fixes spring-attic#5989
1 parent 13a46ab commit 382383c

File tree

6 files changed

+164
-273
lines changed

6 files changed

+164
-273
lines changed

spring-cloud-dataflow-configuration-metadata/pom.xml

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
<groupId>org.springframework</groupId>
3636
<artifactId>spring-web</artifactId>
3737
</dependency>
38+
<dependency>
39+
<groupId>io.projectreactor.netty</groupId>
40+
<artifactId>reactor-netty</artifactId>
41+
</dependency>
3842
<dependency>
3943
<groupId>com.fasterxml.jackson.core</groupId>
4044
<artifactId>jackson-core</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2020-2020 the original author or authors.
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+
* https://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+
17+
package org.springframework.cloud.dataflow.container.registry.authorization;
18+
19+
import java.util.Collections;
20+
import java.util.Map;
21+
22+
import org.junit.jupiter.api.AfterEach;
23+
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.api.Disabled;
25+
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.api.extension.ExtensionContext;
27+
import org.junit.jupiter.api.extension.RegisterExtension;
28+
import org.slf4j.Logger;
29+
import org.slf4j.LoggerFactory;
30+
31+
import org.springframework.boot.builder.SpringApplicationBuilder;
32+
import org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient;
33+
import org.springframework.cloud.dataflow.configuration.metadata.ApplicationConfigurationMetadataResolverAutoConfiguration;
34+
import org.springframework.cloud.dataflow.configuration.metadata.container.DefaultContainerImageMetadataResolver;
35+
import org.springframework.cloud.dataflow.container.registry.ContainerRegistryAutoConfiguration;
36+
import org.springframework.cloud.dataflow.container.registry.ContainerRegistryConfiguration;
37+
import org.springframework.cloud.dataflow.container.registry.ContainerRegistryProperties;
38+
import org.springframework.cloud.dataflow.container.registry.authorization.support.S3SignedRedirectRequestServerApplication;
39+
import org.springframework.context.ConfigurableApplicationContext;
40+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
41+
import org.springframework.context.annotation.Bean;
42+
import org.springframework.context.annotation.Import;
43+
import org.springframework.context.annotation.Primary;
44+
import org.springframework.test.util.TestSocketUtils;
45+
46+
import static org.assertj.core.api.Assertions.assertThat;
47+
import static org.assertj.core.api.Assertions.entry;
48+
49+
/**
50+
* @author Adam J. Weigold
51+
* @author Corneil du Plessis
52+
*/
53+
//TODO: Boot3x followup
54+
@Disabled("TODO: Netty HttpClient configuration for both http and https required")
55+
public class DropAuthorizationHeaderOnInsecureS3RequestRedirectStrategyTest {
56+
private final static Logger logger = LoggerFactory.getLogger(DropAuthorizationHeaderOnInsecureS3RequestRedirectStrategyTest.class);
57+
private AnnotationConfigApplicationContext context;
58+
private ConfigurableApplicationContext application;
59+
private static int serverPort;
60+
@BeforeEach
61+
void setup() {
62+
serverPort = TestSocketUtils.findAvailableTcpPort();
63+
64+
logger.info("Setting S3 Signed Redirect Server port to " + serverPort);
65+
66+
this.application = new SpringApplicationBuilder(S3SignedRedirectRequestServerApplication.class).build()
67+
.run("--server.port=" + serverPort);
68+
logger.info("S3 Signed Redirect Server Server is UP! at " + serverPort);
69+
}
70+
@AfterEach
71+
void clean() {
72+
if (context != null) {
73+
context.close();
74+
}
75+
context = null;
76+
}
77+
78+
@Test
79+
void redirect() {
80+
context = new AnnotationConfigApplicationContext(TestApplication.class);
81+
82+
final DefaultContainerImageMetadataResolver imageMetadataResolver =
83+
context.getBean(DefaultContainerImageMetadataResolver.class);
84+
85+
Map<String, String> imageLabels = imageMetadataResolver.getImageLabels("localhost:" +
86+
serverPort + "/test/s3-redirect-image:1.0.0");
87+
88+
assertThat(imageLabels).containsOnly(entry("foo", "bar"));
89+
}
90+
91+
@Import({ContainerRegistryAutoConfiguration.class, ApplicationConfigurationMetadataResolverAutoConfiguration.class})
92+
@AutoConfigureWebClient
93+
static class TestApplication {
94+
@Bean
95+
@Primary
96+
ContainerRegistryProperties containerRegistryProperties() {
97+
ContainerRegistryProperties properties = new ContainerRegistryProperties();
98+
ContainerRegistryConfiguration registryConfiguration = new ContainerRegistryConfiguration();
99+
registryConfiguration.setRegistryHost(String.format("localhost:%s", serverPort));
100+
registryConfiguration.setAuthorizationType(ContainerRegistryConfiguration.AuthorizationType.dockeroauth2);
101+
registryConfiguration.setUser("admin");
102+
registryConfiguration.setSecret("Harbor12345");
103+
registryConfiguration.setDisableSslVerification(true);
104+
registryConfiguration.setExtra(Collections.singletonMap(
105+
DockerOAuth2RegistryAuthorizer.DOCKER_REGISTRY_AUTH_URI_KEY,
106+
"http://localhost:" + serverPort + "/service/token"));
107+
properties.setRegistryConfigurations(Collections.singletonMap("goharbor", registryConfiguration));
108+
109+
return properties;
110+
}
111+
}
112+
}

spring-cloud-dataflow-configuration-metadata/src/test/java/org/springframework/cloud/dataflow/container/registry/authorization/DropAuthorizationHeaderOnSignedS3RequestRedirectStrategyTest.java

-2
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@
4242
* @author Adam J. Weigold
4343
* @author Corneil du Plessis
4444
*/
45-
//TODO: Boot3x followup
46-
@Disabled("TODO: Boot3x `org.springframework.web.client.HttpClientErrorException$BadRequest: 400 : [no body]` is thrown by REST Template")
4745
public class DropAuthorizationHeaderOnSignedS3RequestRedirectStrategyTest {
4846
@RegisterExtension
4947
public final static S3SignedRedirectRequestServerResource s3SignedRedirectRequestServerResource =

spring-cloud-dataflow-container-registry/pom.xml

+4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@
4242
<groupId>org.springframework</groupId>
4343
<artifactId>spring-web</artifactId>
4444
</dependency>
45+
<dependency>
46+
<groupId>io.projectreactor.netty</groupId>
47+
<artifactId>reactor-netty</artifactId>
48+
</dependency>
4549
<dependency>
4650
<groupId>com.fasterxml.jackson.core</groupId>
4751
<artifactId>jackson-core</artifactId>

spring-cloud-dataflow-container-registry/src/main/java/org/springframework/cloud/dataflow/container/registry/ContainerImageRestTemplateFactory.java

+44-71
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,26 @@
1616

1717
package org.springframework.cloud.dataflow.container.registry;
1818

19-
import java.security.KeyManagementException;
20-
import java.security.NoSuchAlgorithmException;
21-
import java.security.cert.X509Certificate;
2219
import java.util.ArrayList;
2320
import java.util.Collections;
2421
import java.util.Map;
2522
import java.util.Objects;
2623
import java.util.concurrent.ConcurrentHashMap;
2724

28-
import javax.net.ssl.SSLContext;
29-
import javax.net.ssl.TrustManager;
30-
import javax.net.ssl.X509TrustManager;
31-
32-
import org.apache.hc.client5.http.config.RequestConfig;
33-
import org.apache.hc.client5.http.cookie.StandardCookieSpec;
34-
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
35-
import org.apache.hc.client5.http.impl.classic.HttpClients;
36-
import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager;
37-
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
38-
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
39-
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
40-
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
41-
import org.apache.hc.core5.http.HttpHost;
42-
import org.apache.hc.core5.http.config.Lookup;
43-
import org.apache.hc.core5.http.config.RegistryBuilder;
25+
import javax.net.ssl.SSLException;
26+
27+
import io.netty.handler.codec.http.HttpHeaders;
28+
import io.netty.handler.ssl.SslContext;
29+
import io.netty.handler.ssl.SslContextBuilder;
30+
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
31+
import reactor.netty.http.Http11SslContextSpec;
32+
import reactor.netty.http.client.HttpClient;
33+
import reactor.netty.transport.ProxyProvider;
4434

4535
import org.springframework.boot.web.client.RestTemplateBuilder;
46-
import org.springframework.cloud.dataflow.container.registry.authorization.DropAuthorizationHeaderRequestRedirectStrategy;
4736
import org.springframework.http.MediaType;
48-
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
37+
import org.springframework.http.client.ClientHttpRequestFactory;
38+
import org.springframework.http.client.ReactorNettyClientRequestFactory;
4939
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
5040
import org.springframework.web.client.RestTemplate;
5141

@@ -153,67 +143,50 @@ public RestTemplate getContainerRestTemplate(boolean skipSslVerification, boolea
153143
}
154144
}
155145

156-
private RestTemplate createContainerRestTemplate(boolean skipSslVerification, boolean withHttpProxy, Map<String, String> extra)
157-
throws NoSuchAlgorithmException, KeyManagementException {
146+
private RestTemplate createContainerRestTemplate(boolean skipSslVerification, boolean withHttpProxy, Map<String, String> extra) {
147+
HttpClient client = httpClientBuilder(skipSslVerification);
148+
return initRestTemplate(client, withHttpProxy, extra);
149+
}
158150

159-
if (!skipSslVerification) {
160-
// Create a RestTemplate that uses custom request factory
161-
return this.initRestTemplate(HttpClients.custom(), withHttpProxy, extra);
151+
private static void removeAuthorization(HttpHeaders headers) {
152+
for(Map.Entry<String,String> entry: headers.entries()) {
153+
if(entry.getKey().equalsIgnoreCase(org.springframework.http.HttpHeaders.AUTHORIZATION)) {
154+
headers.remove(entry.getKey());
155+
break;
156+
}
162157
}
163-
164-
// Trust manager that blindly trusts all SSL certificates.
165-
TrustManager[] trustAllCerts = new TrustManager[] {
166-
new X509TrustManager() {
167-
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
168-
return new X509Certificate[0];
169-
}
170-
171-
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
172-
}
173-
174-
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
175-
}
176-
}
177-
};
178-
SSLContext sslContext = SSLContext.getInstance("SSL");
179-
// Install trust manager to SSL Context.
180-
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
181-
182-
// Create a RestTemplate that uses custom request factory
183-
return initRestTemplate(
184-
httpClientBuilder(sslContext),
185-
withHttpProxy,
186-
extra);
187-
}
188-
private HttpClientBuilder httpClientBuilder(SSLContext sslContext) {
189-
// Register http/s connection factories
190-
Lookup<ConnectionSocketFactory> connSocketFactoryLookup = RegistryBuilder.<ConnectionSocketFactory> create()
191-
.register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE))
192-
.register("http", new PlainConnectionSocketFactory())
193-
.build();
194-
return HttpClients.custom()
195-
.setConnectionManager(new BasicHttpClientConnectionManager(connSocketFactoryLookup));
196158
}
197-
private RestTemplate initRestTemplate(HttpClientBuilder clientBuilder, boolean withHttpProxy, Map<String, String> extra) {
198159

199-
clientBuilder.setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(StandardCookieSpec.RELAXED).build());
160+
private HttpClient httpClientBuilder(boolean skipSslVerification) {
161+
162+
try {
163+
SslContextBuilder builder = skipSslVerification ? SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE) : SslContextBuilder.forClient();
164+
SslContext sslContext = builder.build();
165+
HttpClient client = HttpClient.create().secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
166+
167+
return client.followRedirect(true, (entries, httpClientRequest) -> {
168+
HttpHeaders httpHeaders = httpClientRequest.requestHeaders();
169+
removeAuthorization(httpHeaders);
170+
removeAuthorization(entries);
171+
httpClientRequest.headers(httpHeaders);
172+
});
173+
} catch (SSLException e) {
174+
throw new RuntimeException(e);
175+
}
176+
177+
}
178+
private RestTemplate initRestTemplate(HttpClient client, boolean withHttpProxy, Map<String, String> extra) {
200179

201180
// Set the HTTP proxy if configured.
202181
if (withHttpProxy) {
203182
if (!properties.getHttpProxy().isEnabled()) {
204183
throw new ContainerRegistryException("Registry Configuration uses a HttpProxy but non is configured!");
205184
}
206-
HttpHost proxy = new HttpHost(properties.getHttpProxy().getHost(), properties.getHttpProxy().getPort());
207-
clientBuilder.setProxy(proxy);
185+
ProxyProvider.Builder builder = ProxyProvider.builder().type(ProxyProvider.Proxy.HTTP).host(properties.getHttpProxy().getHost()).port(properties.getHttpProxy().getPort());
186+
client.proxy(typeSpec -> builder.build());
208187
}
209-
210-
HttpComponentsClientHttpRequestFactory customRequestFactory =
211-
new HttpComponentsClientHttpRequestFactory(
212-
clientBuilder
213-
.setRedirectStrategy(new DropAuthorizationHeaderRequestRedirectStrategy(extra))
214-
// Azure redirects may contain double slashes and on default those are normilised
215-
.setDefaultRequestConfig(RequestConfig.custom().build())
216-
.build());
188+
// TODO what do we do with extra?
189+
ClientHttpRequestFactory customRequestFactory = new ReactorNettyClientRequestFactory(client);
217190

218191
// DockerHub response's media-type is application/octet-stream although the content is in JSON.
219192
// Similarly the Github CR response's media-type is always text/plain although the content is in JSON.

0 commit comments

Comments
 (0)