Skip to content

Commit

Permalink
fix: refactor SSL configuration (#2832)
Browse files Browse the repository at this point in the history
* refactor TLS configuration

Signed-off-by: achmelo <[email protected]>

* builder default bug workaround

Signed-off-by: achmelo <[email protected]>

* default TLS protocol

Signed-off-by: achmelo <[email protected]>

---------

Signed-off-by: achmelo <[email protected]>
  • Loading branch information
achmelo authored Mar 16, 2023
1 parent 6654271 commit 33f4882
Show file tree
Hide file tree
Showing 18 changed files with 222 additions and 205 deletions.
2 changes: 1 addition & 1 deletion api-catalog-services/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ apiml:
security:
ssl:
sslEnabled: true
protocol: TLSv1.2
protocol: TLS
trustStore: ${server.ssl.truststore}
trustStoreType: ${server.ssl.truststoreType}
trustStorePassword: ${server.ssl.truststorePassword}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ public class HttpConfig {

private static final char[] KEYRING_PASSWORD = "password".toCharArray();

@Value("${server.ssl.protocol:TLSv1.2}")
@Value("${server.ssl.protocol:TLS}")
private String protocol;
@Value("${apiml.httpclient.ssl.enabled-protocols:TLSv1.2,TLSv1.3}")
private String[] supportedProtocols;
@Value("${server.ssl.ciphers:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384}")
private String[] ciphers;

@Value("${server.ssl.trustStore:#{null}}")
private String trustStore;
Expand All @@ -75,9 +79,6 @@ public class HttpConfig {
@Value("${server.ssl.keyStoreType:PKCS12}")
private String keyStoreType;

@Value("${server.ssl.ciphers:.*}")
private String[] ciphers;

@Value("${apiml.security.ssl.verifySslCertificatesOfServices:true}")
private boolean verifySslCertificatesOfServices;

Expand Down Expand Up @@ -108,9 +109,6 @@ public class HttpConfig {
@Value("${apiml.httpclient.conn-pool.timeToLive:#{10000}}")
private int timeToLive;

@Value("${server.attls.enabled:false}")
private boolean isAttlsEnabled;

private CloseableHttpClient secureHttpClient;
private CloseableHttpClient secureHttpClientWithoutKeystore;
private SSLContext secureSslContext;
Expand Down Expand Up @@ -145,7 +143,7 @@ public void init() {
try {
Supplier<HttpsConfig.HttpsConfigBuilder> httpsConfigSupplier = () ->
HttpsConfig.builder()
.protocol(protocol)
.protocol(protocol).enabledProtocols(supportedProtocols).cipherSuite(ciphers)
.trustStore(trustStore).trustStoreType(trustStoreType)
.trustStorePassword(trustStorePassword).trustStoreRequired(trustStoreRequired)
.verifySslCertificatesOfServices(verifySslCertificatesOfServices)
Expand All @@ -156,7 +154,7 @@ public void init() {

HttpsConfig httpsConfig = httpsConfigSupplier.get()
.keyAlias(keyAlias).keyStore(keyStore).keyPassword(keyPassword)
.keyStorePassword(keyStorePassword).keyStoreType(keyStoreType).trustStore(trustStore)
.keyStorePassword(keyStorePassword).keyStoreType(keyStoreType)
.build();

HttpsConfig httpsConfigWithoutKeystore = httpsConfigSupplier.get().build();
Expand All @@ -166,8 +164,8 @@ public void init() {
HttpsFactory factory = new HttpsFactory(httpsConfig);
ApimlPoolingHttpClientConnectionManager secureConnectionManager = getConnectionManager(factory);
secureHttpClient = factory.createSecureHttpClient(secureConnectionManager);
secureSslContext = factory.createSslContext();
secureHostnameVerifier = factory.createHostnameVerifier();
secureSslContext = factory.getSslContext();
secureHostnameVerifier = factory.getHostnameVerifier();
eurekaJerseyClientBuilder = factory.createEurekaJerseyClientBuilder(eurekaServerUrl, serviceId);
optionalArgs.setEurekaJerseyClient(eurekaJerseyClient());
HttpsFactory factoryWithoutKeystore = new HttpsFactory(httpsConfigWithoutKeystore);
Expand Down
2 changes: 1 addition & 1 deletion caching-service/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ server:
ssl:
enabled: true
clientAuth: want
protocol: TLSv1.2
protocol: TLS
enabled-protocols: TLSv1.2+TLSv1.3
ciphers: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384
keyStoreType: PKCS12
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,12 @@
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.config.GlobalCorsProperties;
import org.springframework.cloud.gateway.config.HttpClientCustomizer;
import org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping;
import org.springframework.cloud.netflix.eureka.CloudEurekaClient;
import org.springframework.cloud.netflix.eureka.MutableDiscoveryClientOptionalArgs;
Expand All @@ -48,8 +44,6 @@
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.pattern.PathPatternParser;
import org.zowe.apiml.cloudgatewayservice.service.ProxyRouteLocator;
import org.zowe.apiml.cloudgatewayservice.service.RouteLocator;
import org.zowe.apiml.message.core.MessageService;
import org.zowe.apiml.message.yaml.YamlMessageServiceInstance;
import org.zowe.apiml.security.HttpsConfig;
Expand All @@ -63,15 +57,11 @@
import javax.net.ssl.TrustManagerFactory;
import java.security.KeyStore;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Configuration
@Slf4j
public class HttpConfig {
public class ConnectionsConfig {

private static final char[] KEYRING_PASSWORD = "password".toCharArray();

Expand Down Expand Up @@ -121,16 +111,14 @@ public class HttpConfig {
private int requestTimeout;
@Value("${apiml.service.corsEnabled:false}")
private boolean corsEnabled;
@Value("${apiml.service.ignoredHeadersWhenCorsEnabled:-}")
private String ignoredHeadersWhenCorsEnabled;
private final ApplicationContext context;

public HttpConfig(ApplicationContext context) {
public ConnectionsConfig(ApplicationContext context) {
this.context = context;
}

@PostConstruct
public void init() {
public void updateConfigParameters() {
if (SecurityUtils.isKeyring(keyStorePath)) {
keyStorePath = SecurityUtils.formatKeyringUrl(keyStorePath);
if (keyStorePassword == null) keyStorePassword = KEYRING_PASSWORD;
Expand Down Expand Up @@ -168,7 +156,9 @@ HttpClientCustomizer secureCustomizer() {
}



/**
* @return io.netty.handler.ssl.SslContext for http client.
*/
SslContext sslContext() {
try {
KeyStore keyStore = SecurityUtils.loadKeyStore(keyStoreType, keyStorePath, keyStorePassword);
Expand Down Expand Up @@ -208,21 +198,6 @@ public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientCon
return cloudEurekaClient;
}


@Bean
@ConditionalOnProperty(name = "apiml.service.gateway.proxy.enabled", havingValue = "false")
public RouteLocator apimlDiscoveryRouteDefLocator(
ReactiveDiscoveryClient discoveryClient, DiscoveryLocatorProperties properties, List<FilterDefinition> filters, ApplicationContext context, CorsUtils corsUtils) {
return new RouteLocator(discoveryClient, properties, filters, context, corsUtils);
}

@Bean
@ConditionalOnProperty(name = "apiml.service.gateway.proxy.enabled", havingValue = "true")
public RouteLocator proxyRouteDefLocator(
ReactiveDiscoveryClient discoveryClient, DiscoveryLocatorProperties properties, List<FilterDefinition> filters, ApplicationContext context, CorsUtils corsUtils) {
return new ProxyRouteLocator(discoveryClient, properties, filters, context, corsUtils);
}

@Bean
public Customizer<ReactiveResilience4JCircuitBreakerFactory> defaultCustomizer() {
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
Expand All @@ -236,29 +211,6 @@ public WebClient webClient() {

}

@Bean
public List<FilterDefinition> filters() {
FilterDefinition circuitBreakerFilter = new FilterDefinition();
circuitBreakerFilter.setName("CircuitBreaker");
FilterDefinition retryFilter = new FilterDefinition();
retryFilter.setName("Retry");

retryFilter.addArg("retries", "5");
retryFilter.addArg("statuses", "SERVICE_UNAVAILABLE");
List<FilterDefinition> filters = new ArrayList<>();
filters.add(circuitBreakerFilter);
filters.add(retryFilter);
for (String headerName : ignoredHeadersWhenCorsEnabled.split(",")) {
FilterDefinition removeHeaders = new FilterDefinition();
removeHeaders.setName("RemoveRequestHeader");
Map<String, String> args = new HashMap<>();
args.put("name", headerName);
removeHeaders.setArgs(args);
filters.add(removeHeaders);
}
return filters;
}

@Bean
public CorsConfigurationSource corsConfigurationSource(RoutePredicateHandlerMapping handlerMapping, GlobalCorsProperties globalCorsProperties, CorsUtils corsUtils) {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.cloudgatewayservice.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.zowe.apiml.cloudgatewayservice.service.ProxyRouteLocator;
import org.zowe.apiml.cloudgatewayservice.service.RouteLocator;
import org.zowe.apiml.util.CorsUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Configuration
public class RoutingConfig {

@Value("${apiml.service.ignoredHeadersWhenCorsEnabled:-}")
private String ignoredHeadersWhenCorsEnabled;

@Bean
@ConditionalOnProperty(name = "apiml.service.gateway.proxy.enabled", havingValue = "false")
public RouteLocator apimlDiscoveryRouteDefLocator(
ReactiveDiscoveryClient discoveryClient, DiscoveryLocatorProperties properties, List<FilterDefinition> filters, ApplicationContext context, CorsUtils corsUtils) {
return new RouteLocator(discoveryClient, properties, filters, context, corsUtils);
}

@Bean
@ConditionalOnProperty(name = "apiml.service.gateway.proxy.enabled", havingValue = "true")
public RouteLocator proxyRouteDefLocator(
ReactiveDiscoveryClient discoveryClient, DiscoveryLocatorProperties properties, List<FilterDefinition> filters, ApplicationContext context, CorsUtils corsUtils) {
return new ProxyRouteLocator(discoveryClient, properties, filters, context, corsUtils);
}

@Bean
public List<FilterDefinition> filters() {
FilterDefinition circuitBreakerFilter = new FilterDefinition();
circuitBreakerFilter.setName("CircuitBreaker");
FilterDefinition retryFilter = new FilterDefinition();
retryFilter.setName("Retry");

retryFilter.addArg("retries", "5");
retryFilter.addArg("statuses", "SERVICE_UNAVAILABLE");
List<FilterDefinition> filters = new ArrayList<>();
filters.add(circuitBreakerFilter);
filters.add(retryFilter);
for (String headerName : ignoredHeadersWhenCorsEnabled.split(",")) {
FilterDefinition removeHeaders = new FilterDefinition();
removeHeaders.setName("RemoveRequestHeader");
Map<String, String> args = new HashMap<>();
args.put("name", headerName);
removeHeaders.setArgs(args);
filters.add(removeHeaders);
}
return filters;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,19 @@

@SpringBootTest
@ComponentScan(basePackages = "org.zowe.apiml.cloudgatewayservice")
class HttpConfigTest {
class ConnectionsConfigTest {

@Autowired
private HttpConfig httpConfig;
private ConnectionsConfig connectionsConfig;
@Autowired
private RoutingConfig routingConfig;

@Nested
class WhenCreateEurekaJerseyClientBuilder {
@Test
void thenIsNotNull() {
Assertions.assertNotNull(httpConfig);
Assertions.assertNotNull(httpConfig.getEurekaJerseyClient());
Assertions.assertNotNull(connectionsConfig);
Assertions.assertNotNull(connectionsConfig.getEurekaJerseyClient());
}
}

Expand All @@ -53,7 +55,7 @@ class WhenCreateRouteLocator {
void thenIsNotNull() {
ReactiveDiscoveryClient discoveryClient = mock(ReactiveDiscoveryClient.class);
DiscoveryLocatorProperties properties = mock(DiscoveryLocatorProperties.class);
Assertions.assertNotNull(httpConfig.proxyRouteDefLocator(discoveryClient, properties, Collections.singletonList(new FilterDefinition("name=value")), null, null));
Assertions.assertNotNull(routingConfig.proxyRouteDefLocator(discoveryClient, properties, Collections.singletonList(new FilterDefinition("name=value")), null, null));
}
}

Expand All @@ -73,7 +75,7 @@ class WhenInitializeEurekaClient {

@Test
void thenCreateIt() {
Assertions.assertNotNull(httpConfig.eurekaClient(manager, config, eurekaJerseyClient, healthCheckHandler));
Assertions.assertNotNull(connectionsConfig.eurekaClient(manager, config, eurekaJerseyClient, healthCheckHandler));
}
}

Expand All @@ -82,30 +84,30 @@ class KeyringFormatAndPasswordUpdate {

@Test
void whenKeyringHasWrongFormatAndMissingPasswords_thenFixIt() {
HttpConfig httpConfig = new HttpConfig(null);
ReflectionTestUtils.setField(httpConfig, "keyStorePath", "safkeyring:///userId/ringId1");
ReflectionTestUtils.setField(httpConfig, "trustStorePath", "safkeyring:////userId/ringId2");
ConnectionsConfig connectionsConfig = new ConnectionsConfig(null);
ReflectionTestUtils.setField(connectionsConfig, "keyStorePath", "safkeyring:///userId/ringId1");
ReflectionTestUtils.setField(connectionsConfig, "trustStorePath", "safkeyring:////userId/ringId2");

httpConfig.init();
connectionsConfig.updateConfigParameters();

assertEquals("safkeyring://userId/ringId1", ReflectionTestUtils.getField(httpConfig, "keyStorePath"));
assertEquals("safkeyring://userId/ringId2", ReflectionTestUtils.getField(httpConfig, "trustStorePath"));
assertArrayEquals("password".toCharArray(), (char[]) ReflectionTestUtils.getField(httpConfig, "keyStorePassword"));
assertArrayEquals("password".toCharArray(), (char[]) ReflectionTestUtils.getField(httpConfig, "trustStorePassword"));
assertEquals("safkeyring://userId/ringId1", ReflectionTestUtils.getField(connectionsConfig, "keyStorePath"));
assertEquals("safkeyring://userId/ringId2", ReflectionTestUtils.getField(connectionsConfig, "trustStorePath"));
assertArrayEquals("password".toCharArray(), (char[]) ReflectionTestUtils.getField(connectionsConfig, "keyStorePassword"));
assertArrayEquals("password".toCharArray(), (char[]) ReflectionTestUtils.getField(connectionsConfig, "trustStorePassword"));
}

@Test
void whenKeystore_thenDoNothing() {
HttpConfig httpConfig = new HttpConfig(null);
ReflectionTestUtils.setField(httpConfig, "keyStorePath", "/path1");
ReflectionTestUtils.setField(httpConfig, "trustStorePath", "/path2");
ConnectionsConfig connectionsConfig = new ConnectionsConfig(null);
ReflectionTestUtils.setField(connectionsConfig, "keyStorePath", "/path1");
ReflectionTestUtils.setField(connectionsConfig, "trustStorePath", "/path2");

httpConfig.init();
connectionsConfig.updateConfigParameters();

assertEquals("/path1", ReflectionTestUtils.getField(httpConfig, "keyStorePath"));
assertEquals("/path2", ReflectionTestUtils.getField(httpConfig, "trustStorePath"));
assertNull(ReflectionTestUtils.getField(httpConfig, "keyStorePassword"));
assertNull(ReflectionTestUtils.getField(httpConfig, "trustStorePassword"));
assertEquals("/path1", ReflectionTestUtils.getField(connectionsConfig, "keyStorePath"));
assertEquals("/path2", ReflectionTestUtils.getField(connectionsConfig, "trustStorePath"));
assertNull(ReflectionTestUtils.getField(connectionsConfig, "keyStorePassword"));
assertNull(ReflectionTestUtils.getField(connectionsConfig, "trustStorePassword"));
}

}
Expand Down
Loading

0 comments on commit 33f4882

Please sign in to comment.