Extend SSL auto-configuration support to automatically locate, resolve and configure a trusted KeyStore for Apache Geode/Pivotal GemFire SSL transport between client and server based on well-defined conventions.

This commit is contained in:
John Blum
2018-05-25 15:09:35 -07:00
parent ae8566751d
commit d31b4df772
2 changed files with 224 additions and 0 deletions

View File

@@ -16,15 +16,39 @@
package org.springframework.boot.data.geode.autoconfigure;
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Optional;
import org.apache.geode.cache.client.ClientCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
import org.springframework.data.gemfire.config.annotation.EnableSsl;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
* The SslAutoConfiguration class...
@@ -37,8 +61,173 @@ import org.springframework.data.gemfire.config.annotation.EnableSsl;
@Conditional(SslAutoConfiguration.EnableSslCondition.class)
@AutoConfigureBefore(ClientCacheAutoConfiguration.class)
@EnableSsl
@SuppressWarnings("unused")
public class SslAutoConfiguration {
private static final String CURRENT_WORKING_DIRECTORY = System.getProperty("user.dir");
private static final String GEMFIRE_SSL_KEYSTORE_PROPERTY = "gemfire.ssl-keystore";
private static final String GEMFIRE_SSL_TRUSTSTORE_PROPERTY = "gemfire.ssl-truststore";
private static final String SECURITY_SSL_KEYSTORE_PROPERTY = "spring.data.gemfire.security.ssl.keystore";
private static final String SECURITY_SSL_TRUSTSTORE_PROPERTY = "spring.data.gemfire.security.ssl.truststore";
private static final String SSL_KEYSTORE_PROPERTY = "ssl-keystore";
private static final String SSL_TRUSTSTORE_PROPERTY = "ssl-truststore";
private static final String TRUSTED_KEYSTORE_FILENAME = "trusted.keystore";
private static final String USER_HOME_DIRECTORY = System.getProperty("user.home");
private static final Logger logger = LoggerFactory.getLogger(SslAutoConfiguration.class);
private static boolean isSslConfigured(Environment environment) {
return (environment.containsProperty(SECURITY_SSL_KEYSTORE_PROPERTY)
&& environment.containsProperty(SECURITY_SSL_TRUSTSTORE_PROPERTY))
|| (environment.containsProperty(GEMFIRE_SSL_KEYSTORE_PROPERTY)
&& environment.containsProperty(GEMFIRE_SSL_TRUSTSTORE_PROPERTY))
|| (environment.containsProperty(SSL_KEYSTORE_PROPERTY)
&& environment.containsProperty(SSL_TRUSTSTORE_PROPERTY));
}
private static boolean sslIsNotConfigured(Environment environment) {
return !isSslConfigured(environment);
}
private static String resolveTrustedKeyStore(Environment environment) {
return locateKeyStoreInFileSystem()
.map(File::getAbsolutePath)
.orElseGet(() -> locateKeyStoreInUserHome()
.map(File::getAbsolutePath)
.orElseGet(() -> resolveKeyStoreFromClassPathAsPathname()
.orElse(null)));
}
private static Optional<String> resolveKeyStoreFromClassPathAsPathname() {
return resolveKeyStoreFromClassPath()
.filter(File::isFile)
.map(File::getAbsolutePath)
.filter(StringUtils::hasText);
}
private static Optional<File> resolveKeyStoreFromClassPath() {
/*
System.err.printf("KEYSTORE LOCATION [%s]%n", ObjectUtils.doOperationSafely(() ->
new File(new ClassPathResource(keystoreName).getURL().toURI())).getAbsolutePath());
*/
return locateKeyStoreInClassPath().map(resource -> {
File trustedKeyStore = null;
try {
URL url = resource.getURL();
if (ResourceUtils.isFileURL(url)) {
trustedKeyStore = new File(url.toURI());
}
else if (ResourceUtils.isJarURL(url)) {
trustedKeyStore = new File(CURRENT_WORKING_DIRECTORY, TRUSTED_KEYSTORE_FILENAME);
FileCopyUtils.copy(url.openStream(), new FileOutputStream(trustedKeyStore));
}
}
catch (IOException | URISyntaxException cause) {
if (logger.isWarnEnabled()) {
logger.warn("Trusted KeyStore {} found in Class Path but is not resolvable as a File: {}",
resource, cause.getMessage());
if (logger.isTraceEnabled()) {
logger.trace("Caused by:", cause);
}
}
}
return trustedKeyStore;
});
/*
return locateKeyStoreInClassPath()
.map(it -> ObjectUtils.doOperationSafely(it::getURL))
.map(url -> ObjectUtils.doOperationSafely(url::toURI))
.map(uri -> ObjectUtils.doOperationSafely(() -> new File(uri)))
.filter(File::isFile);
*/
}
private static Optional<ClassPathResource> locateKeyStoreInClassPath() {
return locateKeyStoreInClassPath(TRUSTED_KEYSTORE_FILENAME);
}
@SuppressWarnings("all")
private static Optional<ClassPathResource> locateKeyStoreInClassPath(String keystoreName) {
return Optional.of(new ClassPathResource(keystoreName))
.filter(Resource::exists);
}
private static Optional<File> locateKeyStoreInFileSystem() {
return locateKeyStoreInFileSystem(new File(CURRENT_WORKING_DIRECTORY));
}
private static Optional<File> locateKeyStoreInFileSystem(File directory) {
return locateKeyStoreInFileSystem(directory, TRUSTED_KEYSTORE_FILENAME);
}
@SuppressWarnings("all")
private static Optional<File> locateKeyStoreInFileSystem(File directory, String keystoreFilename) {
assertDirectory(directory);
//System.err.printf("Searching [%s]...%n", directory);
for (File file : nullSafeListFiles(directory)) {
//System.err.printf("Testing [%s]...%n", file);
if (isDirectory(file)) {
Optional<File> theFile = locateKeyStoreInFileSystem(file, keystoreFilename);
if (theFile.isPresent()) {
return theFile;
}
else {
continue;
}
}
if (file.getName().equals(keystoreFilename)) {
return Optional.of(file);
}
}
return Optional.empty();
}
private static Optional<File> locateKeyStoreInUserHome() {
return locateKeyStoreInUserHome(TRUSTED_KEYSTORE_FILENAME);
}
private static Optional<File> locateKeyStoreInUserHome(String keystoreFilename) {
return Optional.of(new File(USER_HOME_DIRECTORY, keystoreFilename))
.filter(File::isFile);
}
private static void assertDirectory(File path) {
Assert.isTrue(isDirectory(path), String.format("[%s] is not a valid directory", path));
}
private static boolean isDirectory(File path) {
return path != null && path.isDirectory();
}
private static File[] nullSafeListFiles(File directory) {
return nullSafeArray(directory.listFiles(), File.class);
}
@SuppressWarnings("unused")
static class EnableSslCondition extends AnyNestedCondition {
@@ -46,6 +235,9 @@ public class SslAutoConfiguration {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@Conditional(TrustedKeyStoreIsPresentCondition.class)
static class TrustedKeyStoreCondition {}
@ConditionalOnProperty(prefix = "spring.data.gemfire.security.ssl", name = { "keystore", "truststore", })
static class SpringDataGeodeSslContextCondition {}
@@ -53,4 +245,32 @@ public class SslAutoConfiguration {
static class StandaloneApacheGeodeSslContextCondition {}
}
static class SslEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Optional.of(environment)
.filter(SslAutoConfiguration::sslIsNotConfigured)
.map(SslAutoConfiguration::resolveTrustedKeyStore)
.filter(StringUtils::hasText)
.ifPresent(trustedKeyStore -> {
System.setProperty(SECURITY_SSL_KEYSTORE_PROPERTY, trustedKeyStore);
System.setProperty(SECURITY_SSL_TRUSTSTORE_PROPERTY, trustedKeyStore);
});
}
}
static class TrustedKeyStoreIsPresentCondition implements Condition {
@Override
@SuppressWarnings("all")
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return locateKeyStoreInClassPath().isPresent()
|| locateKeyStoreInUserHome().isPresent()
|| locateKeyStoreInFileSystem().isPresent();
}
}
}

View File

@@ -7,3 +7,7 @@ org.springframework.boot.data.geode.autoconfigure.FunctionExecutionAutoConfigura
org.springframework.boot.data.geode.autoconfigure.RepositoriesAutoConfiguration,\
org.springframework.boot.data.geode.autoconfigure.SecurityAutoConfiguration,\
org.springframework.boot.data.geode.autoconfigure.SslAutoConfiguration
# Environment Post Processing
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.data.geode.autoconfigure.SslAutoConfiguration.SslEnvironmentPostProcessor