Skip to content

Commit 606d2b3

Browse files
authored
feat: allow users to configure client and server SSL separately (#4507)
Signed-off-by: ac892247 <a.chmelo@gmail.com>
1 parent c005c2e commit 606d2b3

44 files changed

Lines changed: 687 additions & 98 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api-catalog-package/src/main/resources/bin/start.sh

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,17 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${CATALOG_CODE} ${JAVA_BIN_DIR}java \
136136
-Dapiml.catalog.customStyle.headerColor=${ZWE_configs_apiml_catalog_customStyle_headerColor:-} \
137137
-Dapiml.catalog.customStyle.textColor=${ZWE_configs_apiml_catalog_customStyle_textColor:-} \
138138
-Dapiml.catalog.customStyle.docLink=${ZWE_configs_apiml_catalog_customStyle_docLink:-} \
139-
-Dapiml.httpclient.ssl.enabled-protocols=${client_enabled_protocols} \
139+
-Dapiml.service.ssl.enabled-protocols=${ZWE_configs_apiml_service_ssl_enabled_protocols:-${client_enabled_protocols}} \
140+
-Dapiml.service.ssl.ciphers=${ZWE_configs_apiml_service_ssl_ciphers:-${client_ciphers}} \
141+
-Dapiml.service.ssl.key-alias="${ZWE_configs_apiml_service_ssl_key_alias:-${key_alias}}" \
142+
-Dapiml.service.ssl.key-password="${ZWE_configs_apiml_service_ssl_key_password:-${key_pass}}" \
143+
-Dapiml.service.ssl.key-store="${ZWE_configs_apiml_service_ssl_key_store:-${keystore_location}}" \
144+
-Dapiml.service.ssl.key-store-password="${ZWE_configs_apiml_service_ssl_key_store_password:-${keystore_pass}}" \
145+
-Dapiml.service.ssl.key-store-type="${ZWE_configs_apiml_service_ssl_key_store_type:-${keystore_type}}" \
146+
-Dapiml.service.ssl.protocol=${ZWE_configs_apiml_service_ssl_protocol:-${server_protocol}} \
147+
-Dapiml.service.ssl.trust-store="${ZWE_configs_apiml_service_ssl_trust_store:-${truststore_location}}" \
148+
-Dapiml.service.ssl.trust-store-password="${ZWE_configs_apiml_service_ssl_trust_store_password:-${truststore_pass}}" \
149+
-Dapiml.service.ssl.trust-store-type="${ZWE_configs_apiml_service_ssl_trust_store_type:-${truststore_type}}" \
140150
-Djdk.tls.client.cipherSuites=${client_ciphers} \
141151
-Dserver.ssl.ciphers=${server_ciphers} \
142152
-Dserver.ssl.protocol=${server_protocol} \

api-catalog-package/src/main/resources/schemas/api-catalog-config.json

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,71 @@
117117
}
118118
}
119119
}
120+
},
121+
"service": {
122+
"type": "object",
123+
"description": "General configuration of the API Catalog service.",
124+
"properties": {
125+
"ssl": {
126+
"type": "object",
127+
"description": "SSL/TLS configuration for the API ML HTTP client",
128+
"properties": {
129+
"enabled-protocols": {
130+
"type": "string",
131+
"description": "List of enabled SSL/TLS protocols.",
132+
"pattern": "^TLSv\\d+(\\.\\d+)?(,TLSv\\d+(\\.\\d+)?)*$"
133+
},
134+
"protocol": {
135+
"type": "string",
136+
"description": "SSL/TLS protocol to use.",
137+
"default": "TLSv1.2"
138+
},
139+
"ciphers": {
140+
"type": "string",
141+
"description": "Comma-separated list of enabled cipher suites."
142+
},
143+
"key-alias": {
144+
"type": "string",
145+
"description": "Alias of the key in the keystore."
146+
},
147+
"key-store": {
148+
"type": "string",
149+
"description": "Path to the keystore file."
150+
},
151+
"key-store-password": {
152+
"type": "string",
153+
"description": "Password for the keystore."
154+
},
155+
"key-store-type": {
156+
"type": "string",
157+
"description": "Type of the keystore.",
158+
"default": "PKCS12"
159+
},
160+
"key-password": {
161+
"type": "string",
162+
"description": "Password for the key in the keystore."
163+
},
164+
"trust-store": {
165+
"type": "string",
166+
"description": "Path to the truststore file."
167+
},
168+
"trust-store-password": {
169+
"type": "string",
170+
"description": "Password for the truststore."
171+
},
172+
"trust-store-type": {
173+
"type": "string",
174+
"description": "Type of the truststore.",
175+
"default": "PKCS12"
176+
},
177+
"trust-store-required": {
178+
"type": "boolean",
179+
"description": "Whether a truststore is required.",
180+
"default": false
181+
}
182+
}
183+
}
184+
}
120185
}
121186
}
122187
},

