From f4feb248ea473d13d2fe604684f9d90e50aebde4 Mon Sep 17 00:00:00 2001 From: John Blum Date: Fri, 28 Jun 2019 18:50:00 -0700 Subject: [PATCH] Add functionality to target the configuration of a specific CloudCache Service Instance in PCF. Introduces the new 'spring.boot.data.gemfire.cloud.cloudfoundry.service.cloudcache.name' property enabling users to target a specific CloudCache Service Instance in PCF. Fails fast and throws an IllegalStateException if the target CloudCache Service Instance cannot be found. Logs warning when no CloudCache Service Instance is found. However, client security auto-configuration will no longer require a CloudCache Service Instance when deploying Spring Boot, GemFire ClientCache app to PCF. This enables users to deploy Spring Boot, ClientCache apps to PCF and connect them to external Apache Geode or Pivotal GemFire clusters. Resolves gh-33. --- .../ClientSecurityAutoConfiguration.java | 67 +++++++++----- ...entSecurityAutoConfigurationUnitTests.java | 92 ++++++++++++++++++- 2 files changed, 136 insertions(+), 23 deletions(-) diff --git a/spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/ClientSecurityAutoConfiguration.java b/spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/ClientSecurityAutoConfiguration.java index 2580b119..005c0e80 100644 --- a/spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/ClientSecurityAutoConfiguration.java +++ b/spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/ClientSecurityAutoConfiguration.java @@ -15,6 +15,8 @@ */ package org.springframework.geode.boot.autoconfigure; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; + import java.util.Optional; import java.util.Properties; @@ -42,6 +44,7 @@ import org.springframework.data.gemfire.config.annotation.support.AutoConfigured import org.springframework.geode.core.env.VcapPropertySource; import org.springframework.geode.core.env.support.CloudCacheService; import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,18 +59,18 @@ import org.slf4j.LoggerFactory; * @see org.apache.geode.cache.client.ClientCache * @see org.springframework.boot.SpringApplication * @see org.springframework.boot.autoconfigure.EnableAutoConfiguration + * @see org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform * @see org.springframework.boot.cloud.CloudPlatform * @see org.springframework.boot.env.EnvironmentPostProcessor + * @see org.springframework.context.annotation.Conditional * @see org.springframework.context.annotation.Configuration - * @see org.springframework.context.annotation.Import * @see org.springframework.core.env.ConfigurableEnvironment * @see org.springframework.core.env.Environment - * @see org.springframework.core.env.PropertiesPropertySource + * @see org.springframework.core.env.PropertySource * @see org.springframework.data.gemfire.client.ClientCacheFactoryBean * @see org.springframework.data.gemfire.config.annotation.EnableSecurity * @see org.springframework.data.gemfire.config.annotation.support.AutoConfiguredAuthenticationInitializer * @see org.springframework.geode.boot.autoconfigure.ClientCacheAutoConfiguration - * @see org.springframework.geode.boot.autoconfigure.HttpBasicAuthenticationSecurityConfiguration * @see org.springframework.geode.core.env.VcapPropertySource * @see org.springframework.geode.core.env.support.CloudCacheService * @see org.springframework.geode.core.env.support.Service @@ -83,6 +86,9 @@ import org.slf4j.LoggerFactory; @SuppressWarnings("unused") public class ClientSecurityAutoConfiguration { + public static final String CLOUD_CACHE_SERVICE_INSTANCE_NAME_PROPERTY = + "spring.boot.data.gemfire.cloud.cloudfoundry.service.cloudcache.name"; + public static final String CLOUD_SECURITY_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY = "spring.boot.data.gemfire.security.auth.environment.post-processor.enabled"; @@ -116,7 +122,7 @@ public class ClientSecurityAutoConfiguration { } private boolean isCloudFoundryEnvironment(Environment environment) { - return environment != null && CloudPlatform.CLOUD_FOUNDRY.isActive(environment); + return CloudPlatform.CLOUD_FOUNDRY.isActive(environment); } private boolean isEnabled(Environment environment) { @@ -145,8 +151,8 @@ public class ClientSecurityAutoConfiguration { return !isSecurityPropertiesSet(environment); } - private void configureAuthentication(Environment environment, Properties cloudCacheProperties, - VcapPropertySource vcapPropertySource, CloudCacheService cloudCacheService) { + private void configureAuthentication(Environment environment, VcapPropertySource vcapPropertySource, + CloudCacheService cloudCacheService, Properties cloudCacheProperties) { vcapPropertySource.findFirstUserByRoleClusterOperator(cloudCacheService) .filter(user -> isSecurityPropertiesNotSet(environment)) @@ -159,15 +165,15 @@ public class ClientSecurityAutoConfiguration { }); } - private void configureLocators(Environment environment, Properties cloudCacheProperties, - VcapPropertySource vcapPropertySource, CloudCacheService cloudCacheService) { + private void configureLocators(Environment environment, VcapPropertySource vcapPropertySource, + CloudCacheService cloudCacheService, Properties cloudCacheProperties) { cloudCacheService.getLocators().ifPresent(locators -> cloudCacheProperties.setProperty(POOL_LOCATORS_PROPERTY, locators)); } - private void configureManagementRestApiAccess(Environment environment, Properties cloudCacheProperties, - VcapPropertySource vcapPropertySource, CloudCacheService cloudCacheService) { + private void configureManagementRestApiAccess(Environment environment, VcapPropertySource vcapPropertySource, + CloudCacheService cloudCacheService, Properties cloudCacheProperties) { cloudCacheService.getGfshUrl().ifPresent(url -> { cloudCacheProperties.setProperty(MANAGEMENT_USE_HTTP_PROPERTY, Boolean.TRUE.toString()); @@ -178,22 +184,41 @@ public class ClientSecurityAutoConfiguration { public void configureSecurityContext(ConfigurableEnvironment environment) { - VcapPropertySource vcapPropertySource = toVcapPropertySource(environment); + String cloudcacheServiceInstanceName = environment.getProperty(CLOUD_CACHE_SERVICE_INSTANCE_NAME_PROPERTY); - Properties cloudCacheProperties = new Properties(); + VcapPropertySource vcapPropertySource = StringUtils.hasText(cloudcacheServiceInstanceName) + ? toVcapPropertySource(environment).withVcapServiceName(cloudcacheServiceInstanceName) + : toVcapPropertySource(environment); - CloudCacheService cloudCacheService = vcapPropertySource.findFirstCloudCacheService(); + vcapPropertySource.findFirstCloudCacheService() + .map(cloudCacheService -> { - configureAuthentication(environment, cloudCacheProperties, vcapPropertySource, cloudCacheService); - configureLocators(environment, cloudCacheProperties, vcapPropertySource, cloudCacheService); - configureManagementRestApiAccess(environment, cloudCacheProperties, vcapPropertySource, cloudCacheService); + Properties cloudCacheProperties = new Properties(); - environment.getPropertySources() - .addLast(newPropertySource(CLOUD_CACHE_PROPERTY_SOURCE_NAME, cloudCacheProperties)); + configureAuthentication(environment, vcapPropertySource, cloudCacheService, cloudCacheProperties); + configureLocators(environment, vcapPropertySource, cloudCacheService, cloudCacheProperties); + configureManagementRestApiAccess(environment, vcapPropertySource, cloudCacheService, cloudCacheProperties); + + environment.getPropertySources() + .addLast(newPropertySource(CLOUD_CACHE_PROPERTY_SOURCE_NAME, cloudCacheProperties)); + + return cloudCacheService; + }) + .orElseGet(() -> { + + if (StringUtils.hasText(cloudcacheServiceInstanceName)) { + throw newIllegalStateException("No CloudCache Service Instance with name [%s] was found", + cloudcacheServiceInstanceName); + } + else { + logger.warn("No CloudCache Service Instance was found"); + } + + return null; + }); } private PropertySource newPropertySource(String name, Properties properties) { - //return new PropertiesPropertySource(name, properties); return new SpringDataGemFirePropertiesPropertySource(name, properties); } @@ -241,12 +266,12 @@ public class ClientSecurityAutoConfiguration { super(name, springDataGemFireProperties); } - @Nullable @Override @SuppressWarnings("all") + @Nullable @Override public Object getProperty(String name) { return getSource().getProperty(name); } - @Override @SuppressWarnings("all") + @Override public boolean containsProperty(String name) { return getSource().containsKey(name); } diff --git a/spring-geode-autoconfigure/src/test/java/org/springframework/geode/boot/autoconfigure/security/auth/ClientSecurityAutoConfigurationUnitTests.java b/spring-geode-autoconfigure/src/test/java/org/springframework/geode/boot/autoconfigure/security/auth/ClientSecurityAutoConfigurationUnitTests.java index 8f019c25..f1ac28eb 100644 --- a/spring-geode-autoconfigure/src/test/java/org/springframework/geode/boot/autoconfigure/security/auth/ClientSecurityAutoConfigurationUnitTests.java +++ b/spring-geode-autoconfigure/src/test/java/org/springframework/geode/boot/autoconfigure/security/auth/ClientSecurityAutoConfigurationUnitTests.java @@ -17,6 +17,7 @@ package org.springframework.geode.boot.autoconfigure.security.auth; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -35,6 +36,7 @@ import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertySource; +import org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender; import org.springframework.geode.boot.autoconfigure.ClientSecurityAutoConfiguration; import org.springframework.geode.boot.autoconfigure.ClientSecurityAutoConfiguration.AutoConfiguredCloudSecurityEnvironmentPostProcessor; import org.springframework.mock.env.MockEnvironment; @@ -43,10 +45,16 @@ import org.springframework.mock.env.MockEnvironment; * Unit Tests for {@link ClientSecurityAutoConfiguration}. * * @author John Blum + * @see java.util.Properties * @see org.junit.Test * @see org.mockito.Mockito * @see org.springframework.core.env.ConfigurableEnvironment + * @see org.springframework.core.env.MutablePropertySources + * @see org.springframework.core.env.PropertiesPropertySource + * @see org.springframework.core.env.PropertySource + * @see org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender * @see org.springframework.geode.boot.autoconfigure.ClientSecurityAutoConfiguration + * @see org.springframework.mock.env.MockEnvironment * @since 1.0.0 */ public class ClientSecurityAutoConfigurationUnitTests { @@ -159,7 +167,6 @@ public class ClientSecurityAutoConfigurationUnitTests { } @Test - @SuppressWarnings("unchecked") public void configuresSecurityContext() { ConfigurableEnvironment mockEnvironment = mock(ConfigurableEnvironment.class); @@ -210,7 +217,6 @@ public class ClientSecurityAutoConfigurationUnitTests { } @Test - @SuppressWarnings("unchecked") public void configuresSecurityContextWithLocatorsOnly() { ConfigurableEnvironment mockEnvironment = mock(ConfigurableEnvironment.class); @@ -261,4 +267,86 @@ public class ClientSecurityAutoConfigurationUnitTests { assertThat(propertySource.containsProperty("spring.data.gemfire.management.http.host")).isFalse(); assertThat(propertySource.containsProperty("spring.data.gemfire.management.http.port")).isFalse(); } + + @Test + public void configureSecurityContextWhenNoCloudCacheServiceInstanceIsFoundLogsWarning() { + + ConfigurableEnvironment mockEnvironment = mock(ConfigurableEnvironment.class); + + MutablePropertySources propertySources = spy(new MutablePropertySources()); + + Properties vcap = new Properties(); + + vcap.setProperty("vcap.application.name", "TestApp"); + vcap.setProperty("vcap.application.uris", "test-app.apps.cloud.skullbox.com"); + + PropertySource vcapPropertySource = new PropertiesPropertySource("vcap", vcap); + + propertySources.addFirst(vcapPropertySource); + + when(mockEnvironment.getPropertySources()).thenReturn(propertySources); + when(mockEnvironment.getProperty(anyString())).thenReturn(null); + + AutoConfiguredCloudSecurityEnvironmentPostProcessor environmentPostProcessor = + new AutoConfiguredCloudSecurityEnvironmentPostProcessor(); + + try { + + environmentPostProcessor.configureSecurityContext(mockEnvironment); + + TestAppender testAppender = TestAppender.getInstance(); + + assertThat(testAppender).isNotNull(); + assertThat(testAppender.lastLogMessage()).isEqualTo("No CloudCache Service Instance was found"); + } + finally { + + verify(mockEnvironment, times(1)) + .getProperty(eq(ClientSecurityAutoConfiguration.CLOUD_CACHE_SERVICE_INSTANCE_NAME_PROPERTY)); + verify(mockEnvironment, times(1)).getPropertySources(); + verify(propertySources, never()).addLast(any(PropertySource.class)); + } + } + + @Test(expected = IllegalStateException.class) + public void configureSecurityContextWhenTargetCloudCacheServiceInstanceIsNotFoundThrowsIllegalStateException() { + + ConfigurableEnvironment mockEnvironment = mock(ConfigurableEnvironment.class); + + MutablePropertySources propertySources = spy(new MutablePropertySources()); + + Properties vcap = new Properties(); + + vcap.setProperty("vcap.application.name", "TestApp"); + vcap.setProperty("vcap.application.uris", "test-app.apps.cloud.skullbox.com"); + + PropertySource vcapPropertySource = new PropertiesPropertySource("vcap", vcap); + + propertySources.addFirst(vcapPropertySource); + + when(mockEnvironment.getPropertySources()).thenReturn(propertySources); + when(mockEnvironment.getProperty(ClientSecurityAutoConfiguration.CLOUD_CACHE_SERVICE_INSTANCE_NAME_PROPERTY)) + .thenReturn("test-pcc"); + + AutoConfiguredCloudSecurityEnvironmentPostProcessor environmentPostProcessor = + new AutoConfiguredCloudSecurityEnvironmentPostProcessor(); + + try { + environmentPostProcessor.configureSecurityContext(mockEnvironment); + } + catch (IllegalStateException expected) { + + assertThat(expected).hasMessage("No CloudCache Service Instance with name [test-pcc] was found"); + assertThat(expected).hasNoCause(); + + throw expected; + } + finally { + + verify(mockEnvironment, times(1)) + .getProperty(eq(ClientSecurityAutoConfiguration.CLOUD_CACHE_SERVICE_INSTANCE_NAME_PROPERTY)); + verify(mockEnvironment, times(1)).getPropertySources(); + verify(propertySources, never()).addLast(any(PropertySource.class)); + } + } }