From 4da241bf20322f4ac640b76e5ce0916698ed711c Mon Sep 17 00:00:00 2001 From: John Blum Date: Fri, 25 May 2018 15:12:06 -0700 Subject: [PATCH] Add integration tests for auto-configuration of Apache Geode/Pivotal GemFire SSL transport between client and server. --- .../AutoConfiguredSslIntegrationTests.java | 252 ++++++++++++++++++ .../src/test/resources/application.properties | 3 + .../src/test/resources/trusted.keystore | Bin 0 -> 1135 bytes 3 files changed, 255 insertions(+) create mode 100644 geode-spring-boot-starter/src/test/java/org/springframework/boot/data/geode/security/ssl/AutoConfiguredSslIntegrationTests.java create mode 100644 geode-spring-boot-starter/src/test/resources/application.properties create mode 100644 geode-spring-boot-starter/src/test/resources/trusted.keystore diff --git a/geode-spring-boot-starter/src/test/java/org/springframework/boot/data/geode/security/ssl/AutoConfiguredSslIntegrationTests.java b/geode-spring-boot-starter/src/test/java/org/springframework/boot/data/geode/security/ssl/AutoConfiguredSslIntegrationTests.java new file mode 100644 index 00000000..e48a3f85 --- /dev/null +++ b/geode-spring-boot-starter/src/test/java/org/springframework/boot/data/geode/security/ssl/AutoConfiguredSslIntegrationTests.java @@ -0,0 +1,252 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.springframework.boot.data.geode.security.ssl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Optional; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.data.geode.core.util.ObjectUtils; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.data.gemfire.PartitionedRegionFactoryBean; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.config.annotation.CacheServerApplication; +import org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer; +import org.springframework.data.gemfire.config.annotation.EnableSsl; +import org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer; +import org.springframework.data.gemfire.support.ConnectionEndpoint; +import org.springframework.data.gemfire.tests.integration.ClientServerIntegrationTestsSupport; +import org.springframework.data.gemfire.tests.process.ProcessWrapper; +import org.springframework.data.gemfire.tests.util.FileSystemUtils; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import example.geode.cache.EchoCacheLoader; + +/** + * Integration tests testing the auto-configuration of Apache Geode/Pivotal GemFire SSL. + * + * @author John Blum + * @see org.junit.Test + * @see org.apache.geode.cache.GemFireCache + * @see org.springframework.boot.autoconfigure.SpringBootApplication + * @see org.springframework.boot.test.context.SpringBootTest + * @see org.springframework.core.io.Resource + * @see org.springframework.data.gemfire.config.annotation.CacheServerApplication + * @see org.springframework.data.gemfire.config.annotation.EnableSsl + * @see org.springframework.data.gemfire.tests.integration.ClientServerIntegrationTestsSupport + * @see org.springframework.data.gemfire.tests.process.ProcessWrapper + * @see org.springframework.test.context.junit4.SpringRunner + * @since 1.0.0 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + classes = AutoConfiguredSslIntegrationTests.GemFireClientConfiguration.class) +@SuppressWarnings("unused") +public class AutoConfiguredSslIntegrationTests extends ClientServerIntegrationTestsSupport { + + private static final String GEMFIRE_LOG_LEVEL = "error"; + private static final String TRUSTED_KEYSTORE_FILENAME = "trusted.keystore"; + + private static ProcessWrapper gemfireServer; + + @BeforeClass + public static void startGemFireServer() throws IOException { + + int availablePort = findAvailablePort(); + + gemfireServer = run(GemFireServerConfiguration.class, + javaxNetSslKeyStorePropertyFromClassPath(), + //javaxNetSslKeyStorePropertyFromFileSystem(), + String.format("-D%s=%d", GEMFIRE_CACHE_SERVER_PORT_PROPERTY, availablePort)); + + waitForServerToStart("localhost", availablePort); + + System.setProperty(GEMFIRE_CACHE_SERVER_PORT_PROPERTY, String.valueOf(availablePort)); + } + + private static String javaxNetSslKeyStorePropertyFromClassPath() { + + return String.format("-Djavax.net.ssl.keyStore=%s", locateKeyStoreInClassPath() + .filter(StringUtils::hasText) + .orElseThrow(() -> newIllegalStateException("KeyStore file [%s] was not found", + TRUSTED_KEYSTORE_FILENAME))); + } + + private static String javaxNetSslKeyStorePropertyFromFileSystem() { + + return String.format("-Djavax.net.ssl.keyStore=%s", locateKeyStoreInFileSystem() + .map(File::getAbsolutePath) + .filter(StringUtils::hasText) + .orElseThrow(() -> newIllegalStateException("KeyStore file [%s] was not found", + TRUSTED_KEYSTORE_FILENAME))); + } + + private static Optional locateKeyStoreInClassPath() { + return locateKeyStoreInClassPath(TRUSTED_KEYSTORE_FILENAME); + } + + @SuppressWarnings("all") + private static Optional locateKeyStoreInClassPath(String keystoreName) { + + /* + System.err.printf("KEYSTORE LOCATION [%s]%n", ObjectUtils.doOperationSafely(() -> + new File(new ClassPathResource(keystoreName).getURL().toURI())).getAbsolutePath()); + */ + + return Optional.of(new ClassPathResource(keystoreName)) + .filter(Resource::exists) + .map(it -> ObjectUtils.doOperationSafely(() -> it.getURL())) + .map(url -> ObjectUtils.doOperationSafely(() -> url.toURI())) + .map(uri -> ObjectUtils.doOperationSafely(() -> new File(uri))) + .filter(File::isFile) + .map(File::getAbsolutePath); + } + + private static Optional locateKeyStoreInFileSystem() { + return locateKeyStoreInFileSystem(FileSystemUtils.WORKING_DIRECTORY); + } + + private static Optional locateKeyStoreInFileSystem(File directory) { + return locateKeyStoreInFileSystem(directory, TRUSTED_KEYSTORE_FILENAME); + } + + @SuppressWarnings("all") + private static Optional locateKeyStoreInFileSystem(File directory, String keystoreFilename) { + + Assert.isTrue(directory != null && directory.isDirectory(), + String.format("[%s] is not a valid directory", directory)); + + //System.err.printf("Searching [%s]...%n", directory); + + for (File file : nullSafeArray(directory.listFiles(), File.class)) { + + //System.err.printf("Testing [%s]...%n", file); + + if (file.isDirectory()) { + + Optional theFile = locateKeyStoreInFileSystem(file, keystoreFilename); + + if (theFile.isPresent()) { + return theFile; + } + else { + continue; + } + } + + if (file.getName().equals(keystoreFilename)) { + return Optional.of(file); + } + } + + return Optional.empty(); + } + + @AfterClass + public static void stopGemFireServer() { + System.clearProperty(GEMFIRE_CACHE_SERVER_PORT_PROPERTY); + stop(gemfireServer); + } + + @javax.annotation.Resource(name = "Echo") + private Region echo; + + @Test + public void clientServerCommunicationsSuccessful() { + + assertThat(this.echo).isNotNull(); + assertThat(this.echo.get("Hello")).isEqualTo("Hello"); + assertThat(this.echo.get("Test")).isEqualTo("Test"); + assertThat(this.echo.get("Good-Bye")).isEqualTo("Good-Bye"); + } + + @SpringBootApplication + static class GemFireClientConfiguration { + + @Bean + ClientCacheConfigurer clientCachePoolPortConfigurer( + @Value("${" + GEMFIRE_CACHE_SERVER_PORT_PROPERTY + ":40404}") int port) { + + return (bean, clientCacheFactoryBean) -> clientCacheFactoryBean.setServers( + Collections.singletonList(new ConnectionEndpoint("localhost", port))); + } + + @Bean("Echo") + public ClientRegionFactoryBean echoRegion(GemFireCache gemfireCache) { + + ClientRegionFactoryBean echoRegion = new ClientRegionFactoryBean<>(); + + echoRegion.setCache(gemfireCache); + echoRegion.setClose(false); + echoRegion.setShortcut(ClientRegionShortcut.PROXY); + + return echoRegion; + } + } + + @EnableSsl + @CacheServerApplication(name = "AutoConfiguredSslIntegrationTests", logLevel = GEMFIRE_LOG_LEVEL) + static class GemFireServerConfiguration { + + public static void main(String[] args) { + SpringApplication.run(GemFireServerConfiguration.class, args); + } + + @Bean + PeerCacheConfigurer cacheServerSslConfigurer( + @Value("${javax.net.ssl.keyStore:trusted.keystore}") String keystoreLocation) { + + return (beanName, bean) -> { + bean.getProperties().setProperty("ssl-keystore", keystoreLocation); + bean.getProperties().setProperty("ssl-truststore", keystoreLocation); + }; + } + + @Bean("Echo") + public PartitionedRegionFactoryBean echoRegion(GemFireCache gemfireCache) { + + PartitionedRegionFactoryBean echoRegion = new PartitionedRegionFactoryBean<>(); + + echoRegion.setCache(gemfireCache); + echoRegion.setCacheLoader(EchoCacheLoader.INSTANCE); + echoRegion.setClose(false); + echoRegion.setPersistent(false); + + return echoRegion; + } + } +} diff --git a/geode-spring-boot-starter/src/test/resources/application.properties b/geode-spring-boot-starter/src/test/resources/application.properties new file mode 100644 index 00000000..0cbede81 --- /dev/null +++ b/geode-spring-boot-starter/src/test/resources/application.properties @@ -0,0 +1,3 @@ +# Spring Boot application.properties for testing +spring.data.gemfire.security.ssl.keystore.password=s3cr3t +spring.data.gemfire.security.ssl.truststore.password=s3cr3t diff --git a/geode-spring-boot-starter/src/test/resources/trusted.keystore b/geode-spring-boot-starter/src/test/resources/trusted.keystore new file mode 100644 index 0000000000000000000000000000000000000000..c6affdb67a8ce3dab6f3d3f4a4e7dafca8a4fa26 GIT binary patch literal 1135 zcmezO_TO6u1_mY|W&~sQ^wiw6%%W5v-!FA<`!XQiZ_vcpZNSIIrOn33!l=b0$jHdb zz|zE6-nL5f&w~CJj2tE*YzI;%YwN6Vzv?vQ=kNI3^^f^pJVcalOn3UX{R__8ax zjXEFqKdU%xcmL{<vLB5itcAK3QCWMl~he;dr@YUwEB?3iW`1Ays#CwCU(xOy=wBp{p?hG_NCSv z?e#zZ=q9kv1_$?oU>qIW@RvtF_bcpU}FwtVHOt5O3EqC)y+*RD9YE% z$xlwqF_07IH8eFaH!w3YHnuc1ivn`ZfLv23mw@gj#_hn=p#!sy5y+8eYHVcqTOZsg zTPma(bl+=^-kT@SzX@&+vU%^qa9Ga1VOyYDrfaN&QsD1M+un#@`;UIzW$^9v?9iO+ zjn)maCi$Pz61Hd03|P8k{`?m#)dEU~{CBUs{%`v+=^xhN&yTHGCd$w9yZPJYqjHCt zOhbgD#3mYjEB(AqJMp^K`%g;Dl5;P5FdkKL;f;X5m*tl$01>Q&Rva35}4_NMf}$nd^hS0eqN6|xI7Hf-?} zD^z~rqah}|{XC~p&xvIZZN#d&Luz_89xPrbr@eVu>BPLn_1wP{O_M(UPO4&5u|Bip zQL1bMjz{c=RLKU8(SEd8XG+Qt{8LKuD`|*XVoaN-S8Q#*d6Uc8^_eL* zP?8a|K7)Y{lZcB0TTj#XjaPNW|9Nn!q`ePGNV~!$qP6hyf75>lZ@)UkQ0>L_eWHl& bBwv%H2W{*&j7^^tZoK{==6*4#Ufl`+`B>v< literal 0 HcmV?d00001