apiml-common/src/main/java/org/zowe/apiml/product/web/HttpConfig.java

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,37 +53,37 @@ public class HttpConfig implements InitializingBean {
5353

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

56-
@Value("${server.ssl.protocol:TLSv1.2}")
56+
@Value("${apiml.service.ssl.protocol:${server.ssl.protocol:TLSv1.2}}")
5757
private String protocol;
5858

59-
@Value("${apiml.httpclient.ssl.enabled-protocols:TLSv1.2,TLSv1.3}")
59+
@Value("${apiml.service.ssl.enabled-protocols:TLSv1.2,TLSv1.3}")
6060
private String[] supportedProtocols;
6161

62-
@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}")
62+
@Value("${apiml.service.ssl.ciphers:${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}}")
6363
private String[] ciphers;
6464

65-
@Value("${server.ssl.trustStore:#{null}}")
65+
@Value("${apiml.service.ssl.trust-store:${server.ssl.trustStore:#{null}}}")
6666
private String trustStorePath;
6767

68-
@Value("${server.ssl.trustStorePassword:#{null}}")
68+
@Value("${apiml.service.ssl.trust-store-password:${server.ssl.trustStorePassword:#{null}}}")
6969
private char[] trustStorePassword;
7070

71-
@Value("${server.ssl.trustStoreType:PKCS12}")
71+
@Value("${apiml.service.ssl.trust-store-type:${server.ssl.trustStoreType:PKCS12}}")
7272
private String trustStoreType;
7373

74-
@Value("${server.ssl.keyAlias:#{null}}")
74+
@Value("${apiml.service.ssl.key-alias:${server.ssl.keyAlias:#{null}}}")
7575
private String keyAlias;
7676

77-
@Value("${server.ssl.keyStore:#{null}}")
77+
@Value("${apiml.service.ssl.key-store:${server.ssl.keyStore:#{null}}}")
7878
private String keyStorePath;
7979

80-
@Value("${server.ssl.keyStorePassword:#{null}}")
80+
@Value("${apiml.service.ssl.key-store-password:${server.ssl.keyStorePassword:#{null}}}")
8181
private char[] keyStorePassword;
8282

83-
@Value("${server.ssl.keyPassword:#{null}}")
83+
@Value("${apiml.service.ssl.key-password:${server.ssl.keyPassword:#{null}}}")
8484
private char[] keyPassword;
8585

86-
@Value("${server.ssl.keyStoreType:PKCS12}")
86+
@Value("${apiml.service.ssl.key-store-type:${server.ssl.keyStoreType:PKCS12}}")
8787
private String keyStoreType;
8888

