From e1017d482fca2f5360da6bb0ea40d8a82222b5cd Mon Sep 17 00:00:00 2001 From: John Blum Date: Tue, 10 Sep 2019 14:25:36 -0700 Subject: [PATCH] Improve Client Security Auto-configuration to allow users to configure the runtime user of their Spring Boot applications running in Pivotal CloudFoundray (PCF) when using Pivotal Cloud Cache (PCC). Note: configuring the runtime user of a Spring Boot app outside of CloudFoundry is already supported. Simply set the 'spring.data.gemfire.security.username' and 'spring.data.gemfire.security.password' properties. Resolves gh-44. --- .../ClientSecurityAutoConfiguration.java | 45 +++- ...entSecurityAutoConfigurationUnitTests.java | 214 ++++++++++++++++-- 2 files changed, 235 insertions(+), 24 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 fc810fa2..991d6ff5 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 @@ -47,6 +47,7 @@ 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.User; import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; @@ -79,6 +80,7 @@ import org.springframework.util.StringUtils; * @see org.springframework.geode.core.env.VcapPropertySource * @see org.springframework.geode.core.env.support.CloudCacheService * @see org.springframework.geode.core.env.support.Service + * @see org.springframework.geode.core.env.support.User * @since 1.0.0 */ @Configuration @@ -136,7 +138,7 @@ public class ClientSecurityAutoConfiguration { environment.getProperty(CLOUD_SECURITY_ENVIRONMENT_POST_PROCESSOR_ENABLED_PROPERTY, Boolean.class, true); - logger.debug("{} enabled {}", ClientSecurityAutoConfiguration.class.getSimpleName(), + logger.debug("{} enabled? [{}]", ClientSecurityAutoConfiguration.class.getSimpleName(), clientSecurityAutoConfigurationEnabled); return clientSecurityAutoConfigurationEnabled; @@ -147,7 +149,7 @@ public class ClientSecurityAutoConfiguration { boolean securityPropertiesSet = environment.containsProperty(SECURITY_USERNAME_PROPERTY) && environment.containsProperty(SECURITY_PASSWORD_PROPERTY); - logger.debug("Security Properties set {}", securityPropertiesSet); + logger.debug("Security Properties set? [{}]", securityPropertiesSet); return securityPropertiesSet; } @@ -159,15 +161,36 @@ public class ClientSecurityAutoConfiguration { private void configureAuthentication(Environment environment, VcapPropertySource vcapPropertySource, CloudCacheService cloudCacheService, Properties cloudCacheProperties) { - vcapPropertySource.findFirstUserByRoleClusterOperator(cloudCacheService) - .filter(user -> isSecurityPropertiesNotSet(environment)) - .ifPresent(user -> { + if (isSecurityPropertiesNotSet(environment)) { + if (environment.containsProperty(SECURITY_USERNAME_PROPERTY)) { - cloudCacheProperties.setProperty(SECURITY_USERNAME_PROPERTY, user.getName()); + String targetUsername = environment.getProperty(SECURITY_USERNAME_PROPERTY); - user.getPassword().ifPresent(password -> - cloudCacheProperties.setProperty(SECURITY_PASSWORD_PROPERTY, password)); - }); + vcapPropertySource.findUserByName(cloudCacheService, targetUsername) + .flatMap(User::getPassword) + .map(password -> { + + cloudCacheProperties.setProperty(SECURITY_USERNAME_PROPERTY, targetUsername); + cloudCacheProperties.setProperty(SECURITY_PASSWORD_PROPERTY, password); + + return password; + + }) + .orElseThrow(() -> newIllegalStateException( + "No User with name [%s] was configured for Cloud Cache service [%s]", + targetUsername, cloudCacheService.getName())); + } + else { + vcapPropertySource.findFirstUserByRoleClusterOperator(cloudCacheService) + .ifPresent(user -> { + + cloudCacheProperties.setProperty(SECURITY_USERNAME_PROPERTY, user.getName()); + + user.getPassword().ifPresent(password -> + cloudCacheProperties.setProperty(SECURITY_PASSWORD_PROPERTY, password)); + }); + } + } } private void configureLocators(Environment environment, VcapPropertySource vcapPropertySource, @@ -212,11 +235,11 @@ public class ClientSecurityAutoConfiguration { .orElseGet(() -> { if (StringUtils.hasText(cloudcacheServiceInstanceName)) { - throw newIllegalStateException("No CloudCache Service Instance with name [%s] was found", + throw newIllegalStateException("No Cloud Cache service instance with name [%s] was found", cloudcacheServiceInstanceName); } else { - logger.warn("No CloudCache Service Instance was found"); + logger.warn("No Cloud Cache service instance was found"); } return null; 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 f1ac28eb..5c4d946a 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 @@ -36,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.core.env.StandardEnvironment; 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; @@ -184,7 +185,8 @@ public class ClientSecurityAutoConfigurationUnitTests { 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"); + vcapProperties.setProperty("vcap.services.test-pcc.name", "test-pcc"); + vcapProperties.setProperty("vcap.services.test-pcc.tags", "gemfire,cloudcache,test,geode,pivotal"); PropertySource vcapPropertySource = new PropertiesPropertySource("vcap", vcapProperties); @@ -224,6 +226,8 @@ public class ClientSecurityAutoConfigurationUnitTests { when(mockEnvironment.containsProperty("spring.data.gemfire.security.username")).thenReturn(true); when(mockEnvironment.containsProperty("spring.data.gemfire.security.password")).thenReturn(true); + MutablePropertySources propertySources = new MutablePropertySources(); + Properties vcapProperties = new Properties(); vcapProperties.setProperty("vcap.application.name", "TestApp"); @@ -240,8 +244,6 @@ public class ClientSecurityAutoConfigurationUnitTests { PropertySource vcapPropertySource = new PropertiesPropertySource("vcap", vcapProperties); - MutablePropertySources propertySources = new MutablePropertySources(); - propertySources.addFirst(vcapPropertySource); when(mockEnvironment.getPropertySources()).thenReturn(propertySources); @@ -275,12 +277,12 @@ public class ClientSecurityAutoConfigurationUnitTests { MutablePropertySources propertySources = spy(new MutablePropertySources()); - Properties vcap = new Properties(); + Properties vcapProperties = new Properties(); - vcap.setProperty("vcap.application.name", "TestApp"); - vcap.setProperty("vcap.application.uris", "test-app.apps.cloud.skullbox.com"); + vcapProperties.setProperty("vcap.application.name", "TestApp"); + vcapProperties.setProperty("vcap.application.uris", "test-app.apps.cloud.skullbox.com"); - PropertySource vcapPropertySource = new PropertiesPropertySource("vcap", vcap); + PropertySource vcapPropertySource = new PropertiesPropertySource("vcap", vcapProperties); propertySources.addFirst(vcapPropertySource); @@ -297,7 +299,7 @@ public class ClientSecurityAutoConfigurationUnitTests { TestAppender testAppender = TestAppender.getInstance(); assertThat(testAppender).isNotNull(); - assertThat(testAppender.lastLogMessage()).isEqualTo("No CloudCache Service Instance was found"); + assertThat(testAppender.lastLogMessage()).isEqualTo("No Cloud Cache service instance was found"); } finally { @@ -315,12 +317,12 @@ public class ClientSecurityAutoConfigurationUnitTests { MutablePropertySources propertySources = spy(new MutablePropertySources()); - Properties vcap = new Properties(); + Properties vcapProperties = new Properties(); - vcap.setProperty("vcap.application.name", "TestApp"); - vcap.setProperty("vcap.application.uris", "test-app.apps.cloud.skullbox.com"); + vcapProperties.setProperty("vcap.application.name", "TestApp"); + vcapProperties.setProperty("vcap.application.uris", "test-app.apps.cloud.skullbox.com"); - PropertySource vcapPropertySource = new PropertiesPropertySource("vcap", vcap); + PropertySource vcapPropertySource = new PropertiesPropertySource("vcap", vcapProperties); propertySources.addFirst(vcapPropertySource); @@ -336,7 +338,7 @@ public class ClientSecurityAutoConfigurationUnitTests { } catch (IllegalStateException expected) { - assertThat(expected).hasMessage("No CloudCache Service Instance with name [test-pcc] was found"); + assertThat(expected).hasMessage("No Cloud Cache service instance with name [test-pcc] was found"); assertThat(expected).hasNoCause(); throw expected; @@ -349,4 +351,190 @@ public class ClientSecurityAutoConfigurationUnitTests { verify(propertySources, never()).addLast(any(PropertySource.class)); } } + + @Test + public void configuresAuthenticationWithCloudPlatformCredentials() { + + ConfigurableEnvironment environment = spy(new StandardEnvironment()); + + Properties vcap = new Properties(); + + vcap.setProperty("vcap.application.name", "TestApp"); + vcap.setProperty("vcap.application.uris", "test-app.apps.cloud.skullbox.com"); + vcap.setProperty("vcap.services.test-pcc.name", "test-pcc"); + vcap.setProperty("vcap.services.test-pcc.tags", "pivotal, cloudcache, database, gemfire"); + vcap.setProperty("vcap.services.test-pcc.credentials.users", "Abuser, Master"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[0].username", "Abuser"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[0].password", "p@55w0rd"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[0].roles", "cluster_developer"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[1].username", "Master"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[1].password", "p9@$$w0rd"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[1].roles", "cluster_operator"); + + PropertiesPropertySource vcapPropertySource = new PropertiesPropertySource("vcap", vcap); + + environment.getPropertySources().addLast(vcapPropertySource); + + AutoConfiguredCloudSecurityEnvironmentPostProcessor environmentPostProcessor = + new AutoConfiguredCloudSecurityEnvironmentPostProcessor(); + + environmentPostProcessor.configureSecurityContext(environment); + + assertThat(environment.getProperty("spring.data.gemfire.security.username")).isEqualTo("Master"); + assertThat(environment.getProperty("spring.data.gemfire.security.password")).isEqualTo("p9@$$w0rd"); + + verify(environment, times(2)) + .containsProperty(eq("spring.data.gemfire.security.username")); + + verify(environment, never()) + .containsProperty(eq("spring.data.gemfire.security.password")); + } + + @Test + public void configuresAuthenticationWithCloudPlatformCredentialsAndTargetUser() { + + ConfigurableEnvironment environment = spy(new StandardEnvironment()); + + Properties springDataGemFire = new Properties(); + + springDataGemFire.setProperty("spring.data.gemfire.security.username", "Abuser"); + + Properties vcap = new Properties(); + + vcap.setProperty("vcap.application.name", "TestApp"); + vcap.setProperty("vcap.application.uris", "test-app.apps.cloud.skullbox.com"); + vcap.setProperty("vcap.services.test-pcc.name", "test-pcc"); + vcap.setProperty("vcap.services.test-pcc.tags", "pivotal, cloudcache, database, gemfire"); + vcap.setProperty("vcap.services.test-pcc.credentials.users", "Abuser, Master"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[0].username", "Abuser"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[0].password", "p@55w0rd"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[0].roles", "cluster_developer"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[1].username", "Master"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[1].password", "p9@$$w0rd"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[1].roles", "cluster_operator"); + + PropertiesPropertySource vcapPropertySource = new PropertiesPropertySource("vcap", vcap); + + PropertiesPropertySource springDataGemFirePropertySource = + new PropertiesPropertySource("spring.data.gemfire", springDataGemFire); + + environment.getPropertySources().addLast(vcapPropertySource); + environment.getPropertySources().addLast(springDataGemFirePropertySource); + + AutoConfiguredCloudSecurityEnvironmentPostProcessor environmentPostProcessor = + new AutoConfiguredCloudSecurityEnvironmentPostProcessor(); + + environmentPostProcessor.configureSecurityContext(environment); + + assertThat(environment.getProperty("spring.data.gemfire.security.username")).isEqualTo("Abuser"); + assertThat(environment.getProperty("spring.data.gemfire.security.password")).isEqualTo("p@55w0rd"); + + verify(environment, times(2)) + .containsProperty(eq("spring.data.gemfire.security.username")); + + verify(environment, times(1)) + .containsProperty(eq("spring.data.gemfire.security.password")); + } + + @Test(expected = IllegalStateException.class) + public void configuresAuthenticationWithCloudPlatformCredentialsAndNonExistingTargetUserThrowsIllegalStateException() { + + ConfigurableEnvironment environment = spy(new StandardEnvironment()); + + Properties springDataGemFire = new Properties(); + + springDataGemFire.setProperty("spring.data.gemfire.security.username", "NonExistingUser"); + + Properties vcap = new Properties(); + + vcap.setProperty("vcap.application.name", "TestApp"); + vcap.setProperty("vcap.application.uris", "test-app.apps.cloud.skullbox.com"); + vcap.setProperty("vcap.services.test-pcc.name", "test-pcc"); + vcap.setProperty("vcap.services.test-pcc.tags", "pivotal, cloudcache, database, gemfire"); + vcap.setProperty("vcap.services.test-pcc.credentials.users", "Abuser, Master"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[0].username", "Abuser"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[0].password", "p@55w0rd"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[0].roles", "cluster_developer"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[1].username", "Master"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[1].password", "p9@$$w0rd"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[1].roles", "cluster_operator"); + + PropertiesPropertySource vcapPropertySource = new PropertiesPropertySource("vcap", vcap); + + PropertiesPropertySource springDataGemFirePropertySource = + new PropertiesPropertySource("spring.data.gemfire", springDataGemFire); + + environment.getPropertySources().addLast(vcapPropertySource); + environment.getPropertySources().addLast(springDataGemFirePropertySource); + + AutoConfiguredCloudSecurityEnvironmentPostProcessor environmentPostProcessor = + new AutoConfiguredCloudSecurityEnvironmentPostProcessor(); + + try { + environmentPostProcessor.configureSecurityContext(environment); + } + catch (IllegalStateException expected) { + + assertThat(expected) + .hasMessage("No User with name [NonExistingUser] was configured for Cloud Cache service [test-pcc]"); + + assertThat(expected).hasNoCause(); + + throw expected; + } + finally { + verify(environment, times(2)) + .containsProperty(eq("spring.data.gemfire.security.username")); + + verify(environment, times(1)) + .containsProperty(eq("spring.data.gemfire.security.password")); + } + } + + @Test + public void configuresAuthenticationWithUserSuppliedCredentials() { + + ConfigurableEnvironment environment = spy(new StandardEnvironment()); + + Properties springDataGemFire = new Properties(); + + springDataGemFire.setProperty("spring.data.gemfire.security.username", "MyUser"); + springDataGemFire.setProperty("spring.data.gemfire.security.password", "s3cUr3"); + + Properties vcap = new Properties(); + + vcap.setProperty("vcap.application.name", "TestApp"); + vcap.setProperty("vcap.application.uris", "test-app.apps.cloud.skullbox.com"); + vcap.setProperty("vcap.services.test-pcc.name", "test-pcc"); + vcap.setProperty("vcap.services.test-pcc.tags", "pivotal, cloudcache, database, gemfire"); + vcap.setProperty("vcap.services.test-pcc.credentials.users", "Abuser, Master"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[0].username", "Abuser"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[0].password", "p@55w0rd"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[0].roles", "cluster_developer"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[1].username", "Master"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[1].password", "p9@$$w0rd"); + vcap.setProperty("vcap.services.test-pcc.credentials.users[1].roles", "cluster_operator"); + + PropertiesPropertySource vcapPropertySource = new PropertiesPropertySource("vcap", vcap); + + PropertiesPropertySource springDataGemFirePropertySource = + new PropertiesPropertySource("spring.data.gemfire", springDataGemFire); + + environment.getPropertySources().addLast(vcapPropertySource); + environment.getPropertySources().addLast(springDataGemFirePropertySource); + + AutoConfiguredCloudSecurityEnvironmentPostProcessor environmentPostProcessor = + new AutoConfiguredCloudSecurityEnvironmentPostProcessor(); + + environmentPostProcessor.configureSecurityContext(environment); + + assertThat(environment.getProperty("spring.data.gemfire.security.username")).isEqualTo("MyUser"); + assertThat(environment.getProperty("spring.data.gemfire.security.password")).isEqualTo("s3cUr3"); + + verify(environment, times(1)) + .containsProperty(eq("spring.data.gemfire.security.username")); + + verify(environment, times(1)) + .containsProperty(eq("spring.data.gemfire.security.password")); + } }