From 2e263257f2500adb204cd07ce24927d9a8eb4810 Mon Sep 17 00:00:00 2001 From: John Blum Date: Tue, 29 Jan 2019 10:29:22 -0800 Subject: [PATCH] Workaround issue caused by org.springframework.cloud:spring-cloud-services-starter-service-registry. Resolves gh-21. --- .../ClientSecurityAutoConfiguration.java | 94 ++++++++--- ...entSecurityAutoConfigurationUnitTests.java | 153 +++++++++++++++--- 2 files changed, 204 insertions(+), 43 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 19119480..f25f6b3b 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 @@ -21,6 +21,8 @@ import java.util.Properties; import org.apache.geode.cache.GemFireCache; 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.EnableAutoConfiguration; @@ -35,14 +37,13 @@ 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.env.PropertiesPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.data.gemfire.client.ClientCacheFactoryBean; import org.springframework.data.gemfire.config.annotation.EnableSecurity; import org.springframework.data.gemfire.config.annotation.support.AutoConfiguredAuthenticationInitializer; import org.springframework.geode.core.env.VcapPropertySource; import org.springframework.geode.core.env.support.CloudCacheService; -import org.springframework.geode.core.env.support.Service; +import org.springframework.lang.Nullable; /** * Spring Boot {@link EnableAutoConfiguration auto-configuration} enabling Apache Geode's Security functionality, @@ -78,10 +79,12 @@ import org.springframework.geode.core.env.support.Service; @SuppressWarnings("unused") public class ClientSecurityAutoConfiguration { - public static final String SECURITY_CLOUD_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY = + public static final String CLOUD_SECURITY_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY = "spring.boot.data.gemfire.security.auth.environment.post-processor.enabled"; - private static final String CLOUD_CACHE_PROPERTY_SOURCE_NAME = "cloudcache-configuration"; + private static final Logger logger = LoggerFactory.getLogger(ClientSecurityAutoConfiguration.class); + + private static final String CLOUD_CACHE_PROPERTY_SOURCE_NAME = "boot.data.gemfire.cloudcache"; private static final String MANAGEMENT_HTTP_HOST_PROPERTY = "spring.data.gemfire.management.http.host"; private static final String MANAGEMENT_HTTP_PORT_PROPERTY = "spring.data.gemfire.management.http.port"; @@ -109,17 +112,32 @@ public class ClientSecurityAutoConfiguration { } private boolean isCloudFoundryEnvironment(Environment environment) { - return Optional.ofNullable(environment).filter(CloudPlatform.CLOUD_FOUNDRY::isActive).isPresent(); + + return Optional.ofNullable(environment) + .filter(CloudPlatform.CLOUD_FOUNDRY::isActive) + .isPresent(); } private boolean isEnabled(Environment environment) { - return environment.getProperty(SECURITY_CLOUD_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY, - Boolean.class, true); + + boolean clientSecurityAutoConfigurationEnabled = + environment.getProperty(CLOUD_SECURITY_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY, + Boolean.class, true); + + logger.debug("{} enabled {}", ClientSecurityAutoConfiguration.class.getSimpleName(), + clientSecurityAutoConfigurationEnabled); + + return clientSecurityAutoConfigurationEnabled; } private boolean isSecurityPropertiesSet(Environment environment) { - return environment.containsProperty(SECURITY_USERNAME_PROPERTY) + + boolean securityPropertiesSet = environment.containsProperty(SECURITY_USERNAME_PROPERTY) && environment.containsProperty(SECURITY_PASSWORD_PROPERTY); + + logger.debug("Security Properties set {}", securityPropertiesSet); + + return securityPropertiesSet; } private boolean isSecurityPropertiesNotSet(Environment environment) { @@ -127,28 +145,30 @@ public class ClientSecurityAutoConfiguration { } private void configureAuthentication(Environment environment, Properties cloudCacheProperties, - VcapPropertySource propertySource, Service cloudCache) { + VcapPropertySource vcapPropertySource, CloudCacheService cloudCacheService) { - propertySource.findFirstUserByRoleClusterOperator(cloudCache) + vcapPropertySource.findFirstUserByRoleClusterOperator(cloudCacheService) .filter(user -> isSecurityPropertiesNotSet(environment)) .ifPresent(user -> { + cloudCacheProperties.setProperty(SECURITY_USERNAME_PROPERTY, user.getName()); + user.getPassword().ifPresent(password -> cloudCacheProperties.setProperty(SECURITY_PASSWORD_PROPERTY, password)); }); } private void configureLocators(Environment environment, Properties cloudCacheProperties, - VcapPropertySource propertySource, CloudCacheService cloudCache) { + VcapPropertySource vcapPropertySource, CloudCacheService cloudCacheService) { - cloudCache.getLocators().ifPresent(locators -> + cloudCacheService.getLocators().ifPresent(locators -> cloudCacheProperties.setProperty(POOL_LOCATORS_PROPERTY, locators)); } private void configureManagementRestApiAccess(Environment environment, Properties cloudCacheProperties, - VcapPropertySource propertySource, CloudCacheService cloudCache) { + VcapPropertySource vcapPropertySource, CloudCacheService cloudCacheService) { - cloudCache.getGfshUrl().ifPresent(url -> { + cloudCacheService.getGfshUrl().ifPresent(url -> { cloudCacheProperties.setProperty(MANAGEMENT_USE_HTTP_PROPERTY, Boolean.TRUE.toString()); cloudCacheProperties.setProperty(MANAGEMENT_HTTP_HOST_PROPERTY, url.getHost()); cloudCacheProperties.setProperty(MANAGEMENT_HTTP_PORT_PROPERTY, String.valueOf(url.getPort())); @@ -157,22 +177,27 @@ public class ClientSecurityAutoConfiguration { public void configureSecurityContext(ConfigurableEnvironment environment) { - VcapPropertySource propertySource = VcapPropertySource.from(environment); + VcapPropertySource vcapPropertySource = toVcapPropertySource(environment); Properties cloudCacheProperties = new Properties(); - CloudCacheService cloudCache = propertySource.findFirstCloudCacheService(); + CloudCacheService cloudCacheService = vcapPropertySource.findFirstCloudCacheService(); - configureAuthentication(environment, cloudCacheProperties, propertySource, cloudCache); - configureLocators(environment, cloudCacheProperties, propertySource, cloudCache); - configureManagementRestApiAccess(environment, cloudCacheProperties, propertySource, cloudCache); + configureAuthentication(environment, cloudCacheProperties, vcapPropertySource, cloudCacheService); + configureLocators(environment, cloudCacheProperties, vcapPropertySource, cloudCacheService); + configureManagementRestApiAccess(environment, cloudCacheProperties, vcapPropertySource, cloudCacheService); environment.getPropertySources() - .addFirst(newPropertySource(CLOUD_CACHE_PROPERTY_SOURCE_NAME, cloudCacheProperties)); + .addLast(newPropertySource(CLOUD_CACHE_PROPERTY_SOURCE_NAME, cloudCacheProperties)); } private PropertySource newPropertySource(String name, Properties properties) { - return new PropertiesPropertySource(name, properties); + //return new PropertiesPropertySource(name, properties); + return new SpringDataGemFirePropertiesPropertySource(name, properties); + } + + private VcapPropertySource toVcapPropertySource(Environment environment) { + return VcapPropertySource.from(environment); } } @@ -198,4 +223,31 @@ public class ClientSecurityAutoConfiguration { static class StandaloneApacheGeodeSecurityContextCondition { } } + + // This custom PropertySource is required to prevent Pivotal Spring Cloud Services + // (spring-cloud-services-starter-service-registry) from losing the GemFire/PCC Security Context credentials + // stored in the Environment. + static class SpringDataGemFirePropertiesPropertySource extends PropertySource { + + private static final String SPRING_DATA_GEMFIRE_PROPERTIES_PROPERTY_SOURCE_NAME = + "spring.data.gemfire.properties"; + + SpringDataGemFirePropertiesPropertySource(Properties springDataGemFireProperties) { + this(SPRING_DATA_GEMFIRE_PROPERTIES_PROPERTY_SOURCE_NAME, springDataGemFireProperties); + } + + SpringDataGemFirePropertiesPropertySource(String name, Properties springDataGemFireProperties) { + super(name, springDataGemFireProperties); + } + + @Nullable @Override @SuppressWarnings("all") + public Object getProperty(String name) { + return getSource().getProperty(name); + } + + @Override @SuppressWarnings("all") + 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 d6360d7e..440f77f3 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 @@ -16,6 +16,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.eq; import static org.mockito.Mockito.doNothing; @@ -27,11 +28,16 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.Properties; + import org.junit.Test; import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; import org.springframework.geode.boot.autoconfigure.ClientSecurityAutoConfiguration; import org.springframework.geode.boot.autoconfigure.ClientSecurityAutoConfiguration.AutoConfiguredCloudSecurityEnvironmentPostProcessor; +import org.springframework.mock.env.MockEnvironment; /** * Unit Tests for {@link ClientSecurityAutoConfiguration}. @@ -55,15 +61,15 @@ public class ClientSecurityAutoConfigurationUnitTests { ConfigurableEnvironment mockEnvironment = mock(ConfigurableEnvironment.class); - when(mockEnvironment.getProperty(eq(ClientSecurityAutoConfiguration.SECURITY_CLOUD_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY), - eq(Boolean.class), eq(true))).thenReturn(true); - when(mockEnvironment.containsProperty(eq("VCAP_APPLICATION"))).thenReturn(true); + when(mockEnvironment.getProperty(eq(ClientSecurityAutoConfiguration.CLOUD_SECURITY_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY), + eq(Boolean.class), eq(true))).thenReturn(true); + environmentPostProcessor.postProcessEnvironment(mockEnvironment, null); verify(mockEnvironment, times(1)) - .getProperty(eq(ClientSecurityAutoConfiguration.SECURITY_CLOUD_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY), + .getProperty(eq(ClientSecurityAutoConfiguration.CLOUD_SECURITY_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY), eq(Boolean.class), eq(true)); verify(mockEnvironment, times(1)).containsProperty(eq("VCAP_APPLICATION")); @@ -82,21 +88,20 @@ public class ClientSecurityAutoConfigurationUnitTests { doNothing().when(environmentPostProcessor).configureSecurityContext(any(ConfigurableEnvironment.class)); - ConfigurableEnvironment environment = spy(new StandardEnvironment()); + ConfigurableEnvironment mockEnvironment = spy(new MockEnvironment()); - doReturn(true).when(environment).containsProperty(eq("VCAP_SERVICES")); + doReturn(true).when(mockEnvironment).containsProperty(eq("VCAP_SERVICES")); - environmentPostProcessor.postProcessEnvironment(environment, null); + environmentPostProcessor.postProcessEnvironment(mockEnvironment, null); - verify(environment, times(1)) - .getProperty(eq(ClientSecurityAutoConfiguration.SECURITY_CLOUD_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY), + verify(mockEnvironment, times(1)) + .getProperty(eq(ClientSecurityAutoConfiguration.CLOUD_SECURITY_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY), eq(Boolean.class), eq(true)); - verify(environment, times(1)).containsProperty(eq("VCAP_APPLICATION")); - verify(environment, times(1)).containsProperty(eq("VCAP_SERVICES")); + verify(mockEnvironment, times(1)).containsProperty(eq("VCAP_APPLICATION")); + verify(mockEnvironment, times(1)).containsProperty(eq("VCAP_SERVICES")); - verify(environmentPostProcessor, times(1)) - .configureSecurityContext(eq(environment)); + verify(environmentPostProcessor, times(1)).configureSecurityContext(eq(mockEnvironment)); } @Test @@ -109,16 +114,16 @@ public class ClientSecurityAutoConfigurationUnitTests { ConfigurableEnvironment mockEnvironment = mock(ConfigurableEnvironment.class); - when(mockEnvironment.getProperty(eq(ClientSecurityAutoConfiguration.SECURITY_CLOUD_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY), - eq(Boolean.class), eq(true))).thenReturn(false); - when(mockEnvironment.containsProperty(eq("VCAP_APPLICATION"))).thenReturn(true); when(mockEnvironment.containsProperty(eq("VCAP_SERVICES"))).thenReturn(true); + when(mockEnvironment.getProperty(eq(ClientSecurityAutoConfiguration.CLOUD_SECURITY_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY), + eq(Boolean.class), eq(true))).thenReturn(false); + environmentPostProcessor.postProcessEnvironment(mockEnvironment, null); verify(mockEnvironment, times(1)) - .getProperty(eq(ClientSecurityAutoConfiguration.SECURITY_CLOUD_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY), + .getProperty(eq(ClientSecurityAutoConfiguration.CLOUD_SECURITY_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY), eq(Boolean.class), eq(true)); verify(mockEnvironment, never()).containsProperty(eq("VCAP_APPLICATION")); @@ -136,20 +141,124 @@ public class ClientSecurityAutoConfigurationUnitTests { ConfigurableEnvironment mockEnvironment = mock(ConfigurableEnvironment.class); - when(mockEnvironment.getProperty(eq(ClientSecurityAutoConfiguration.SECURITY_CLOUD_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY), - eq(Boolean.class), eq(true))).thenReturn(true); - when(mockEnvironment.containsProperty(eq("VCAP_APPLICATION"))).thenReturn(false); when(mockEnvironment.containsProperty(eq("VCAP_SERVICES"))).thenReturn(false); + when(mockEnvironment.getProperty(eq(ClientSecurityAutoConfiguration.CLOUD_SECURITY_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY), + eq(Boolean.class), eq(true))).thenReturn(true); + environmentPostProcessor.postProcessEnvironment(mockEnvironment, null); verify(mockEnvironment, times(1)) - .getProperty(eq(ClientSecurityAutoConfiguration.SECURITY_CLOUD_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY), + .getProperty(eq(ClientSecurityAutoConfiguration.CLOUD_SECURITY_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY), eq(Boolean.class), eq(true)); verify(mockEnvironment, times(1)).containsProperty(eq("VCAP_APPLICATION")); verify(mockEnvironment, times(1)).containsProperty(eq("VCAP_SERVICES")); verify(environmentPostProcessor, never()).configureSecurityContext(eq(mockEnvironment)); } + + @Test + @SuppressWarnings("unchecked") + public void configuresSecurityContext() { + + ConfigurableEnvironment mockEnvironment = mock(ConfigurableEnvironment.class); + + Properties vcapProperties = new Properties(); + + vcapProperties.setProperty("vcap.application.name", "TestApp"); + vcapProperties.setProperty("vcap.application.uris", "test-app.apps.cloud.skullbox.com"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.locators", "boombox[10334],skullbox[10334]"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.urls.gfsh", "https://cloud.skullbox.com:8080/gemfire/v1"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.urls.pulse", "https://cloud.skullbox.com:8080/pulse"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.users[0].username", "Abuser"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.users[0].password", "p@55w0rd"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.users[0].roles", "cluster_developer"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.users[1].username", "Master"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.users[1].password", "p@$$w0rd"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.users[1].roles", "cluster_operator"); + vcapProperties.setProperty("vcap.services.test-pcc.tags", "gemfire,cloudcache,test,geode"); + + PropertySource vcapPropertySource = new PropertiesPropertySource("vcap", vcapProperties); + + MutablePropertySources propertySources = new MutablePropertySources(); + + propertySources.addFirst(vcapPropertySource); + + when(mockEnvironment.getPropertySources()).thenReturn(propertySources); + + AutoConfiguredCloudSecurityEnvironmentPostProcessor environmentPostProcessor = + spy(new AutoConfiguredCloudSecurityEnvironmentPostProcessor()); + + environmentPostProcessor.configureSecurityContext(mockEnvironment); + + verify(mockEnvironment, times(2)).getPropertySources(); + + assertThat(propertySources.contains("boot.data.gemfire.cloudcache")).isTrue(); + + PropertySource propertySource = propertySources.get("boot.data.gemfire.cloudcache"); + + assertThat(propertySource).isNotNull(); + assertThat(propertySource.getName()).isEqualTo("boot.data.gemfire.cloudcache"); + assertThat(propertySource.getProperty("spring.data.gemfire.security.username")).isEqualTo("Master"); + assertThat(propertySource.getProperty("spring.data.gemfire.security.password")).isEqualTo("p@$$w0rd"); + assertThat(propertySource.getProperty("spring.data.gemfire.pool.locators")) + .isEqualTo("boombox[10334],skullbox[10334]"); + assertThat(propertySource.getProperty("spring.data.gemfire.management.use-http")).isEqualTo("true"); + assertThat(propertySource.getProperty("spring.data.gemfire.management.http.host")).isEqualTo("cloud.skullbox.com"); + assertThat(propertySource.getProperty("spring.data.gemfire.management.http.port")).isEqualTo("8080"); + } + + @Test + @SuppressWarnings("unchecked") + public void configuresSecurityContextWithLocatorsOnly() { + + ConfigurableEnvironment mockEnvironment = mock(ConfigurableEnvironment.class); + + when(mockEnvironment.containsProperty("spring.data.gemfire.security.username")).thenReturn(true); + when(mockEnvironment.containsProperty("spring.data.gemfire.security.password")).thenReturn(true); + + Properties vcapProperties = new Properties(); + + vcapProperties.setProperty("vcap.application.name", "TestApp"); + vcapProperties.setProperty("vcap.application.uris", "test-app.apps.cloud.skullbox.com"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.locators", "boombox[10334],skullbox[10334]"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.urls.pulse", "https://cloud.skullbox.com:8080/pulse"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.users[0].username", "Abuser"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.users[0].password", "p@55w0rd"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.users[0].roles", "cluster_developer"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.users[1].username", "Master"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.users[1].password", "p@$$w0rd"); + vcapProperties.setProperty("vcap.services.test-pcc.credentials.users[1].roles", "cluster_operator"); + vcapProperties.setProperty("vcap.services.test-pcc.tags", "gemfire,cloudcache,test"); + + PropertySource vcapPropertySource = new PropertiesPropertySource("vcap", vcapProperties); + + MutablePropertySources propertySources = new MutablePropertySources(); + + propertySources.addFirst(vcapPropertySource); + + when(mockEnvironment.getPropertySources()).thenReturn(propertySources); + + AutoConfiguredCloudSecurityEnvironmentPostProcessor environmentPostProcessor = + spy(new AutoConfiguredCloudSecurityEnvironmentPostProcessor()); + + environmentPostProcessor.configureSecurityContext(mockEnvironment); + + verify(mockEnvironment, times(2)).getPropertySources(); + + assertThat(propertySources.contains("boot.data.gemfire.cloudcache")).isTrue(); + + PropertySource propertySource = propertySources.get("boot.data.gemfire.cloudcache"); + + assertThat(propertySource).isNotNull(); + assertThat(propertySource.getName()).isEqualTo("boot.data.gemfire.cloudcache"); + assertThat(propertySource.containsProperty("spring.data.gemfire.security.username")).isFalse(); + assertThat(propertySource.containsProperty("spring.data.gemfire.security.password")).isFalse(); + assertThat(propertySource.getProperty("spring.data.gemfire.pool.locators")) + .isEqualTo("boombox[10334],skullbox[10334]"); + assertThat(propertySource.containsProperty("spring.data.gemfire.management.use-http")).isFalse(); + assertThat(propertySource.containsProperty("spring.data.gemfire.management.http.host")).isFalse(); + assertThat(propertySource.containsProperty("spring.data.gemfire.management.http.port")).isFalse(); + } }