8989
@Value("${apiml.security.ssl.verifySslCertificatesOfServices:true}")
@@ -92,7 +92,7 @@ public class HttpConfig implements InitializingBean {
9292
@Value("${apiml.security.ssl.nonStrictVerifySslCertificatesOfServices:false}")
9393
private boolean nonStrictVerifySslCertificatesOfServices;
9494

95-
@Value("${server.ssl.trustStoreRequired:false}")
95+
@Value("${apiml.service.ssl.trust-store-required:${server.ssl.trustStoreRequired:false}}")
9696
private boolean trustStoreRequired;
9797

9898
@Value("${server.maxConnectionsPerRoute:#{10}}")
@@ -123,17 +123,26 @@ public class HttpConfig implements InitializingBean {
123123
private final ApplicationContext context;
124124

125125
void updateStorePaths() {
126-
ServerProperties serverProperties = context.getBean(ServerProperties.class);
127126
if (SecurityUtils.isKeyring(keyStorePath)) {
128127
keyStorePath = SecurityUtils.formatKeyringUrl(keyStorePath);
129-
serverProperties.getSsl().setKeyStore(keyStorePath);
130128
if (keyStorePassword == null) keyStorePassword = KEYRING_PASSWORD;
131129
}
132130
if (SecurityUtils.isKeyring(trustStorePath)) {
133131
trustStorePath = SecurityUtils.formatKeyringUrl(trustStorePath);
134-
serverProperties.getSsl().setTrustStore(trustStorePath);
135132
if (trustStorePassword == null) trustStorePassword = KEYRING_PASSWORD;
136133
}
134+
135+
ServerProperties serverProperties = context.getBean(ServerProperties.class);
136+
if (serverProperties.getSsl() != null) {
137+
String serverKeyStore = serverProperties.getSsl().getKeyStore();
138+
if (SecurityUtils.isKeyring(serverKeyStore)) {
139+
serverProperties.getSsl().setKeyStore(SecurityUtils.formatKeyringUrl(serverKeyStore));
140+
}
141+
String serverTrustStore = serverProperties.getSsl().getTrustStore();
142+
if (SecurityUtils.isKeyring(serverTrustStore)) {
143+
serverProperties.getSsl().setTrustStore(SecurityUtils.formatKeyringUrl(serverTrustStore));
144+
}
145+
}
137146
}
138147

139148
@Override

apiml-common/src/test/java/org/zowe/apiml/product/web/HttpConfigTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,26 @@ void whenKeystore_thenDoNothing() {
7272
assertNull(ReflectionTestUtils.getField(httpConfig, "trustStorePassword"));
7373
}
7474

75+
@Test
76+
void whenServerPropertiesHaveKeyring_thenFormatServerPropertiesIndependently() {
77+
ServerProperties properties = new ServerProperties();
78+
Ssl ssl = new Ssl();
79+
ssl.setKeyStore("safkeyring:///userId/ringId1");
80+
ssl.setTrustStore("safkeyring:////userId/ringId2");
81+
properties.setSsl(ssl);
82+
when(context.getBean(ServerProperties.class)).thenReturn(properties);
83+
84+
ReflectionTestUtils.setField(httpConfig, "keyStorePath", "/client/keystore.p12");
85+
ReflectionTestUtils.setField(httpConfig, "trustStorePath", "/client/truststore.p12");
86+
87+
httpConfig.updateStorePaths();
88+
89+
assertEquals("/client/keystore.p12", ReflectionTestUtils.getField(httpConfig, "keyStorePath"));
90+
assertEquals("/client/truststore.p12", ReflectionTestUtils.getField(httpConfig, "trustStorePath"));
91+
assertEquals("safkeyring://userId/ringId1", properties.getSsl().getKeyStore());
92+
assertEquals("safkeyring://userId/ringId2", properties.getSsl().getTrustStore());
93+
}
94+
7595
}
7696

7797
}

