Skip to content

Commit 59a8fe9

Browse files
authored
KNOX-3263: Setting JVM's default truststore password as a System property when creating CM service discovery ApiClient, if needed (#1159)
1 parent 5961ac2 commit 59a8fe9

File tree

7 files changed

+242
-30
lines changed

7 files changed

+242
-30
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with this
4+
* work for additional information regarding copyright ownership. The ASF
5+
* licenses this file to you under the Apache License, Version 2.0 (the
6+
* "License"); you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
* <p>
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
* <p>
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations under
15+
* the License.
16+
*/
17+
package org.apache.knox.gateway.topology.discovery.cm;
18+
19+
import org.apache.knox.gateway.config.GatewayConfig;
20+
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
21+
import org.apache.knox.gateway.services.security.AliasService;
22+
import org.apache.knox.gateway.services.security.AliasServiceException;
23+
import org.apache.knox.gateway.topology.discovery.ServiceDiscoveryConfig;
24+
25+
import java.security.KeyStore;
26+
27+
public class ApiClientFactory {
28+
29+
private static final ClouderaManagerServiceDiscoveryMessages LOG = MessagesFactory.get(ClouderaManagerServiceDiscoveryMessages.class);
30+
static final String TRUSTSTORE_PASSWORD_SYSTEM_PROPERTY = "javax.net.ssl.trustStorePassword";
31+
public static final String TRUSTSTORE_PASSWORD_ALIAS = "cm.discovery.trustStorePassword";
32+
33+
public static DiscoveryApiClient getApiClient(final GatewayConfig gatewayConfig, final ServiceDiscoveryConfig discoveryConfig,
34+
final AliasService aliasService, final KeyStore truststore) {
35+
try {
36+
final char[] trustStorePassword = aliasService.getPasswordFromAliasForGateway(TRUSTSTORE_PASSWORD_ALIAS);
37+
if (trustStorePassword != null) {
38+
System.setProperty(TRUSTSTORE_PASSWORD_SYSTEM_PROPERTY, new String(trustStorePassword));
39+
}
40+
return new DiscoveryApiClient(gatewayConfig, discoveryConfig, aliasService, truststore);
41+
} catch (AliasServiceException e) {
42+
LOG.clouderaManagerApiClientBuildError(e);
43+
throw new ServiceDiscoveryException("Unable to retrieve CM service discovery truststore password", e);
44+
} finally {
45+
System.clearProperty(TRUSTSTORE_PASSWORD_SYSTEM_PROPERTY);
46+
}
47+
}
48+
}

gateway-discovery-cm/src/main/java/org/apache/knox/gateway/topology/discovery/cm/ClouderaManagerServiceDiscovery.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ private DiscoveryApiClient getClient(GatewayConfig gatewayConfig, ServiceDiscove
158158
throw new IllegalArgumentException("Missing or invalid discovery address.");
159159
}
160160

161-
DiscoveryApiClient client = new DiscoveryApiClient(gatewayConfig, discoveryConfig, aliasService, truststore);
161+
DiscoveryApiClient client = ApiClientFactory.getApiClient(gatewayConfig, discoveryConfig, aliasService, truststore);
162162
client.setDebugging(debug);
163163
return client;
164164
}

