Skip to content

Commit 0e5edc3

Browse files
authored
GEODE-10562: Testcases for Hybrid CA TLS Configuration Test Suite (#7988)
* GEODE-10562 : Testcases — Hybrid Model (Public CA servers, Private CA clients) * GEODE-10562 : Testcases — Hybrid Model (Public CA servers, Private CA clients) * Add sun.security.util exports for CertificateBuilder - Export sun.security.util package alongside sun.security.x509 - Required for ObjectIdentifier import in CertificateBuilder.java - Added to both compileJava and javadoc tasks for Java 17 compatibility * javadoc
1 parent 2b83c0a commit 0e5edc3

5 files changed

Lines changed: 984 additions & 3 deletions

File tree

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
3+
* agreements. See the NOTICE file distributed with this work for additional information regarding
4+
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
5+
* "License"); you may not use this file except in compliance with the License. You may obtain a
6+
* copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software distributed under the License
11+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12+
* or implied. See the License for the specific language governing permissions and limitations under
13+
* the License.
14+
*/
15+
package org.apache.geode.cache.ssl;
16+
17+
import static org.apache.geode.distributed.ConfigurationProperties.SSL_ENABLED_COMPONENTS;
18+
import static org.apache.geode.distributed.ConfigurationProperties.SSL_ENDPOINT_IDENTIFICATION_ENABLED;
19+
import static org.apache.geode.security.SecurableCommunicationChannels.CLUSTER;
20+
import static org.apache.geode.security.SecurableCommunicationChannels.SERVER;
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
23+
24+
import java.util.Properties;
25+
26+
import org.junit.Before;
27+
import org.junit.Rule;
28+
import org.junit.Test;
29+
import org.junit.experimental.categories.Category;
30+
31+
import org.apache.geode.cache.Region;
32+
import org.apache.geode.cache.RegionShortcut;
33+
import org.apache.geode.cache.client.ClientRegionShortcut;
34+
import org.apache.geode.test.dunit.IgnoredException;
35+
import org.apache.geode.test.dunit.rules.ClientVM;
36+
import org.apache.geode.test.dunit.rules.ClusterStartupRule;
37+
import org.apache.geode.test.dunit.rules.MemberVM;
38+
import org.apache.geode.test.junit.categories.ClientServerTest;
39+
40+
/**
41+
* Tests hybrid TLS configuration where:
42+
* - Servers use certificates issued by a public CA (e.g., Let's Encrypt, DigiCert)
43+
* - Clients use certificates issued by a private/enterprise CA
44+
*
45+
* This configuration mitigates the impact of public CA changes that affect the
46+
* Client Authentication Extended Key Usage (EKU). See the Apache Geode security
47+
* documentation for details.
48+
*
49+
* Key requirements validated:
50+
* - Server certificates must include serverAuth EKU and subjectAltName
51+
* - Client certificates must include clientAuth EKU
52+
* - Servers trust the private CA to validate client certificates
53+
* - Clients trust the public CA to validate server certificates
54+
*/
55+
@Category({ClientServerTest.class})
56+
public class HybridCASSLDUnitTest {
57+
58+
private HybridCATestFixture fixture;
59+
60+
@Rule
61+
public ClusterStartupRule cluster = new ClusterStartupRule();
62+
63+
@Before
64+
public void setup() {
65+
fixture = new HybridCATestFixture();
66+
fixture.setup();
67+
68+
// Ignore expected exceptions during locator/server shutdown with SSL
69+
IgnoredException.addIgnoredException("Could not stop Locator");
70+
IgnoredException.addIgnoredException("ForcedDisconnectException");
71+
}
72+
73+
/**
74+
* Tests basic client-server connection with hybrid TLS.
75+
* Verifies that a client with a private-CA certificate can connect to
76+
* a server with a public-CA certificate when trust is properly configured.
77+
*
78+
* Note: This test uses SSL only for client-server communication (SERVER component),
79+
* not for peer-to-peer cluster communication, which simplifies the test setup.
80+
*/
81+
@Test
82+
public void testHybridTLSBasicConnection() throws Exception {
83+
// Start locator without SSL (peer-to-peer communication doesn't require SSL for this test)
84+
MemberVM locator = cluster.startLocatorVM(0);
85+
86+
// Create server with public-CA certificate, SSL enabled only for SERVER component
87+
CertStores serverStore = fixture.createServerStores("1");
88+
Properties serverProps = serverStore.propertiesWith(SERVER, true, true);
89+
serverProps.setProperty(SSL_ENABLED_COMPONENTS, SERVER);
90+
91+
MemberVM server = cluster.startServerVM(1, serverProps, locator.getPort());
92+
93+
// Create region on server
94+
server.invoke(() -> {
95+
ClusterStartupRule.getCache()
96+
.createRegionFactory(RegionShortcut.REPLICATE)
97+
.create("testRegion");
98+
});
99+
100+
// Create client with private-CA certificate
101+
CertStores clientStore = fixture.createClientStores("1");
102+
Properties clientProps = clientStore.propertiesWith(SERVER, true, true);
103+
clientProps.setProperty(SSL_ENABLED_COMPONENTS, SERVER);
104+
105+
ClientVM client = cluster.startClientVM(2, clientProps,
106+
ccf -> ccf.addPoolLocator("localhost", locator.getPort()));
107+
108+
// Verify client can perform operations
109+
client.invoke(() -> {
110+
Region<Object, Object> clientRegion = ClusterStartupRule.getClientCache()
111+
.createClientRegionFactory(ClientRegionShortcut.PROXY)
112+
.create("testRegion");
113+
114+
clientRegion.put("key1", "value1");
115+
assertThat(clientRegion.get("key1")).isEqualTo("value1");
116+
});
117+
}
118+
119+
/**
120+
* Tests that client authentication is properly enforced in hybrid TLS.
121+
* A client without a certificate should be rejected.
122+
*/
123+
@Test
124+
public void testHybridTLSClientAuthenticationRequired() throws Exception {
125+
CertStores serverStore = fixture.createServerStores("1");
126+
Properties serverProps = serverStore.propertiesWith(SERVER, true, true);
127+
serverProps.setProperty(SSL_ENABLED_COMPONENTS, SERVER);
128+
129+
MemberVM locator = cluster.startLocatorVM(0);
130+
MemberVM server = cluster.startServerVM(1, serverProps, locator.getPort());
131+
132+
server.invoke(() -> {
133+
ClusterStartupRule.getCache()
134+
.createRegionFactory(RegionShortcut.REPLICATE)
135+
.create("testRegion");
136+
});
137+
138+
// Create client with only truststore (no keystore with client certificate)
139+
CertStores clientStore = CertStores.clientStore();
140+
clientStore.trust("publicCA", fixture.getPublicCA());
141+
Properties clientProps = clientStore.propertiesWith(SERVER, true, true);
142+
clientProps.setProperty(SSL_ENABLED_COMPONENTS, SERVER);
143+
144+
IgnoredException.addIgnoredException("javax.net.ssl.SSLHandshakeException");
145+
IgnoredException.addIgnoredException("java.io.IOException");
146+
IgnoredException.addIgnoredException("Broken pipe");
147+
148+
// Attempt to create client VM should fail
149+
assertThatThrownBy(() -> {
150+
cluster.startClientVM(2, clientProps,
151+
ccf -> ccf.addPoolLocator("localhost", locator.getPort()));
152+
}).hasCauseInstanceOf(javax.net.ssl.SSLHandshakeException.class);
153+
154+
IgnoredException.removeAllExpectedExceptions();
155+
}
156+
157+
/**
158+
* Tests endpoint identification (hostname verification) with hybrid TLS.
159+
* Verifies that the server certificate's subjectAltName is validated.
160+
*/
161+
@Test
162+
public void testHybridTLSWithEndpointIdentification() throws Exception {
163+
CertStores serverStore = fixture.createServerStores("1");
164+
Properties serverProps = serverStore.propertiesWith(SERVER, true, true);
165+
serverProps.setProperty(SSL_ENABLED_COMPONENTS, SERVER);
166+
serverProps.setProperty(SSL_ENDPOINT_IDENTIFICATION_ENABLED, "true");
167+
168+
MemberVM locator = cluster.startLocatorVM(0, serverProps);
169+
MemberVM server = cluster.startServerVM(1, serverProps, locator.getPort());
170+
171+
server.invoke(() -> {
172+
ClusterStartupRule.getCache()
173+
.createRegionFactory(RegionShortcut.REPLICATE)
174+
.create("testRegion");
175+
});
176+
177+
CertStores clientStore = fixture.createClientStores("1");
178+
Properties clientProps = clientStore.propertiesWith(SERVER, true, true);
179+
clientProps.setProperty(SSL_ENABLED_COMPONENTS, SERVER);
180+
clientProps.setProperty(SSL_ENDPOINT_IDENTIFICATION_ENABLED, "true");
181+
182+
ClientVM client = cluster.startClientVM(2, clientProps,
183+
ccf -> ccf.addPoolLocator("localhost", locator.getPort()));
184+
185+
client.invoke(() -> {
186+
Region<Object, Object> clientRegion = ClusterStartupRule.getClientCache()
187+
.createClientRegionFactory(ClientRegionShortcut.PROXY)
188+
.create("testRegion");
189+
190+
clientRegion.put("key1", "value1");
191+
assertThat(clientRegion.get("key1")).isEqualTo("value1");
192+
});
193+
}
194+
195+
/**
196+
* Tests that peer-to-peer SSL works with hybrid TLS.
197+
* Multiple servers should be able to communicate using public-CA certificates.
198+
*/
199+
@Test
200+
public void testHybridTLSPeerToPeerCommunication() throws Exception {
201+
CertStores locatorStore = fixture.createLocatorStores("1");
202+
Properties locatorProps = locatorStore.propertiesWith(CLUSTER, false, true);
203+
204+
MemberVM locator = cluster.startLocatorVM(0, locatorProps);
205+
206+
// Start two servers
207+
CertStores serverStore1 = fixture.createServerStores("1");
208+
Properties serverProps1 = serverStore1.propertiesWith(CLUSTER, true, true);
209+
serverProps1.setProperty(SSL_ENABLED_COMPONENTS, CLUSTER);
210+
MemberVM server1 = cluster.startServerVM(1, serverProps1, locator.getPort());
211+
212+
CertStores serverStore2 = fixture.createServerStores("2");
213+
Properties serverProps2 = serverStore2.propertiesWith(CLUSTER, true, true);
214+
serverProps2.setProperty(SSL_ENABLED_COMPONENTS, CLUSTER);
215+
MemberVM server2 = cluster.startServerVM(2, serverProps2, locator.getPort());
216+
217+
// Create replicated region on both servers
218+
server1.invoke(() -> {
219+
ClusterStartupRule.getCache()
220+
.createRegionFactory(RegionShortcut.REPLICATE)
221+
.create("testRegion");
222+
});
223+
224+
server2.invoke(() -> {
225+
ClusterStartupRule.getCache()
226+
.createRegionFactory(RegionShortcut.REPLICATE)
227+
.create("testRegion");
228+
});
229+
230+
// Put data from server1
231+
server1.invoke(() -> {
232+
Region<String, String> region = ClusterStartupRule.getCache().getRegion("testRegion");
233+
region.put("key1", "value1");
234+
});
235+
236+
// Verify data replicated to server2
237+
server2.invoke(() -> {
238+
Region<String, String> region = ClusterStartupRule.getCache().getRegion("testRegion");
239+
assertThat(region.get("key1")).isEqualTo("value1");
240+
});
241+
}
242+
243+
/**
244+
* Tests that clients can connect when only SERVER component has SSL enabled.
245+
* This validates component-specific SSL configuration.
246+
*/
247+
@Test
248+
public void testHybridTLSServerComponentOnly() throws Exception {
249+
// Server uses hybrid TLS only for SERVER component
250+
CertStores serverStore = fixture.createServerStores("1");
251+
Properties serverProps = serverStore.propertiesWith(SERVER, true, true);
252+
// Locator doesn't need SSL
253+
serverProps.setProperty(SSL_ENABLED_COMPONENTS, SERVER);
254+
255+
MemberVM locator = cluster.startLocatorVM(0);
256+
MemberVM server = cluster.startServerVM(1, serverProps, locator.getPort());
257+
258+
server.invoke(() -> {
259+
ClusterStartupRule.getCache()
260+
.createRegionFactory(RegionShortcut.REPLICATE)
261+
.create("testRegion");
262+
});
263+
264+
// Client uses hybrid TLS for SERVER component
265+
CertStores clientStore = fixture.createClientStores("1");
266+
Properties clientProps = clientStore.propertiesWith(SERVER, true, true);
267+
clientProps.setProperty(SSL_ENABLED_COMPONENTS, SERVER);
268+
269+
ClientVM client = cluster.startClientVM(2, clientProps,
270+
ccf -> ccf.addPoolLocator("localhost", locator.getPort()));
271+
272+
client.invoke(() -> {
273+
Region<Object, Object> clientRegion = ClusterStartupRule.getClientCache()
274+
.createClientRegionFactory(ClientRegionShortcut.PROXY)
275+
.create("testRegion");
276+
277+
clientRegion.put("key1", "value1");
278+
assertThat(clientRegion.get("key1")).isEqualTo("value1");
279+
});
280+
}
281+
282+
/**
283+
* Tests multiple clients connecting with different private-CA certificates.
284+
* Validates that each client can authenticate independently.
285+
*/
286+
@Test
287+
public void testHybridTLSMultipleClients() throws Exception {
288+
CertStores serverStore = fixture.createServerStores("1");
289+
Properties serverProps = serverStore.propertiesWith(SERVER, true, true);
290+
serverProps.setProperty(SSL_ENABLED_COMPONENTS, SERVER);
291+
292+
MemberVM locator = cluster.startLocatorVM(0);
293+
MemberVM server = cluster.startServerVM(1, serverProps, locator.getPort());
294+
295+
server.invoke(() -> {
296+
ClusterStartupRule.getCache()
297+
.createRegionFactory(RegionShortcut.REPLICATE)
298+
.create("testRegion");
299+
});
300+
301+
// Create first client
302+
CertStores clientStore1 = fixture.createClientStores("client1");
303+
Properties clientProps1 = clientStore1.propertiesWith(SERVER, true, true);
304+
clientProps1.setProperty(SSL_ENABLED_COMPONENTS, SERVER);
305+
306+
ClientVM client1 = cluster.startClientVM(2, clientProps1,
307+
ccf -> ccf.addPoolLocator("localhost", locator.getPort()));
308+
309+
client1.invoke(() -> {
310+
Region<Object, Object> clientRegion1 = ClusterStartupRule.getClientCache()
311+
.createClientRegionFactory(ClientRegionShortcut.PROXY)
312+
.create("testRegion");
313+
314+
clientRegion1.put("client1-key", "client1-value");
315+
});
316+
317+
// Create second client with different certificate
318+
CertStores clientStore2 = fixture.createClientStores("client2");
319+
Properties clientProps2 = clientStore2.propertiesWith(SERVER, true, true);
320+
clientProps2.setProperty(SSL_ENABLED_COMPONENTS, SERVER);
321+
322+
ClientVM client2 = cluster.startClientVM(3, clientProps2,
323+
ccf -> ccf.addPoolLocator("localhost", locator.getPort()));
324+
325+
client2.invoke(() -> {
326+
Region<Object, Object> clientRegion2 = ClusterStartupRule.getClientCache()
327+
.createClientRegionFactory(ClientRegionShortcut.PROXY)
328+
.create("testRegion");
329+
330+
// Verify second client can see first client's data
331+
assertThat(clientRegion2.get("client1-key")).isEqualTo("client1-value");
332+
333+
// Verify second client can put its own data
334+
clientRegion2.put("client2-key", "client2-value");
335+
});
336+
337+
// Verify first client can see second client's data
338+
client1.invoke(() -> {
339+
Region<Object, Object> clientRegion1 =
340+
ClusterStartupRule.getClientCache().getRegion("testRegion");
341+
assertThat(clientRegion1.get("client2-key")).isEqualTo("client2-value");
342+
});
343+
}
344+
}

0 commit comments

Comments
 (0)