apiml-package/src/main/resources/bin/start.sh

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,17 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \
257257
-Dapiml.gateway.servicesToDisableRetry=${ZWE_components_gateway_apiml_gateway_servicesToDisableRetry:-${ZWE_configs_apiml_gateway_servicesToDisableRetry:-}} \
258258
-Dapiml.gateway.servicesToLimitRequestRate=${ZWE_components_gateway_apiml_gateway_servicesToLimitRequestRate:-${ZWE_configs_apiml_gateway_servicesToLimitRequestRate:-}} \
259259
-Dapiml.health.protected=${ZWE_components_gateway_apiml_health_protected:-${ZWE_configs_apiml_health_protected:-true}} \
260-
-Dapiml.httpclient.ssl.enabled-protocols=${client_enabled_protocols} \
260+
-Dapiml.service.ssl.enabled-protocols=${ZWE_configs_apiml_service_ssl_enabled_protocols:-${client_enabled_protocols}} \
261+
-Dapiml.service.ssl.ciphers=${ZWE_configs_apiml_service_ssl_ciphers:-${client_ciphers}} \
262+
-Dapiml.service.ssl.key-alias="${ZWE_configs_apiml_service_ssl_key_alias:-${key_alias}}" \
263+
-Dapiml.service.ssl.key-password="${ZWE_configs_apiml_service_ssl_key_password:-${key_pass}}" \
264+
-Dapiml.service.ssl.key-store="${ZWE_configs_apiml_service_ssl_key_store:-${keystore_location}}" \
265+
-Dapiml.service.ssl.key-store-password="${ZWE_configs_apiml_service_ssl_key_store_password:-${keystore_pass}}" \
266+
-Dapiml.service.ssl.key-store-type="${ZWE_configs_apiml_service_ssl_key_store_type:-${keystore_type}}" \
267+
-Dapiml.service.ssl.protocol=${ZWE_configs_apiml_service_ssl_protocol:-${server_protocol}} \
268+
-Dapiml.service.ssl.trust-store="${ZWE_configs_apiml_service_ssl_trust_store:-${truststore_location}}" \
269+
-Dapiml.service.ssl.trust-store-password="${ZWE_configs_apiml_service_ssl_trust_store_password:-${truststore_pass}}" \
270+
-Dapiml.service.ssl.trust-store-type="${ZWE_configs_apiml_service_ssl_trust_store_type:-${truststore_type}}" \
261271
-Dapiml.internal-discovery.port=${ZWE_components_discovery_port:-${ZWE_configs_internal_discovery_port:-7553}} \
262272
-Dapiml.internal-discovery.address=${ZWE_configs_internal_discovery_address:-${ZWE_configs_zowe_network_server_listenAddresses_0:-${ZWE_zowe_network_server_listenAddresses_0:-"0.0.0.0"}}} \
263273
-Dapiml.logs.location=${ZWE_zowe_logDirectory} \

apiml-package/src/main/resources/schemas/apiml-config.json

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,65 @@
564564
"type": "string",
565565
"description": "List of allowed HTTP methods when CORS is enabled.",
566566
"default": "GET,HEAD,POST,PATCH,DELETE,PUT,OPTIONS"
567+
},
568+
"ssl": {
569+
"type": "object",
570+
"description": "SSL/TLS configuration for the API ML HTTP client",
571+
"properties": {
572+
"enabled-protocols": {
573+
"type": "string",
574+
"description": "List of enabled SSL/TLS protocols.",
575+
"pattern": "^TLSv\\d+(\\.\\d+)?(,TLSv\\d+(\\.\\d+)?)*$"
576+
},
577+
"protocol": {
578+
"type": "string",
579+
"description": "SSL/TLS protocol to use.",
580+
"default": "TLSv1.2"
581+
},
582+
"ciphers": {
583+
"type": "string",
584+
"description": "Comma-separated list of enabled cipher suites."
585+
},
586+
"key-alias": {
587+
"type": "string",
588+
"description": "Alias of the key in the keystore."
589+
},
590+
"key-store": {
591+
"type": "string",
592+
"description": "Path to the keystore file."
593+
},
594+
"key-store-password": {
595+
"type": "string",
596+
"description": "Password for the keystore."
597+
},
598+
"key-store-type": {
599+
"type": "string",
600+
"description": "Type of the keystore.",
601+
"default": "PKCS12"
602+
},
603+
"key-password": {
604+
"type": "string",
605+
"description": "Password for the key in the keystore."
606+
},
607+
"trust-store": {
608+
"type": "string",
609+
"description": "Path to the truststore file."
610+
},
611+
"trust-store-password": {
612+
"type": "string",
613+
"description": "Password for the truststore."
614+
},
615+
"trust-store-type": {
616+
"type": "string",
617+
"description": "Type of the truststore.",
618+
"default": "PKCS12"
619+
},
620+
"trust-store-required": {
621+
"type": "boolean",
622+
"description": "Whether a truststore is required.",
623+
"default": false
624+
}
625+
}
567626
}
568627
}
569628
},