gateway-discovery-cm/src/main/java/org/apache/knox/gateway/topology/discovery/cm/ClouderaManagerServiceDiscoveryMessages.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,4 +325,7 @@ void roleConfigurationPropertyHasChanged(String propertyName,
325325
@Message(level = MessageLevel.INFO, text = "Role fetch strategy was set to {0}. Will fetch role configuration by each service with page size {1} and client base path is {2}")
326326
void usingRoleStrategyWithPageSize(String roleFetchConfigValue, long pageSize, String basePath);
327327

328+
@Message(level = MessageLevel.ERROR, text = "Error while building CM dicovery ApiClient: {0}")
329+
void clouderaManagerApiClientBuildError(@StackTrace(level = MessageLevel.DEBUG) Exception e);
330+
328331
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with this
4+
* work for additional information regarding copyright ownership. The ASF
5+
* licenses this file to you under the Apache License, Version 2.0 (the
6+
* "License"); you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
* <p>
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
* <p>
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations under
15+
* the License.
16+
*/
17+
package org.apache.knox.gateway.topology.discovery.cm;
18+
19+
public class ServiceDiscoveryException extends RuntimeException {
20+
21+
public ServiceDiscoveryException(String message, Throwable cause) {
22+
super(message, cause);
23+
}
24+
}

gateway-discovery-cm/src/main/java/org/apache/knox/gateway/topology/discovery/cm/monitor/PollingConfigurationAnalyzer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.apache.knox.gateway.services.topology.impl.GatewayStatusService;
4848
import org.apache.knox.gateway.topology.ClusterConfigurationMonitorService;
4949
import org.apache.knox.gateway.topology.discovery.ServiceDiscoveryConfig;
50+
import org.apache.knox.gateway.topology.discovery.cm.ApiClientFactory;
5051
import org.apache.knox.gateway.topology.discovery.cm.ClouderaManagerServiceDiscoveryMessages;
5152
import org.apache.knox.gateway.topology.discovery.cm.DiscoveryApiClient;
5253
import org.apache.knox.gateway.topology.discovery.cm.ServiceModelGeneratorsHolder;
@@ -475,7 +476,7 @@ private Instant getEventQueryTimestamp(final String address, final String cluste
475476
*/
476477
private DiscoveryApiClient getApiClient(final ServiceDiscoveryConfig discoveryConfig) {
477478
return clients.computeIfAbsent(discoveryConfig.getAddress(),
478-
c -> new DiscoveryApiClient(gatewayConfig, discoveryConfig, aliasService, truststore));
479+
c -> ApiClientFactory.getApiClient(gatewayConfig, discoveryConfig, aliasService, truststore));
479480
}
480481

481482
/**
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with this
4+
* work for additional information regarding copyright ownership. The ASF
5+
* licenses this file to you under the Apache License, Version 2.0 (the
6+
* "License"); you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
* <p>
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
* <p>
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations under
15+
* the License.
16+
*/
17+
package org.apache.knox.gateway.topology.discovery.cm;
18+
19+
import org.apache.knox.gateway.config.GatewayConfig;
20+
import org.apache.knox.gateway.services.security.AliasService;
21+
import org.apache.knox.gateway.services.security.AliasServiceException;
22+
import org.apache.knox.gateway.topology.discovery.ServiceDiscoveryConfig;
23+
import org.easymock.EasyMock;
24+
import org.junit.After;
25+
import org.junit.Assert;
26+
import org.junit.Before;
27+
import org.junit.Test;
28+
29+
import java.security.KeyStore;
30+
import java.util.Collections;
31+
import java.util.Properties;
32+
33+
public class ApiClientFactoryTest {
34+
35+
Properties originalProps;
36+
37+
@Before
38+
public void setUp() {
39+
originalProps = System.getProperties();
40+
}
41+
42+
@After
43+
public void tearDown() {
44+
System.setProperties(originalProps);
45+
}
46+
47+
@Test
48+
public void testSystemPropertySetWhenAliasExists() throws AliasServiceException {
49+
testGetApiClient(true);
50+
}
51+
52+
@Test
53+
public void testSystemPropertyNotSetWhenAliasNotExists() throws AliasServiceException {
54+
testGetApiClient(false);
55+
}
56+
57+
private void testGetApiClient(final boolean shouldSetSystemProperty) throws AliasServiceException {
58+
final String trustStorePassword = "changeit";
59+
final RecordingProperties testProps = new RecordingProperties(originalProps);
60+
System.setProperties(testProps);
61+
62+
final GatewayConfig gatewayConfig = EasyMock.createMock(GatewayConfig.class);
63+
EasyMock.expect(gatewayConfig.getClouderaManagerServiceDiscoveryApiVersion()).andReturn("57").anyTimes();
64+
EasyMock.expect(gatewayConfig.getClouderaManagerServiceDiscoveryConnectTimeoutMillis()).andReturn(1L).anyTimes();
65+
EasyMock.expect(gatewayConfig.getClouderaManagerServiceDiscoveryReadTimeoutMillis()).andReturn(1L).anyTimes();
66+
EasyMock.expect(gatewayConfig.getClouderaManagerServiceDiscoveryWriteTimeoutMillis()).andReturn(1L).anyTimes();
67+
EasyMock.expect(gatewayConfig.getIncludedSSLCiphers()).andReturn(Collections.emptyList()).anyTimes();
68+
EasyMock.expect(gatewayConfig.getIncludedSSLProtocols()).andReturn(Collections.emptySet()).anyTimes();
69+
final ServiceDiscoveryConfig serviceDiscoveryConfig = EasyMock.createMock(ServiceDiscoveryConfig.class);
70+
EasyMock.expect(serviceDiscoveryConfig.getAddress()).andReturn("myCmHost").anyTimes();
71+
EasyMock.expect(serviceDiscoveryConfig.getUser()).andReturn("myCmUser").anyTimes();
72+
EasyMock.expect(serviceDiscoveryConfig.getPasswordAlias()).andReturn("myCmPasswordAlias").anyTimes();
73+
final AliasService aliasService = EasyMock.createMock(AliasService.class);
74+
if (shouldSetSystemProperty) {
75+
EasyMock.expect(aliasService.getPasswordFromAliasForGateway(ApiClientFactory.TRUSTSTORE_PASSWORD_ALIAS)).andReturn(trustStorePassword.toCharArray()).anyTimes();
76+
} else {
77+
EasyMock.expect(aliasService.getPasswordFromAliasForGateway(ApiClientFactory.TRUSTSTORE_PASSWORD_ALIAS)).andReturn(null).anyTimes();
78+
}
79+
EasyMock.expect(aliasService.getPasswordFromAliasForGateway("myCmPasswordAlias")).andReturn("myCmPassword".toCharArray()).anyTimes();
80+
final KeyStore trustStore = EasyMock.createMock(KeyStore.class);
81+
82+
EasyMock.replay(aliasService, gatewayConfig, serviceDiscoveryConfig, trustStore);
83+
ApiClientFactory.getApiClient(gatewayConfig, serviceDiscoveryConfig, aliasService, trustStore);
84+
85+
if (shouldSetSystemProperty) {
86+
Assert.assertEquals(ApiClientFactory.TRUSTSTORE_PASSWORD_SYSTEM_PROPERTY, testProps.lastSetKey);
87+
Assert.assertEquals(trustStorePassword, testProps.lastSetValue);
88+
Assert.assertEquals(ApiClientFactory.TRUSTSTORE_PASSWORD_SYSTEM_PROPERTY, testProps.lastRemovedKey);
89+
} else {
90+
Assert.assertNull(testProps.lastSetKey);
91+
Assert.assertNull(testProps.lastSetValue);
92+
Assert.assertEquals(ApiClientFactory.TRUSTSTORE_PASSWORD_SYSTEM_PROPERTY, testProps.lastRemovedKey);
93+
}
94+
}
95+
96+
static class RecordingProperties extends Properties {
97+
private final Properties delegate;
98+
99+
String lastSetKey;
100+
String lastSetValue;
101+
String lastRemovedKey;
102+
103+
RecordingProperties(Properties delegate) {
104+
this.delegate = delegate;
105+
}
106+
107+
@Override
108+
public synchronized Object setProperty(String key, String value) {
109+
lastSetKey = key;
110+
lastSetValue = value;
111+
return delegate.setProperty(key, value);
112+
}
113+
114+
@Override
115+
public synchronized Object remove(Object key) {
116+
lastRemovedKey = (String) key;
117+
return delegate.remove(key);
118+
}
119+
120+
@Override
121+
public String getProperty(String key) {
122+
return delegate.getProperty(key);
123+
}
124+
}
125+
}

0 commit comments

Comments
 (0)