Skip to content

Commit a07fb46

Browse files
newtorka-dMatKuhr
authored
Make TLS/SSL version upgrade handling OPTIN for OnPremise (#631)
Co-authored-by: Alexander Dümont <alexander_duemont@web.de> Co-authored-by: Matthias Kuhr <52661546+MatKuhr@users.noreply.github.com>
1 parent 557431d commit a07fb46

File tree

6 files changed

+156
-19
lines changed

6 files changed

+156
-19
lines changed

cloudplatform/connectivity-apache-httpclient5/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/ApacheHttpClient5FactoryBuilder.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
import org.apache.hc.client5.http.classic.HttpClient;
1212

13+
import com.google.common.annotations.Beta;
14+
1315
/**
1416
* Builder class for a default implementation of the {@link ApacheHttpClient5Factory} interface.
1517
*
@@ -19,9 +21,32 @@ public class ApacheHttpClient5FactoryBuilder
1921
{
2022
@Nonnull
2123
private Duration timeout = DefaultApacheHttpClient5Factory.DEFAULT_TIMEOUT;
24+
private TlsUpgrade tlsUpgrade = TlsUpgrade.AUTOMATIC;
2225
private int maxConnectionsTotal = DefaultApacheHttpClient5Factory.DEFAULT_MAX_CONNECTIONS_TOTAL;
2326
private int maxConnectionsPerRoute = DefaultApacheHttpClient5Factory.DEFAULT_MAX_CONNECTIONS_PER_ROUTE;
2427

28+
/**
29+
* Enum to control the automatic TLS upgrade feature for insecure connections.
30+
*
31+
* @since 5.14.0
32+
*/
33+
@Beta
34+
public enum TlsUpgrade
35+
{
36+
/**
37+
* Automatic TLS upgrade is enabled.
38+
*/
39+
ENABLED,
40+
/**
41+
* Automatic TLS upgrade is disabled.
42+
*/
43+
DISABLED,
44+
/**
45+
* Automatic TLS upgrade is enabled only for {@link ProxyType#INTERNET}, default.
46+
*/
47+
AUTOMATIC;
48+
}
49+
2550
/**
2651
* Sets the timeout {@link HttpClient} instances created by the to-be-built {@link ApacheHttpClient5Factory} should
2752
* use. This timeout applies to the following concerns:
@@ -89,6 +114,20 @@ public ApacheHttpClient5FactoryBuilder maxConnectionsTotal( final int maxConnect
89114
return this;
90115
}
91116

117+
/**
118+
* Sets the automatic TLS upgrade strategy. This strategy controls whether insecure connections should be
119+
* automatically upgraded.
120+
*
121+
* @since 5.14.0
122+
*/
123+
@Beta
124+
@Nonnull
125+
public ApacheHttpClient5FactoryBuilder tlsUpgrade( @Nonnull final TlsUpgrade tlsUpgrade )
126+
{
127+
this.tlsUpgrade = tlsUpgrade;
128+
return this;
129+
}
130+
92131
/**
93132
* Sets the maximum number of parallel connections <b>per route</b> (e.g. per remote host) that can be established
94133
* with a {@link HttpClient} created by the to-be-built {@link ApacheHttpClient5Factory}.
@@ -116,6 +155,11 @@ public ApacheHttpClient5FactoryBuilder maxConnectionsPerRoute( final int maxConn
116155
@Nonnull
117156
public ApacheHttpClient5Factory build()
118157
{
119-
return new DefaultApacheHttpClient5Factory(timeout, maxConnectionsTotal, maxConnectionsPerRoute, null);
158+
return new DefaultApacheHttpClient5Factory(
159+
timeout,
160+
maxConnectionsTotal,
161+
maxConnectionsPerRoute,
162+
null,
163+
tlsUpgrade);
120164
}
121165
}

cloudplatform/connectivity-apache-httpclient5/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/DefaultApacheHttpClient5Factory.java

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,28 +50,25 @@ class DefaultApacheHttpClient5Factory implements ApacheHttpClient5Factory
5050
private final Timeout timeout;
5151
private final int maxConnectionsTotal;
5252
private final int maxConnectionsPerRoute;
53-
// for testing purposes
53+
5454
@Nullable
5555
private final HttpRequestInterceptor requestInterceptor;
5656

57-
DefaultApacheHttpClient5Factory(
58-
@Nonnull final Duration timeout,
59-
final int maxConnectionsTotal,
60-
final int maxConnectionsPerRoute )
61-
{
62-
this(timeout, maxConnectionsTotal, maxConnectionsPerRoute, null);
63-
}
57+
@Nonnull
58+
private final ApacheHttpClient5FactoryBuilder.TlsUpgrade tlsUpgrade;
6459

6560
DefaultApacheHttpClient5Factory(
6661
@Nonnull final Duration timeout,
6762
final int maxConnectionsTotal,
6863
final int maxConnectionsPerRoute,
69-
@Nullable final HttpRequestInterceptor requestInterceptor )
64+
@Nullable final HttpRequestInterceptor requestInterceptor,
65+
@Nonnull final ApacheHttpClient5FactoryBuilder.TlsUpgrade tlsUpgrade )
7066
{
7167
this.timeout = toTimeout(timeout);
7268
this.maxConnectionsTotal = maxConnectionsTotal;
7369
this.maxConnectionsPerRoute = maxConnectionsPerRoute;
7470
this.requestInterceptor = requestInterceptor;
71+
this.tlsUpgrade = tlsUpgrade;
7572
}
7673

7774
@Nonnull
@@ -95,7 +92,7 @@ private CloseableHttpClient buildHttpClient( @Nullable final HttpDestinationProp
9592
HttpClients
9693
.custom()
9794
.setConnectionManager(getConnectionManager(destination))
98-
.setDefaultRequestConfig(getRequestConfig())
95+
.setDefaultRequestConfig(getRequestConfig(destination))
9996
.setProxy(getProxy(destination));
10097

10198
if( requestInterceptor != null ) {
@@ -162,9 +159,30 @@ private HostnameVerifier getHostnameVerifier( final HttpDestinationProperties de
162159
}
163160

164161
@Nonnull
165-
private RequestConfig getRequestConfig()
162+
private RequestConfig getRequestConfig( @Nullable final HttpDestinationProperties destination )
163+
{
164+
return RequestConfig
165+
.custom()
166+
.setProtocolUpgradeEnabled(isProtocolUpgradeEnabled(destination))
167+
.setConnectionRequestTimeout(timeout)
168+
.build();
169+
}
170+
171+
private boolean isProtocolUpgradeEnabled( @Nullable final HttpDestinationProperties destination )
166172
{
167-
return RequestConfig.custom().setConnectionRequestTimeout(timeout).build();
173+
return switch( tlsUpgrade ) {
174+
case ENABLED -> true;
175+
case DISABLED -> false;
176+
case AUTOMATIC -> {
177+
if( destination == null ) {
178+
yield true;
179+
}
180+
if( destination.getTlsVersion().isDefined() ) {
181+
yield false;
182+
}
183+
yield !destination.getProxyType().contains(ProxyType.ON_PREMISE);
184+
}
185+
};
168186
}
169187

170188
@Nullable

cloudplatform/connectivity-apache-httpclient5/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/ApacheHttpClient5FactoryBuilderTest.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,19 @@
44

55
package com.sap.cloud.sdk.cloudplatform.connectivity;
66

7+
import static com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5FactoryBuilder.TlsUpgrade.DISABLED;
8+
import static com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5FactoryBuilder.TlsUpgrade.ENABLED;
9+
import static com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination.builder;
10+
import static com.sap.cloud.sdk.cloudplatform.connectivity.ProxyType.INTERNET;
11+
import static com.sap.cloud.sdk.cloudplatform.connectivity.ProxyType.ON_PREMISE;
12+
import static org.assertj.core.api.Assertions.as;
13+
import static org.assertj.core.api.Assertions.assertThat;
714
import static org.assertj.core.api.Assertions.assertThatNoException;
15+
import static org.assertj.core.api.InstanceOfAssertFactories.type;
816

17+
import javax.annotation.Nonnull;
18+
19+
import org.apache.hc.client5.http.config.Configurable;
920
import org.junit.jupiter.api.Test;
1021

1122
class ApacheHttpClient5FactoryBuilderTest
@@ -17,4 +28,59 @@ void testBuilderContainsOptionalParametersOnly()
1728
// make sure we can build a new factory instance without supplying any parameters
1829
assertThatNoException().isThrownBy(() -> new ApacheHttpClient5FactoryBuilder().build());
1930
}
31+
32+
@Test
33+
void testTlsUpgradeToggle()
34+
{
35+
var service = "https://servive";
36+
var proxy = ProxyConfiguration.of("https://proxy");
37+
38+
var destInternet = builder(service).trustAllCertificates().build();
39+
var destOnPremise = builder(service).proxyType(ON_PREMISE).proxyConfiguration(proxy).buildInternal();
40+
var destProxy = builder(service).trustAllCertificates().proxyType(INTERNET).proxyConfiguration(proxy).build();
41+
var destTlsVersion = builder(service).trustAllCertificates().tlsVersion("TLSv1.1").build();
42+
43+
ApacheHttpClient5Factory sut;
44+
45+
// force upgrade=true
46+
sut = new ApacheHttpClient5FactoryBuilder().tlsUpgrade(ENABLED).build();
47+
assertProtocolUpgradeEnabled(sut, destInternet);
48+
assertProtocolUpgradeEnabled(sut, destOnPremise);
49+
assertProtocolUpgradeEnabled(sut, destProxy);
50+
assertProtocolUpgradeEnabled(sut, destTlsVersion);
51+
52+
// force upgrade=false
53+
sut = new ApacheHttpClient5FactoryBuilder().tlsUpgrade(DISABLED).build();
54+
assertProtocolUpgradeDisabled(sut, destInternet);
55+
assertProtocolUpgradeDisabled(sut, destOnPremise);
56+
assertProtocolUpgradeDisabled(sut, destProxy);
57+
assertProtocolUpgradeDisabled(sut, destTlsVersion);
58+
59+
// default
60+
sut = new ApacheHttpClient5FactoryBuilder().build();
61+
assertProtocolUpgradeEnabled(sut, destInternet);
62+
assertProtocolUpgradeDisabled(sut, destOnPremise);
63+
assertProtocolUpgradeEnabled(sut, destProxy);
64+
assertProtocolUpgradeDisabled(sut, destTlsVersion);
65+
}
66+
67+
private void assertProtocolUpgradeEnabled(
68+
@Nonnull final ApacheHttpClient5Factory factory,
69+
@Nonnull final DefaultHttpDestination dest )
70+
{
71+
assertThat(factory.createHttpClient(dest))
72+
.isNotNull()
73+
.extracting("httpClient", as(type(Configurable.class)))
74+
.satisfies(client -> assertThat(client.getConfig().isProtocolUpgradeEnabled()).isTrue());
75+
}
76+
77+
private void assertProtocolUpgradeDisabled(
78+
@Nonnull final ApacheHttpClient5Factory factory,
79+
@Nonnull final DefaultHttpDestination dest )
80+
{
81+
assertThat(factory.createHttpClient(dest))
82+
.isNotNull()
83+
.extracting("httpClient", as(type(Configurable.class)))
84+
.satisfies(client -> assertThat(client.getConfig().isProtocolUpgradeEnabled()).isFalse());
85+
}
2086
}

cloudplatform/connectivity-apache-httpclient5/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DefaultApacheHttpClient5CacheTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ class DefaultApacheHttpClient5CacheTest
5252
new DefaultApacheHttpClient5Factory(
5353
DefaultApacheHttpClient5Factory.DEFAULT_TIMEOUT,
5454
DefaultApacheHttpClient5Factory.DEFAULT_MAX_CONNECTIONS_TOTAL,
55-
DefaultApacheHttpClient5Factory.DEFAULT_MAX_CONNECTIONS_PER_ROUTE);
55+
DefaultApacheHttpClient5Factory.DEFAULT_MAX_CONNECTIONS_PER_ROUTE,
56+
null,
57+
ApacheHttpClient5FactoryBuilder.TlsUpgrade.AUTOMATIC);
5658
private static final long NANOSECONDS_IN_MINUTE = 60_000_000_000L;
5759
private static final Duration TEN_MINUTES = Duration.ofMinutes(10L);
5860

cloudplatform/connectivity-apache-httpclient5/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DefaultApacheHttpClient5FactoryTest.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import static com.github.tomakehurst.wiremock.client.WireMock.ok;
1010
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
1111
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
12+
import static com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5FactoryBuilder.TlsUpgrade.AUTOMATIC;
1213
import static org.assertj.core.api.Assertions.assertThat;
1314
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1415
import static org.mockito.ArgumentMatchers.any;
@@ -87,7 +88,8 @@ void setup()
8788
CLIENT_TIMEOUT,
8889
MAX_CONNECTIONS,
8990
MAX_CONNECTIONS_PER_ROUTE,
90-
requestInterceptor);
91+
requestInterceptor,
92+
AUTOMATIC);
9193
}
9294

9395
@Test
@@ -101,14 +103,16 @@ void testHttpClientUsesTimeout()
101103
Duration.ofSeconds(3L),
102104
MAX_CONNECTIONS,
103105
MAX_CONNECTIONS_PER_ROUTE,
104-
requestInterceptor);
106+
requestInterceptor,
107+
AUTOMATIC);
105108

106109
final ApacheHttpClient5Factory factoryWithEnoughTimeout =
107110
new DefaultApacheHttpClient5Factory(
108111
Duration.ofSeconds(7L),
109112
MAX_CONNECTIONS,
110113
MAX_CONNECTIONS_PER_ROUTE,
111-
requestInterceptor);
114+
requestInterceptor,
115+
AUTOMATIC);
112116

113117
final ClassicHttpRequest request = new HttpGet(WIRE_MOCK_SERVER.url("/timeout"));
114118

@@ -136,7 +140,8 @@ void testHttpClientUsesMaxConnections()
136140
Duration.ofSeconds(3L), // this timeout is also used for the connection lease
137141
1,
138142
MAX_CONNECTIONS_PER_ROUTE,
139-
requestInterceptor);
143+
requestInterceptor,
144+
AUTOMATIC);
140145

141146
final HttpClient client = sut.createHttpClient();
142147
final ClassicHttpRequest firstRequest = new HttpGet(WIRE_MOCK_SERVER.url("/max-connections-1"));
@@ -158,7 +163,8 @@ void testHttpClientUsesMaxConnectionsPerRoute()
158163
Duration.ofSeconds(3L), // this timeout is also used for the connection lease
159164
MAX_CONNECTIONS,
160165
1,
161-
requestInterceptor);
166+
requestInterceptor,
167+
AUTOMATIC);
162168

163169
final ClassicHttpRequest firstRequest = new HttpGet(WIRE_MOCK_SERVER.url("/max-connections-per-route"));
164170
final ClassicHttpRequest secondRequest = new HttpGet(SECOND_WIRE_MOCK_SERVER.url("/max-connections-per-route"));

release_notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
### 📈 Improvements
1818

1919
- Minor improvement on OpenAPI code generator to apply JavaDoc on customized model class constructors.
20+
- Fix a TLS compatibility issue between the latest _Apache HttpClient 5_ and on-premise connectivity (via SAP Cloud Connector).
2021
- Stabilize ApacheHttpClient5 related API without changes.
2122
The `@Beta` annotations are removed in most places and consuming applications no longer need to suppress warnings.
2223

0 commit comments

Comments
 (0)