apiml-security-common/src/main/java/org/zowe/apiml/security/common/config/WebClientConfig.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@
1010

1111
package org.zowe.apiml.security.common.config;
1212

13-
import lombok.RequiredArgsConstructor;
14-
import lombok.extern.slf4j.Slf4j;
13+
import java.util.List;
14+
1515
import org.apache.commons.lang3.StringUtils;
1616
import org.springframework.beans.factory.annotation.Value;
1717
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
18+
import org.springframework.boot.autoconfigure.web.ServerProperties;
19+
import org.springframework.cloud.gateway.config.HttpClientCustomizer;
20+
import org.springframework.cloud.gateway.config.HttpClientFactory;
21+
import org.springframework.cloud.gateway.config.HttpClientProperties;
22+
import org.springframework.cloud.gateway.config.HttpClientSslConfigurer;
1823
import org.springframework.context.annotation.Bean;
1924
import org.springframework.context.annotation.Configuration;
2025
import org.springframework.context.annotation.Primary;
@@ -26,6 +31,10 @@
2631
import org.zowe.apiml.security.HttpsConfigError;
2732
import org.zowe.apiml.security.common.util.ConnectionUtil;
2833

34+
import io.netty.handler.ssl.SslContext;
35+
import io.netty.resolver.DefaultAddressResolverGroup;
36+
import lombok.RequiredArgsConstructor;
37+
import lombok.extern.slf4j.Slf4j;
2938
import reactor.netty.http.client.HttpClient;
3039

3140
@Slf4j
@@ -41,6 +50,30 @@ public class WebClientConfig {
4150
@Value("${server.attlsClient.enabled:false}")
4251
private boolean isClientAttlsEnabled;
4352

53+
@Bean
54+
HttpClientFactory gatewayHttpClientFactory(
55+
HttpClientProperties properties,
56+
ServerProperties serverProperties, List<HttpClientCustomizer> customizers,
57+
HttpClientSslConfigurer sslConfigurer
58+
) {
59+
SslContext sslContext;
60+
try {
61+
sslContext = ConnectionUtil.getSslContext(config, false);
62+
} catch (Exception e) {
63+
apimlLog.log("org.zowe.apiml.common.sslContextInitializationError", e.getMessage());
64+
throw new HttpsConfigError("Error initializing SSL Context: " + e.getMessage(), e,
65+
HttpsConfigError.ErrorCode.HTTP_CLIENT_INITIALIZATION_FAILED, config.httpsConfig());
66+
}
67+
return new HttpClientFactory(properties, serverProperties, sslConfigurer, customizers) {
68+
@Override
69+
protected HttpClient createInstance() {
70+
return super.createInstance()
71+
.secure(sslContextSpec -> sslContextSpec.sslContext(sslContext))
72+
.resolver(DefaultAddressResolverGroup.INSTANCE);
73+
}
74+
};
75+
}
76+
4477
HttpClient getHttpClient(HttpClient httpClient, boolean useClientCert) {
4578
try {
4679
return ConnectionUtil.getHttpClient(config, httpClient, useClientCert);

0 commit comments

Comments
 (0)