Skip to content

Commit dfb0168

Browse files
authored
fix: Fix support of different type of keyrings in proper format (just two slashes) (#2687)
1 parent 20fa246 commit dfb0168

File tree

28 files changed

+738
-170
lines changed

28 files changed

+738
-170
lines changed

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

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -123,21 +123,8 @@ key_pass="${ZWE_configs_certificate_key_password:-${ZWE_zowe_certificate_key_pas
123123
truststore_type="${ZWE_configs_certificate_truststore_type:-${ZWE_zowe_certificate_truststore_type:-PKCS12}}"
124124
truststore_pass="${ZWE_configs_certificate_truststore_password:-${ZWE_zowe_certificate_truststore_password}}"
125125

126-
127-
# Workaround for Java desiring safkeyring://// instead of just ://
128-
# We can handle both cases of user input by just adding extra "//" if we detect its missing.
129-
ensure_keyring_slashes() {
130-
keyring_string="${1}"
131-
only_two_slashes=$(echo "${keyring_string}" | grep "^safkeyring://[^//]")
132-
if [ -n "${only_two_slashes}" ]; then
133-
keyring_string=$(echo "${keyring_string}" | sed "s#safkeyring://#safkeyring:////#")
134-
fi
135-
# else, unmodified, perhaps its even p12
136-
echo $keyring_string
137-
}
138-
139-
keystore_location=$(ensure_keyring_slashes "${ZWE_configs_certificate_keystore_file:-${ZWE_zowe_certificate_keystore_file}}")
140-
truststore_location=$(ensure_keyring_slashes "${ZWE_configs_certificate_truststore_file:-${ZWE_zowe_certificate_truststore_file}}")
126+
keystore_location="${ZWE_configs_certificate_keystore_file:-${ZWE_zowe_certificate_keystore_file}}"
127+
truststore_location="${ZWE_configs_certificate_truststore_file:-${ZWE_zowe_certificate_truststore_file}}"
141128

142129
# NOTE: these are moved from below
143130
# -Dapiml.service.ipAddress=${ZOWE_IP_ADDRESS:-127.0.0.1} \

api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/config/TomcatConfiguration.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,25 @@
1010

1111
package org.zowe.apiml.apicatalog.config;
1212

13+
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
1314
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
1415
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
1516
import org.springframework.context.annotation.Bean;
1617
import org.springframework.context.annotation.Configuration;
1718

19+
import java.util.List;
20+
1821
/**
1922
* Configuration of Tomcat for the API Gateway.
2023
*/
2124
@Configuration
2225
public class TomcatConfiguration {
2326

2427
@Bean
25-
public ServletWebServerFactory servletContainer() {
28+
public ServletWebServerFactory servletContainer(List<TomcatConnectorCustomizer> connectorCustomizers) {
2629
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
2730
tomcat.setProtocol(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
31+
tomcat.addConnectorCustomizers(connectorCustomizers.toArray(new TomcatConnectorCustomizer[0]));
2832
return tomcat;
2933
}
3034
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
@Slf4j
4646
@Configuration
4747
public class HttpConfig {
48+
49+
private static final char[] KEYRING_PASSWORD = "password".toCharArray();
50+
4851
@Value("${server.ssl.protocol:TLSv1.2}")
4952
private String protocol;
5053

@@ -124,9 +127,21 @@ public class HttpConfig {
124127
@Resource
125128
private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;
126129

130+
void updateStorePaths() {
131+
if (SecurityUtils.isKeyring(keyStore)) {
132+
keyStore = SecurityUtils.formatKeyringUrl(keyStore);
133+
if (keyStorePassword == null) keyStorePassword = KEYRING_PASSWORD;
134+
}
135+
if (SecurityUtils.isKeyring(trustStore)) {
136+
trustStore = SecurityUtils.formatKeyringUrl(trustStore);
137+
if (trustStorePassword == null) trustStorePassword = KEYRING_PASSWORD;
138+
}
139+
}
127140

128141
@PostConstruct
129142
public void init() {
143+
updateStorePaths();
144+
130145
try {
131146
Supplier<HttpsConfig.HttpsConfigBuilder> httpsConfigSupplier = () ->
132147
HttpsConfig.builder()
@@ -197,7 +212,7 @@ public Set<String> publicKeyCertificatesBase64() {
197212

198213
private void setTruststore(SslContextFactory sslContextFactory) {
199214
if (StringUtils.isNotEmpty(trustStore)) {
200-
sslContextFactory.setTrustStorePath(SecurityUtils.replaceFourSlashes(trustStore));
215+
sslContextFactory.setTrustStorePath(SecurityUtils.formatKeyringUrl(trustStore));
201216
sslContextFactory.setTrustStoreType(trustStoreType);
202217
sslContextFactory.setTrustStorePassword(trustStorePassword == null ? null : String.valueOf(trustStorePassword));
203218
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* This program and the accompanying materials are made available under the terms of the
3+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
4+
* https://www.eclipse.org/legal/epl-v20.html
5+
*
6+
* SPDX-License-Identifier: EPL-2.0
7+
*
8+
* Copyright Contributors to the Zowe Project.
9+
*/
10+
11+
package org.zowe.apiml.product.web;
12+
13+
import org.junit.jupiter.api.Nested;
14+
import org.junit.jupiter.api.Test;
15+
import org.springframework.test.util.ReflectionTestUtils;
16+
17+
import static org.junit.jupiter.api.Assertions.*;
18+
19+
class HttpConfigTest {
20+
21+
@Nested
22+
class KeyringFormatAndPasswordUpdate {
23+
24+
@Test
25+
void whenKeyringHasWrongFormatAndMissingPasswords_thenFixIt() {
26+
HttpConfig httpConfig = new HttpConfig();
27+
ReflectionTestUtils.setField(httpConfig, "keyStore", "safkeyring:///userId/ringId1");
28+
ReflectionTestUtils.setField(httpConfig, "trustStore", "safkeyring:////userId/ringId2");
29+
30+
httpConfig.updateStorePaths();
31+
32+
assertEquals("safkeyring://userId/ringId1", ReflectionTestUtils.getField(httpConfig, "keyStore"));
33+
assertEquals("safkeyring://userId/ringId2", ReflectionTestUtils.getField(httpConfig, "trustStore"));
34+
assertArrayEquals("password".toCharArray(), (char[]) ReflectionTestUtils.getField(httpConfig, "keyStorePassword"));
35+
assertArrayEquals("password".toCharArray(), (char[]) ReflectionTestUtils.getField(httpConfig, "trustStorePassword"));
36+
}
37+
38+
@Test
39+
void whenKeystore_thenDoNothing() {
40+
HttpConfig httpConfig = new HttpConfig();
41+
ReflectionTestUtils.setField(httpConfig, "keyStore", "/path1");
42+
ReflectionTestUtils.setField(httpConfig, "trustStore", "/path2");
43+
44+
httpConfig.updateStorePaths();
45+
46+
assertEquals("/path1", ReflectionTestUtils.getField(httpConfig, "keyStore"));
47+
assertEquals("/path2", ReflectionTestUtils.getField(httpConfig, "trustStore"));
48+
assertNull(ReflectionTestUtils.getField(httpConfig, "keyStorePassword"));
49+
assertNull(ReflectionTestUtils.getField(httpConfig, "trustStorePassword"));
50+
}
51+
52+
}
53+
54+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* This program and the accompanying materials are made available under the terms of the
3+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
4+
* https://www.eclipse.org/legal/epl-v20.html
5+
*
6+
* SPDX-License-Identifier: EPL-2.0
7+
*
8+
* Copyright Contributors to the Zowe Project.
9+
*/
10+
11+
package org.zowe.apiml.product.web;
12+
13+
import org.apache.catalina.connector.Connector;
14+
import org.apache.tomcat.util.net.SSLHostConfig;
15+
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
16+
import org.springframework.beans.factory.annotation.Value;
17+
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
18+
import org.springframework.stereotype.Component;
19+
20+
import java.lang.reflect.Field;
21+
import java.util.Arrays;
22+
import java.util.Set;
23+
import java.util.regex.Matcher;
24+
import java.util.regex.Pattern;
25+
26+
@Component
27+
public class TomcatKeyringFix implements TomcatConnectorCustomizer {
28+
29+
private static final Pattern KEYRING_PATTERN = Pattern.compile("^(safkeyring[^:]*):/{2,4}([^/]+)/(.+)$");
30+
private static final String KEYRING_PASSWORD = "password";
31+
32+
@Value("${server.ssl.keyStore:#{null}}")
33+
protected String keyStore;
34+
35+
@Value("${server.ssl.keyStorePassword:#{null}}")
36+
protected char[] keyStorePassword;
37+
38+
@Value("${server.ssl.keyPassword:#{null}}")
39+
protected char[] keyPassword;
40+
41+
@Value("${server.ssl.trustStore:#{null}}")
42+
protected String trustStore;
43+
44+
@Value("${server.ssl.trustStorePassword:#{null}}")
45+
protected char[] trustStorePassword;
46+
47+
void fixDefaultCertificate(SSLHostConfig sslHostConfig) {
48+
Set<SSLHostConfigCertificate> originalSet = sslHostConfig.getCertificates();
49+
if (originalSet.isEmpty()) return;
50+
51+
try {
52+
Field defaultCertificateField = sslHostConfig.getClass().getDeclaredField("defaultCertificate");
53+
defaultCertificateField.setAccessible(true); // NOSONAR
54+
if (defaultCertificateField.get(sslHostConfig) == null) {
55+
defaultCertificateField.set(sslHostConfig, originalSet.iterator().next()); // NOSONAR
56+
}
57+
} catch (NoSuchFieldException | IllegalAccessException e) {
58+
throw new IllegalStateException("Cannot update Tomcat SSL context", e);
59+
}
60+
}
61+
62+
boolean isKeyring(String input) {
63+
if (input == null) return false;
64+
Matcher matcher = KEYRING_PATTERN.matcher(input);
65+
return matcher.matches();
66+
}
67+
68+
static String formatKeyringUrl(String keyringUrl) {
69+
if (keyringUrl == null) return null;
70+
Matcher matcher = KEYRING_PATTERN.matcher(keyringUrl);
71+
if (matcher.matches()) {
72+
keyringUrl = matcher.group(1) + "://" + matcher.group(2) + "/" + matcher.group(3);
73+
}
74+
return keyringUrl;
75+
}
76+
77+
@Override
78+
public void customize(Connector connector) {
79+
Arrays.stream(connector.findSslHostConfigs()).forEach(sslConfig -> {
80+
fixDefaultCertificate(sslConfig);
81+
82+
if (isKeyring(keyStore)) {
83+
sslConfig.setCertificateKeystoreFile(formatKeyringUrl(keyStore));
84+
if (keyStorePassword == null) sslConfig.setCertificateKeystorePassword(KEYRING_PASSWORD);
85+
if (keyPassword == null) sslConfig.setCertificateKeyPassword(KEYRING_PASSWORD);
86+
}
87+
88+
if (isKeyring(trustStore)) {
89+
sslConfig.setTruststoreFile(formatKeyringUrl(trustStore));
90+
if (trustStorePassword == null) sslConfig.setTruststorePassword(KEYRING_PASSWORD);
91+
}
92+
});
93+
}
94+
95+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* This program and the accompanying materials are made available under the terms of the
3+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
4+
* https://www.eclipse.org/legal/epl-v20.html
5+
*
6+
* SPDX-License-Identifier: EPL-2.0
7+
*
8+
* Copyright Contributors to the Zowe Project.
9+
*/
10+
11+
package org.zowe.apiml.product.web;
12+
13+
import org.apache.catalina.connector.Connector;
14+
import org.apache.tomcat.util.net.SSLHostConfig;
15+
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
16+
import org.junit.jupiter.api.BeforeEach;
17+
import org.junit.jupiter.api.Nested;
18+
import org.junit.jupiter.api.Test;
19+
import org.springframework.test.util.ReflectionTestUtils;
20+
21+
import static org.junit.jupiter.api.Assertions.assertNull;
22+
import static org.junit.jupiter.api.Assertions.assertSame;
23+
import static org.mockito.Mockito.*;
24+
25+
class TomcatKeyringFixTest {
26+
27+
private static final String PASSWORD = "password";
28+
29+
private SSLHostConfig sslHostConfig = mock(SSLHostConfig.class);
30+
31+
@BeforeEach
32+
void setUp() {
33+
}
34+
35+
private Connector getConnector() {
36+
Connector connector = mock(Connector.class);
37+
doReturn(new SSLHostConfig[] {sslHostConfig}).when(connector).findSslHostConfigs();
38+
return connector;
39+
}
40+
41+
private Connector customize(
42+
String keyStore, char[] keyStorePassword, char[] keyPassword,
43+
String trustStore, char[] trustStorePassword
44+
) {
45+
TomcatKeyringFix customizer = new TomcatKeyringFix();
46+
ReflectionTestUtils.setField(customizer, "keyStore", keyStore);
47+
ReflectionTestUtils.setField(customizer, "keyStorePassword", keyStorePassword);
48+
ReflectionTestUtils.setField(customizer, "keyPassword", keyPassword);
49+
ReflectionTestUtils.setField(customizer, "trustStore", trustStore);
50+
ReflectionTestUtils.setField(customizer, "trustStorePassword", trustStorePassword);
51+
52+
Connector connector = getConnector();
53+
customizer.customize(connector);
54+
return connector;
55+
}
56+
57+
@Nested
58+
class UsingKeyring {
59+
60+
@Test
61+
void whenInvalidFormatAndMissingPassword_thenFixIt() {
62+
customize("safkeyring:///userId/ringIdKs", null, null, "safkeyringpce:////userId/ringIdTs", null);
63+
verify(sslHostConfig).setCertificateKeystoreFile("safkeyring://userId/ringIdKs");
64+
verify(sslHostConfig).setCertificateKeystorePassword(PASSWORD);
65+
verify(sslHostConfig).setCertificateKeyPassword(PASSWORD);
66+
verify(sslHostConfig).setTruststoreFile("safkeyringpce://userId/ringIdTs");
67+
verify(sslHostConfig).setTruststorePassword(PASSWORD);
68+
}
69+
70+
}
71+
72+
@Nested
73+
class UsingKeystore {
74+
75+
@Test
76+
void dontUpdate() {
77+
customize("/somePath", null, null, "/anotherPath", null);
78+
verify(sslHostConfig, never()).setCertificateKeystoreFile(any());
79+
verify(sslHostConfig, never()).setCertificateKeystorePassword(any());
80+
verify(sslHostConfig, never()).setCertificateKeyPassword(any());
81+
verify(sslHostConfig, never()).setTruststoreFile(any());
82+
verify(sslHostConfig, never()).setTruststorePassword(any());
83+
}
84+
85+
}
86+
87+
@Nested
88+
class InvalidDefaultCertStructure {
89+
90+
@Test
91+
void whenSSLHostConfigContainsACertificate() {
92+
SSLHostConfigCertificate cert = mock(SSLHostConfigCertificate.class);
93+
sslHostConfig = new SSLHostConfig();
94+
sslHostConfig.getCertificates().add(cert);
95+
assertNull(ReflectionTestUtils.getField(sslHostConfig, "defaultCertificate"));
96+
customize("safkeyring:///userId/ringIdKs", null, null, "safkeyringpce:////userId/ringIdTs", null);
97+
assertSame(cert, ReflectionTestUtils.getField(sslHostConfig, "defaultCertificate"));
98+
}
99+
100+
}
101+
102+
}

caching-service-package/src/main/resources/bin/start.sh

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -117,21 +117,8 @@ key_pass="${ZWE_configs_certificate_key_password:-${ZWE_zowe_certificate_key_pas
117117
truststore_type="${ZWE_configs_certificate_truststore_type:-${ZWE_zowe_certificate_truststore_type:-PKCS12}}"
118118
truststore_pass="${ZWE_configs_certificate_truststore_password:-${ZWE_zowe_certificate_truststore_password}}"
119119

120-
121-
# Workaround for Java desiring safkeyring://// instead of just ://
122-
# We can handle both cases of user input by just adding extra "//" if we detect its missing.
123-
ensure_keyring_slashes() {
124-
keyring_string="${1}"
125-
only_two_slashes=$(echo "${keyring_string}" | grep "^safkeyring://[^//]")
126-
if [ -n "${only_two_slashes}" ]; then
127-
keyring_string=$(echo "${keyring_string}" | sed "s#safkeyring://#safkeyring:////#")
128-
fi
129-
# else, unmodified, perhaps its even p12
130-
echo $keyring_string
131-
}
132-
133-
keystore_location=$(ensure_keyring_slashes "${ZWE_configs_certificate_keystore_file:-${ZWE_zowe_certificate_keystore_file}}")
134-
truststore_location=$(ensure_keyring_slashes "${ZWE_configs_certificate_truststore_file:-${ZWE_zowe_certificate_truststore_file}}")
120+
keystore_location="${ZWE_configs_certificate_keystore_file:-${ZWE_zowe_certificate_keystore_file}}"
121+
truststore_location="${ZWE_configs_certificate_truststore_file:-${ZWE_zowe_certificate_truststore_file}}"
135122

136123
# NOTE: these are moved from below
137124
# -Dapiml.service.ipAddress=${ZOWE_IP_ADDRESS:-127.0.0.1} \

caching-service/src/main/java/org/zowe/apiml/caching/config/GeneralConfig.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@
1818
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
1919
import org.springframework.context.annotation.Bean;
2020
import org.springframework.context.annotation.Configuration;
21+
import org.springframework.context.annotation.Import;
2122
import org.zowe.apiml.product.web.ApimlTomcatCustomizer;
23+
import org.zowe.apiml.product.web.TomcatAcceptFixConfig;
24+
import org.zowe.apiml.product.web.TomcatKeyringFix;
2225

2326
@Configuration
27+
@Import({ TomcatKeyringFix.class, TomcatAcceptFixConfig.class })
2428
@Data
2529
@ToString
2630
public class GeneralConfig {

0 commit comments

Comments
 (0)