From c504576793e772672bee0aa2ccbbff2f3e1bdffb Mon Sep 17 00:00:00 2001 From: Roy Clarkson Date: Tue, 11 Dec 2018 13:44:13 -0500 Subject: [PATCH] Add support to optionally generate secrets from CredHub - Add `CredHubCredentialsGenerator` - Update `CredHubAutoconfiguration` to conditionally create beans - Refactor the `CredentialGenerator` interface to be reactive - Replace use of Pair in favor of Tuple2 Resolves #152 --- .../CredHubAutoConfiguration.java | 11 +- .../AppBrokerAutoConfigurationTest.java | 17 ++- .../credentials/CredentialGenerator.java | 25 ++-- .../SimpleCredentialGenerator.java | 40 ++--- ...ityBasicAuthCredentialProviderFactory.java | 52 ++++--- ...curityOAuth2CredentialProviderFactory.java | 73 ++++----- .../SimpleCredentialGeneratorTest.java | 46 +++--- ...asicAuthCredentialProviderFactoryTest.java | 21 ++- ...tyOAuth2CredentialProviderFactoryTest.java | 28 ++-- .../CredHubCredentialsGenerator.java | 87 +++++++++++ ...eateServiceInstanceAppBindingWorkflow.java | 16 +- .../CredHubCredentialsGeneratorTest.java | 139 ++++++++++++++++++ 12 files changed, 422 insertions(+), 133 deletions(-) create mode 100644 spring-cloud-app-broker-security-credhub/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/CredHubCredentialsGenerator.java create mode 100644 spring-cloud-app-broker-security-credhub/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/CredHubCredentialsGeneratorTest.java diff --git a/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/CredHubAutoConfiguration.java b/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/CredHubAutoConfiguration.java index 975d9dc..38b39e6 100644 --- a/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/CredHubAutoConfiguration.java +++ b/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/CredHubAutoConfiguration.java @@ -16,27 +16,32 @@ package org.springframework.cloud.appbroker.autoconfigure; -import org.springframework.cloud.appbroker.workflow.binding.CredHubPersistingCreateServiceInstanceAppBindingWorkflow; - import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.cloud.appbroker.extensions.credentials.CredHubCredentialsGenerator; import org.springframework.cloud.appbroker.service.CreateServiceInstanceAppBindingWorkflow; +import org.springframework.cloud.appbroker.workflow.binding.CredHubPersistingCreateServiceInstanceAppBindingWorkflow; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.credhub.core.ReactiveCredHubOperations; @Configuration @AutoConfigureBefore(AppBrokerAutoConfiguration.class) +@ConditionalOnBean(ReactiveCredHubOperations.class) public class CredHubAutoConfiguration { @Value("${spring.application.name}") private String appName; @Bean - @ConditionalOnBean(ReactiveCredHubOperations.class) public CreateServiceInstanceAppBindingWorkflow credhubPersistingCreateServiceInstanceAppBindingWorkflow(ReactiveCredHubOperations credHubOperations) { return new CredHubPersistingCreateServiceInstanceAppBindingWorkflow(credHubOperations, appName); } + @Bean + public CredHubCredentialsGenerator credHubCredentialsGenerator(ReactiveCredHubOperations credHubOperations) { + return new CredHubCredentialsGenerator(credHubOperations); + } + } diff --git a/spring-cloud-app-broker-autoconfigure/src/test/java/org/springframework/cloud/appbroker/autoconfigure/AppBrokerAutoConfigurationTest.java b/spring-cloud-app-broker-autoconfigure/src/test/java/org/springframework/cloud/appbroker/autoconfigure/AppBrokerAutoConfigurationTest.java index 9c61752..65f3511 100644 --- a/spring-cloud-app-broker-autoconfigure/src/test/java/org/springframework/cloud/appbroker/autoconfigure/AppBrokerAutoConfigurationTest.java +++ b/spring-cloud-app-broker-autoconfigure/src/test/java/org/springframework/cloud/appbroker/autoconfigure/AppBrokerAutoConfigurationTest.java @@ -17,6 +17,10 @@ package org.springframework.cloud.appbroker.autoconfigure; import org.junit.jupiter.api.Test; + +import org.springframework.cloud.appbroker.extensions.credentials.CredHubCredentialsGenerator; +import org.springframework.cloud.appbroker.extensions.credentials.CredentialGenerator; +import org.springframework.cloud.appbroker.extensions.credentials.SimpleCredentialGenerator; import org.springframework.cloud.appbroker.workflow.binding.CredHubPersistingCreateServiceInstanceAppBindingWorkflow; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -60,7 +64,14 @@ class AppBrokerAutoConfigurationTest { @Test void servicesAreCreatedWithCloudFoundryConfigured() { configuredContext() - .run(this::assertContext); + .run(context -> { + assertContext(context); + assertThat(context).doesNotHaveBean(CreateServiceInstanceAppBindingWorkflow.class); + assertThat(context) + .hasSingleBean(CredentialGenerator.class) + .getBean(CredentialGenerator.class) + .isExactlyInstanceOf(SimpleCredentialGenerator.class); + }); } @Test @@ -73,6 +84,10 @@ class AppBrokerAutoConfigurationTest { .hasSingleBean(CreateServiceInstanceAppBindingWorkflow.class) .getBean(CreateServiceInstanceAppBindingWorkflow.class) .isExactlyInstanceOf(CredHubPersistingCreateServiceInstanceAppBindingWorkflow.class); + assertThat(context) + .hasSingleBean(CredentialGenerator.class) + .getBean(CredentialGenerator.class) + .isExactlyInstanceOf(CredHubCredentialsGenerator.class); }); } diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/CredentialGenerator.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/CredentialGenerator.java index be79d08..efb12f9 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/CredentialGenerator.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/CredentialGenerator.java @@ -16,23 +16,26 @@ package org.springframework.cloud.appbroker.extensions.credentials; -import org.apache.commons.lang3.tuple.Pair; +import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; public interface CredentialGenerator { - Pair generateUser(String applicationId, String serviceInstanceId, - int length, boolean includeUppercaseAlpha, - boolean includeLowercaseAlpha, boolean includeNumeric, - boolean includeSpecial); + Mono> generateUser(String applicationId, String serviceInstanceId, String descriptor, + int length, boolean includeUppercaseAlpha, + boolean includeLowercaseAlpha, boolean includeNumeric, + boolean includeSpecial); - String generateString(String applicationId, String serviceInstanceId, - int length, boolean includeUppercaseAlpha, - boolean includeLowercaseAlpha, boolean includeNumeric, - boolean includeSpecial); + Mono generateString(String applicationId, String serviceInstanceId, String descriptor, + int length, boolean includeUppercaseAlpha, + boolean includeLowercaseAlpha, boolean includeNumeric, + boolean includeSpecial); - default void deleteUser(String applicationId, String serviceInstanceId) { + default Mono deleteUser(String applicationId, String serviceInstanceId, String descriptor) { + return Mono.empty(); } - default void deleteString(String applicationId, String serviceInstanceId) { + default Mono deleteString(String applicationId, String serviceInstanceId, String descriptor) { + return Mono.empty(); } } diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/SimpleCredentialGenerator.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/SimpleCredentialGenerator.java index 06ba8e9..b0e9bf6 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/SimpleCredentialGenerator.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/SimpleCredentialGenerator.java @@ -16,11 +16,13 @@ package org.springframework.cloud.appbroker.extensions.credentials; -import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.tuple.Pair; - import java.security.SecureRandom; +import org.apache.commons.lang3.RandomStringUtils; +import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + public class SimpleCredentialGenerator implements CredentialGenerator { private static final String UPPERCASE_ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; @@ -29,22 +31,22 @@ public class SimpleCredentialGenerator implements CredentialGenerator { private static final String SPECIAL_CHARACTERS = "~`!@#$%^&*()-_=+[{]}\\|;:\'\",<.>/?"; private final SecureRandom secureRandom = new SecureRandom(); - + @Override - public Pair generateUser(String applicationId, String serviceInstanceId, int length, - boolean includeUppercaseAlpha, boolean includeLowercaseAlpha, - boolean includeNumeric, boolean includeSpecial) { - return Pair.of( - generateString(applicationId, serviceInstanceId, length, - includeUppercaseAlpha, includeLowercaseAlpha, includeNumeric, includeSpecial), - generateString(applicationId, serviceInstanceId, length, - includeUppercaseAlpha, includeLowercaseAlpha, includeNumeric, includeSpecial)); + public Mono> generateUser(String applicationId, String serviceInstanceId, String descriptor, int length, + boolean includeUppercaseAlpha, boolean includeLowercaseAlpha, + boolean includeNumeric, boolean includeSpecial) { + return generateString(applicationId, serviceInstanceId, descriptor, length, + includeUppercaseAlpha, includeLowercaseAlpha, includeNumeric, includeSpecial) + .flatMap(username -> generateString(applicationId, serviceInstanceId, descriptor, length, + includeUppercaseAlpha, includeLowercaseAlpha, includeNumeric, includeSpecial) + .map(password -> Tuples.of(username, password))); } @Override - public String generateString(String applicationId, String serviceInstanceId, int length, - boolean includeUppercaseAlpha, boolean includeLowercaseAlpha, - boolean includeNumeric, boolean includeSpecial) { + public Mono generateString(String applicationId, String serviceInstanceId, String descriptor, int length, + boolean includeUppercaseAlpha, boolean includeLowercaseAlpha, + boolean includeNumeric, boolean includeSpecial) { StringBuilder builder = new StringBuilder(); if (includeUppercaseAlpha) { @@ -65,13 +67,13 @@ public class SimpleCredentialGenerator implements CredentialGenerator { if (builder.length() == 0) { builder.append(UPPERCASE_ALPHA) - .append(LOWERCASE_ALPHA) - .append(DIGITS) - .append(SPECIAL_CHARACTERS); + .append(LOWERCASE_ALPHA) + .append(DIGITS) + .append(SPECIAL_CHARACTERS); } char[] chars = builder.toString().toCharArray(); - return RandomStringUtils.random(length, 0, chars.length - 1, false, false, chars, secureRandom); + return Mono.just(RandomStringUtils.random(length, 0, chars.length - 1, false, false, chars, secureRandom)); } } diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityBasicAuthCredentialProviderFactory.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityBasicAuthCredentialProviderFactory.java index 7fef0fa..02ef4c8 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityBasicAuthCredentialProviderFactory.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityBasicAuthCredentialProviderFactory.java @@ -16,17 +16,19 @@ package org.springframework.cloud.appbroker.extensions.credentials; -import org.apache.commons.lang3.tuple.Pair; -import org.springframework.cloud.appbroker.deployer.BackingApplication; -import reactor.core.publisher.Mono; - import java.util.Collections; import java.util.HashMap; -import java.util.Map; + +import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; + +import org.springframework.cloud.appbroker.deployer.BackingApplication; public class SpringSecurityBasicAuthCredentialProviderFactory extends CredentialProviderFactory { + private static final String CREDENTIAL_DESCRIPTOR = "basic"; + static final String SPRING_KEY = "spring"; static final String SPRING_SECURITY_KEY = "security"; static final String SPRING_SECURITY_USER_KEY = "user"; @@ -46,35 +48,37 @@ public class SpringSecurityBasicAuthCredentialProviderFactory extends @Override public Mono addCredentials(BackingApplication backingApplication, String serviceInstanceGuid) { - Pair user = generateCredentials(config, backingApplication, serviceInstanceGuid); - addUserToEnvironment(backingApplication, user); - return Mono.just(backingApplication); + return generateCredentials(config, backingApplication, serviceInstanceGuid) + .flatMap(user -> addUserToEnvironment(backingApplication, user)) + .thenReturn(backingApplication); } @Override public Mono deleteCredentials(BackingApplication backingApplication, String serviceInstanceGuid) { - credentialGenerator.deleteUser(backingApplication.getName(), serviceInstanceGuid); - return Mono.just(backingApplication); + return credentialGenerator.deleteUser(backingApplication.getName(), serviceInstanceGuid, CREDENTIAL_DESCRIPTOR) + .thenReturn(backingApplication); } }; } - private Pair generateCredentials(CredentialGenerationConfig config, - BackingApplication backingApplication, - String serviceInstanceGuid) { - return credentialGenerator.generateUser(backingApplication.getName(), serviceInstanceGuid, - config.getLength(), config.isIncludeUppercaseAlpha(), config.isIncludeLowercaseAlpha(), - config.isIncludeNumeric(), config.isIncludeSpecial()); + private Mono> generateCredentials(CredentialGenerationConfig config, + BackingApplication backingApplication, + String serviceInstanceGuid) { + return credentialGenerator.generateUser(backingApplication.getName(), serviceInstanceGuid, CREDENTIAL_DESCRIPTOR, + config.getLength(), config.isIncludeUppercaseAlpha(), config.isIncludeLowercaseAlpha(), + config.isIncludeNumeric(), config.isIncludeSpecial()); } - private void addUserToEnvironment(BackingApplication backingApplication, Pair user) { - Map userProperties = new HashMap<>(2); - userProperties.put(SPRING_SECURITY_USER_NAME_KEY, user.getLeft()); - userProperties.put(SPRING_SECURITY_USER_PASSWORD_KEY, user.getRight()); - - backingApplication.addEnvironment(SPRING_KEY, - Collections.singletonMap(SPRING_SECURITY_KEY, - Collections.singletonMap(SPRING_SECURITY_USER_KEY, userProperties))); + private Mono addUserToEnvironment(BackingApplication backingApplication, Tuple2 user) { + return Mono.just(new HashMap<>(2)) + .flatMap(userProperties -> { + userProperties.put(SPRING_SECURITY_USER_NAME_KEY, user.getT1()); + userProperties.put(SPRING_SECURITY_USER_PASSWORD_KEY, user.getT2()); + backingApplication.addEnvironment(SPRING_KEY, + Collections.singletonMap(SPRING_SECURITY_KEY, + Collections.singletonMap(SPRING_SECURITY_USER_KEY, userProperties))); + return Mono.empty(); + }); } } diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityOAuth2CredentialProviderFactory.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityOAuth2CredentialProviderFactory.java index 20c3c8c..2ebd539 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityOAuth2CredentialProviderFactory.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityOAuth2CredentialProviderFactory.java @@ -16,22 +16,26 @@ package org.springframework.cloud.appbroker.extensions.credentials; -import org.apache.commons.lang3.tuple.Pair; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + import org.springframework.cloud.appbroker.deployer.BackingApplication; import org.springframework.cloud.appbroker.oauth2.CreateOAuth2ClientRequest; import org.springframework.cloud.appbroker.oauth2.CreateOAuth2ClientResponse; import org.springframework.cloud.appbroker.oauth2.DeleteOAuth2ClientRequest; import org.springframework.cloud.appbroker.oauth2.DeleteOAuth2ClientResponse; import org.springframework.cloud.appbroker.oauth2.OAuth2Client; -import reactor.core.publisher.Mono; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; public class SpringSecurityOAuth2CredentialProviderFactory extends CredentialProviderFactory { + private static final String CREDENTIAL_DESCRIPTOR = "oauth2"; + static final String SPRING_KEY = "spring"; static final String SPRING_SECURITY_KEY = "security"; static final String SPRING_SECURITY_OAUTH2_KEY = "oauth2"; @@ -65,33 +69,30 @@ public class SpringSecurityOAuth2CredentialProviderFactory extends @Override public Mono deleteCredentials(BackingApplication backingApplication, String serviceInstanceGuid) { - credentialGenerator.deleteString(backingApplication.getName(), serviceInstanceGuid); - - String clientId = generateClientId(config, backingApplication, serviceInstanceGuid); - - return deleteOAuth2Client(config, clientId) - .flatMap(response -> Mono.just(backingApplication)); + return credentialGenerator.deleteString(backingApplication.getName(), serviceInstanceGuid, CREDENTIAL_DESCRIPTOR) + .then(generateClientId(config, backingApplication, serviceInstanceGuid)) + .flatMap(clientId -> deleteOAuth2Client(config, clientId) + .flatMap(response -> Mono.just(backingApplication))); } }; } - private Mono> generateCredentials(Config config, - BackingApplication backingApplication, - String serviceInstanceGuid) { - String id = generateClientId(config, backingApplication, serviceInstanceGuid); - String secret = generateClientSecret(config, backingApplication, serviceInstanceGuid); - - Pair client = Pair.of(id, secret); - - return Mono.just(client); + private Mono> generateCredentials(Config config, + BackingApplication backingApplication, + String serviceInstanceGuid) { + return generateClientId(config, backingApplication, serviceInstanceGuid) + .flatMap(id -> generateClientSecret(config, backingApplication, serviceInstanceGuid) + .map(secret -> Tuples.of(id, secret))); } - private Mono> addClientToEnvironment(Config config, + private Mono> addClientToEnvironment(Config config, BackingApplication backingApplication, - Pair client) { + Tuple2 client) { + + Map clientProperties = new HashMap<>(2); - clientProperties.put(SPRING_SECURITY_CLIENT_ID_KEY, client.getLeft()); - clientProperties.put(SPRING_SECURITY_CLIENT_SECRET_KEY, client.getRight()); + clientProperties.put(SPRING_SECURITY_CLIENT_ID_KEY, client.getT1()); + clientProperties.put(SPRING_SECURITY_CLIENT_SECRET_KEY, client.getT2()); backingApplication.addEnvironment(SPRING_KEY, Collections.singletonMap(SPRING_SECURITY_KEY, @@ -103,25 +104,27 @@ public class SpringSecurityOAuth2CredentialProviderFactory extends return Mono.just(client); } - private String generateClientId(Config config, BackingApplication backingApplication, + private Mono generateClientId(Config config, BackingApplication backingApplication, String serviceInstanceGuid) { - if (config.clientId == null) { - return backingApplication.getName() + "-" + serviceInstanceGuid; - } - return config.getClientId(); + return Mono.defer(() -> { + if (config.clientId == null) { + return Mono.just(backingApplication.getName() + "-" + serviceInstanceGuid); + } + return Mono.just(config.getClientId()); + }); } - private String generateClientSecret(Config config, BackingApplication backingApplication, + private Mono generateClientSecret(Config config, BackingApplication backingApplication, String serviceInstanceGuid) { return credentialGenerator.generateString(backingApplication.getName(), serviceInstanceGuid, - config.getLength(), config.isIncludeUppercaseAlpha(), config.isIncludeLowercaseAlpha(), + CREDENTIAL_DESCRIPTOR, config.getLength(), config.isIncludeUppercaseAlpha(), config.isIncludeLowercaseAlpha(), config.isIncludeNumeric(), config.isIncludeSpecial()); } - private Mono createOAuth2Client(Config config, Pair client) { + private Mono createOAuth2Client(Config config, Tuple2 client) { CreateOAuth2ClientRequest.CreateOAuth2ClientRequestBuilder builder = CreateOAuth2ClientRequest.builder() - .clientId(client.getLeft()) - .clientSecret(client.getRight()) + .clientId(client.getT1()) + .clientSecret(client.getT2()) .clientName(config.getClientName()) .identityZoneSubdomain(config.getIdentityZoneSubdomain()) .identityZoneId(config.getIdentityZoneId()); diff --git a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/SimpleCredentialGeneratorTest.java b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/SimpleCredentialGeneratorTest.java index 6094192..e1ac857 100644 --- a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/SimpleCredentialGeneratorTest.java +++ b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/SimpleCredentialGeneratorTest.java @@ -16,8 +16,8 @@ package org.springframework.cloud.appbroker.extensions.credentials; -import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.Test; +import reactor.test.StepVerifier; import static org.assertj.core.api.Assertions.assertThat; @@ -27,28 +27,40 @@ class SimpleCredentialGeneratorTest { void generateString() { SimpleCredentialGenerator generator = new SimpleCredentialGenerator(); - assertThat(generator.generateString(null, null, 12, true, false, false, false)) - .matches("^[A-Z]{12}$"); - assertThat(generator.generateString(null, null, 12, false, true, false, false)) - .matches("^[a-z]{12}$"); - assertThat(generator.generateString(null, null, 12, false, false, true, false)) - .matches("^[0-9]{12}$"); - assertThat(generator.generateString(null, null, 12, false, false, false, true)) - .matches("^[\\p{Punct}]{12}$"); + StepVerifier.create(generator.generateString(null, null, null, 12, true, false, false, false)) + .assertNext(s -> assertThat(s).matches("^[A-Z]{12}$")) + .verifyComplete(); - assertThat(generator.generateString(null, null, 12, true, true, true, true)) - .matches("^[a-zA-Z0-9\\p{Punct}]{12}$"); - assertThat(generator.generateString(null, null, 12, false, false, false, false)) - .matches("^[a-zA-Z0-9\\p{Punct}]{12}$"); + StepVerifier.create(generator.generateString(null, null, null, 12, false, true, false, false)) + .assertNext(s -> assertThat(s).matches("^[a-z]{12}$")) + .verifyComplete(); + + StepVerifier.create(generator.generateString(null, null, null, 12, false, false, true, false)) + .assertNext(s -> assertThat(s).matches("^[0-9]{12}$")) + .verifyComplete(); + + StepVerifier.create(generator.generateString(null, null, null, 12, false, false, false, true)) + .assertNext(s -> assertThat(s).matches("^[\\p{Punct}]{12}$")) + .verifyComplete(); + + StepVerifier.create(generator.generateString(null, null, null, 12, true, true, true, true)) + .assertNext(s -> assertThat(s).matches("^[a-zA-Z0-9\\p{Punct}]{12}$")) + .verifyComplete(); + + StepVerifier.create(generator.generateString(null, null, null, 12, false, false, false, false)) + .assertNext(s -> assertThat(s).matches("^[a-zA-Z0-9\\p{Punct}]{12}$")) + .verifyComplete(); } @Test void generateUser() { SimpleCredentialGenerator generator = new SimpleCredentialGenerator(); - Pair user = generator.generateUser(null, null, 10, true, true, true, true); - - assertThat(user.getLeft().length()).isEqualTo(10); - assertThat(user.getRight().length()).isEqualTo(10); + StepVerifier.create(generator.generateUser(null, null, null, 10, true, true, true, true)) + .assertNext(user -> { + assertThat(user.getT1().length()).isEqualTo(10); + assertThat(user.getT2().length()).isEqualTo(10); + }) + .verifyComplete(); } } \ No newline at end of file diff --git a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityBasicAuthCredentialProviderFactoryTest.java b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityBasicAuthCredentialProviderFactoryTest.java index 63d43da..a512402 100644 --- a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityBasicAuthCredentialProviderFactoryTest.java +++ b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityBasicAuthCredentialProviderFactoryTest.java @@ -16,18 +16,21 @@ package org.springframework.cloud.appbroker.extensions.credentials; -import org.apache.commons.lang3.tuple.Pair; +import java.util.Map; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.cloud.appbroker.deployer.BackingApplication; +import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import reactor.util.function.Tuples; -import java.util.Map; +import org.springframework.cloud.appbroker.deployer.BackingApplication; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -63,9 +66,9 @@ class SpringSecurityBasicAuthCredentialProviderFactoryTest { BackingApplication backingApplication = BackingApplication.builder() .build(); - when(credentialGenerator.generateUser(backingApplication.getName(), "service-instance-id", + when(credentialGenerator.generateUser(backingApplication.getName(), "service-instance-id", "basic", 8, true, false, true, false)) - .thenReturn(Pair.of("username", "password")); + .thenReturn(Mono.just(Tuples.of("username", "password"))); StepVerifier .create(provider.addCredentials(backingApplication, "service-instance-id")) @@ -83,15 +86,19 @@ class SpringSecurityBasicAuthCredentialProviderFactoryTest { @Test void deleteCredentials() { - BackingApplication backingApplication = BackingApplication.builder() + BackingApplication backingApplication = BackingApplication + .builder() .build(); + given(credentialGenerator.deleteUser(backingApplication.getName(), "service-instance-id", "basic")) + .willReturn(Mono.empty()); + StepVerifier .create(provider.deleteCredentials(backingApplication, "service-instance-id")) .expectNext(backingApplication) .verifyComplete(); - verify(credentialGenerator).deleteUser(backingApplication.getName(), "service-instance-id"); + verify(credentialGenerator).deleteUser(backingApplication.getName(), "service-instance-id", "basic"); verifyNoMoreInteractions(credentialGenerator); } } \ No newline at end of file diff --git a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityOAuth2CredentialProviderFactoryTest.java b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityOAuth2CredentialProviderFactoryTest.java index f6576cf..f450f51 100644 --- a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityOAuth2CredentialProviderFactoryTest.java +++ b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/SpringSecurityOAuth2CredentialProviderFactoryTest.java @@ -16,23 +16,25 @@ package org.springframework.cloud.appbroker.extensions.credentials; +import java.util.Map; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + import org.springframework.cloud.appbroker.deployer.BackingApplication; import org.springframework.cloud.appbroker.oauth2.CreateOAuth2ClientRequest; import org.springframework.cloud.appbroker.oauth2.CreateOAuth2ClientResponse; import org.springframework.cloud.appbroker.oauth2.DeleteOAuth2ClientRequest; import org.springframework.cloud.appbroker.oauth2.DeleteOAuth2ClientResponse; import org.springframework.cloud.appbroker.oauth2.OAuth2Client; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -69,9 +71,9 @@ class SpringSecurityOAuth2CredentialProviderFactoryTest { BackingApplication backingApplication = BackingApplication.builder() .build(); - when(credentialGenerator.generateString(backingApplication.getName(), "service-instance-id", + when(credentialGenerator.generateString(backingApplication.getName(), "service-instance-id", "oauth2", 8, true, false, true, false)) - .thenReturn("test-secret"); + .thenReturn(Mono.just("test-secret")); when(oAuth2Client.createClient(buildCreateOAuth2Request("test-id"))) .thenReturn(Mono.just(CreateOAuth2ClientResponse.builder().build())); @@ -93,9 +95,9 @@ class SpringSecurityOAuth2CredentialProviderFactoryTest { .build(); String clientId = backingApplication.getName() + "-" + "service-instance-id"; - when(credentialGenerator.generateString(backingApplication.getName(), "service-instance-id", + when(credentialGenerator.generateString(backingApplication.getName(), "service-instance-id", "oauth2", 8, true, false, true, false)) - .thenReturn("test-secret"); + .thenReturn(Mono.just("test-secret")); when(oAuth2Client.createClient(buildCreateOAuth2Request(clientId))) .thenReturn(Mono.just(CreateOAuth2ClientResponse.builder().build())); @@ -130,12 +132,15 @@ class SpringSecurityOAuth2CredentialProviderFactoryTest { when(oAuth2Client.deleteClient(buildDeleteOAuth2Request("test-id"))) .thenReturn(Mono.just(DeleteOAuth2ClientResponse.builder().build())); + given(credentialGenerator.deleteString(backingApplication.getName(), "service-instance-id", "oauth2")) + .willReturn(Mono.empty()); + StepVerifier .create(provider.deleteCredentials(backingApplication, "service-instance-id")) .expectNext(backingApplication) .verifyComplete(); - verify(credentialGenerator).deleteString(backingApplication.getName(), "service-instance-id"); + verify(credentialGenerator).deleteString(backingApplication.getName(), "service-instance-id", "oauth2"); verifyNoMoreInteractions(credentialGenerator); } @@ -151,12 +156,15 @@ class SpringSecurityOAuth2CredentialProviderFactoryTest { when(oAuth2Client.deleteClient(buildDeleteOAuth2Request(clientId))) .thenReturn(Mono.just(DeleteOAuth2ClientResponse.builder().build())); + given(credentialGenerator.deleteString(backingApplication.getName(), "service-instance-id", "oauth2")) + .willReturn(Mono.empty()); + StepVerifier .create(provider.deleteCredentials(backingApplication, "service-instance-id")) .expectNext(backingApplication) .verifyComplete(); - verify(credentialGenerator).deleteString(backingApplication.getName(), "service-instance-id"); + verify(credentialGenerator).deleteString(backingApplication.getName(), "service-instance-id", "oauth2"); verifyNoMoreInteractions(credentialGenerator); } diff --git a/spring-cloud-app-broker-security-credhub/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/CredHubCredentialsGenerator.java b/spring-cloud-app-broker-security-credhub/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/CredHubCredentialsGenerator.java new file mode 100644 index 0000000..cca9c67 --- /dev/null +++ b/spring-cloud-app-broker-security-credhub/src/main/java/org/springframework/cloud/appbroker/extensions/credentials/CredHubCredentialsGenerator.java @@ -0,0 +1,87 @@ +/* + * Copyright 2016-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.cloud.appbroker.extensions.credentials; + +import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + +import org.springframework.credhub.core.ReactiveCredHubOperations; +import org.springframework.credhub.support.CredentialDetails; +import org.springframework.credhub.support.SimpleCredentialName; +import org.springframework.credhub.support.password.PasswordCredential; +import org.springframework.credhub.support.password.PasswordParameters; +import org.springframework.credhub.support.password.PasswordParametersRequest; +import org.springframework.credhub.support.user.UserCredential; +import org.springframework.credhub.support.user.UserParametersRequest; + +public class CredHubCredentialsGenerator implements CredentialGenerator { + + private final ReactiveCredHubOperations credHubOperations; + + public CredHubCredentialsGenerator(ReactiveCredHubOperations credHubOperations) { + this.credHubOperations = credHubOperations; + } + + @Override + public Mono> generateUser(String applicationId, String serviceInstanceId, String descriptor, + int length, boolean includeUppercaseAlpha, boolean includeLowercaseAlpha, + boolean includeNumeric, boolean includeSpecial) { + return this.credHubOperations.credentials().generate(UserParametersRequest + .builder() + .name(new SimpleCredentialName(applicationId, serviceInstanceId, descriptor)) + .parameters(passwordParameters(length, includeUppercaseAlpha, includeLowercaseAlpha, includeNumeric, includeSpecial)) + .build(), UserCredential.class) + .map(CredentialDetails::getValue) + .map(userCredential -> Tuples.of(userCredential.getUsername(), userCredential.getPassword())); + } + + @Override + public Mono generateString(String applicationId, String serviceInstanceId, String descriptor, int length, + boolean includeUppercaseAlpha, boolean includeLowercaseAlpha, boolean includeNumeric, + boolean includeSpecial) { + return credHubOperations.credentials().generate(PasswordParametersRequest + .builder() + .name(new SimpleCredentialName(applicationId, serviceInstanceId, descriptor)) + .parameters(passwordParameters(length, includeUppercaseAlpha, includeLowercaseAlpha, includeNumeric, includeSpecial)) + .build(), PasswordCredential.class) + .map(CredentialDetails::getValue) + .map(PasswordCredential::getPassword); + } + + @Override + public Mono deleteUser(String applicationId, String serviceInstanceId, String descriptor) { + return credHubOperations.credentials().deleteByName(new SimpleCredentialName(applicationId, serviceInstanceId, descriptor)); + } + + @Override + public Mono deleteString(String applicationId, String serviceInstanceId, String descriptor) { + return credHubOperations.credentials().deleteByName(new SimpleCredentialName(applicationId, serviceInstanceId, descriptor)); + } + + private PasswordParameters passwordParameters(int length, boolean includeUppercaseAlpha, boolean includeLowercaseAlpha, + boolean includeNumeric, boolean includeSpecial) { + return PasswordParameters + .builder() + .length(length) + .excludeUpper(!includeUppercaseAlpha) + .excludeLower(!includeLowercaseAlpha) + .excludeNumber(!includeNumeric) + .includeSpecial(includeSpecial) + .build(); + } +} diff --git a/spring-cloud-app-broker-security-credhub/src/main/java/org/springframework/cloud/appbroker/workflow/binding/CredHubPersistingCreateServiceInstanceAppBindingWorkflow.java b/spring-cloud-app-broker-security-credhub/src/main/java/org/springframework/cloud/appbroker/workflow/binding/CredHubPersistingCreateServiceInstanceAppBindingWorkflow.java index b61c090..426be21 100644 --- a/spring-cloud-app-broker-security-credhub/src/main/java/org/springframework/cloud/appbroker/workflow/binding/CredHubPersistingCreateServiceInstanceAppBindingWorkflow.java +++ b/spring-cloud-app-broker-security-credhub/src/main/java/org/springframework/cloud/appbroker/workflow/binding/CredHubPersistingCreateServiceInstanceAppBindingWorkflow.java @@ -27,6 +27,7 @@ import org.springframework.cloud.servicebroker.model.binding.CreateServiceInstan import org.springframework.cloud.servicebroker.model.binding.CreateServiceInstanceBindingRequest; import org.springframework.core.annotation.Order; import org.springframework.credhub.core.ReactiveCredHubOperations; +import org.springframework.credhub.support.ServiceInstanceCredentialName; import org.springframework.credhub.support.json.JsonCredentialRequest; import org.springframework.util.CollectionUtils; @@ -37,7 +38,7 @@ public class CredHubPersistingCreateServiceInstanceAppBindingWorkflow implements private static final String CREDENTIALS_KEY = "credhub-ref"; - private static final String CREDENTIALS_VALUE_TEMPLATE = "/c/%s/%s/%s/credentials-json"; + private static final String CREDENTIALS_NAME = "credentials-json"; private final String appName; @@ -80,14 +81,17 @@ public class CredHubPersistingCreateServiceInstanceAppBindingWorkflow implements .builder() .async(response.isAsync()) .bindingExisted(response.isBindingExisted()) - .credentials(CREDENTIALS_KEY, formatCredentials(request)) + .credentials(CREDENTIALS_KEY, ServiceInstanceCredentialName + .builder() + .serviceBrokerName(this.appName) + .serviceOfferingName(request.getServiceDefinitionId()) + .serviceBindingId(request.getBindingId()) + .credentialName(CREDENTIALS_NAME) + .build() + .getName()) .operation(response.getOperation()) .syslogDrainUrl(response.getSyslogDrainUrl()) .volumeMounts(response.getVolumeMounts())); } - private String formatCredentials(CreateServiceInstanceBindingRequest request) { - return String.format(CREDENTIALS_VALUE_TEMPLATE, this.appName, request.getServiceDefinitionId(), request.getBindingId()); - } - } diff --git a/spring-cloud-app-broker-security-credhub/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/CredHubCredentialsGeneratorTest.java b/spring-cloud-app-broker-security-credhub/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/CredHubCredentialsGeneratorTest.java new file mode 100644 index 0000000..b49d7dc --- /dev/null +++ b/spring-cloud-app-broker-security-credhub/src/test/java/org/springframework/cloud/appbroker/extensions/credentials/CredHubCredentialsGeneratorTest.java @@ -0,0 +1,139 @@ +/* + * Copyright 2016-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.cloud.appbroker.extensions.credentials; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import org.springframework.credhub.core.ReactiveCredHubOperations; +import org.springframework.credhub.core.credential.ReactiveCredHubCredentialOperations; +import org.springframework.credhub.support.CredentialDetails; +import org.springframework.credhub.support.CredentialType; +import org.springframework.credhub.support.SimpleCredentialName; +import org.springframework.credhub.support.password.PasswordCredential; +import org.springframework.credhub.support.password.PasswordParameters; +import org.springframework.credhub.support.user.UserCredential; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +@ExtendWith(MockitoExtension.class) +class CredHubCredentialsGeneratorTest { + + @Mock + private ReactiveCredHubOperations credHubOperations; + + @Mock + private ReactiveCredHubCredentialOperations credHubCredentialOperations; + + private CredHubCredentialsGenerator generator; + + @BeforeEach + void setUp() { + this.generator = new CredHubCredentialsGenerator(credHubOperations); + } + + @Test + void passwordParameters() { + PasswordParameters params = ReflectionTestUtils.invokeMethod(generator, "passwordParameters", 42, false, false, false, false); + assertThat(params.getLength()).isEqualTo(42); + assertThat(params.getExcludeUpper()).isTrue(); + assertThat(params.getExcludeLower()).isTrue(); + assertThat(params.getExcludeNumber()).isTrue(); + assertThat(params.getIncludeSpecial()).isFalse(); + } + + @Test + void generateUser() { + CredentialDetails credentialDetails = new CredentialDetails("id", + new SimpleCredentialName("app-service"), CredentialType.PASSWORD, + new UserCredential("username", "password")); + + given(this.credHubOperations.credentials()) + .willReturn(credHubCredentialOperations); + + given(this.credHubCredentialOperations.generate(any(), eq(UserCredential.class))) + .willReturn(Mono.just(credentialDetails)); + + StepVerifier.create(generator.generateUser("foo", "bar", "hello", 12, false, false, false, false)) + .assertNext(tuple2 -> { + assertThat(tuple2.getT1()).isEqualTo("username"); + assertThat(tuple2.getT2()).isEqualTo("password"); + }) + .verifyComplete(); + } + + @Test + void generateString() { + CredentialDetails credentialDetails = new CredentialDetails("id", + new SimpleCredentialName("app-service"), CredentialType.PASSWORD, + new PasswordCredential("password")); + + given(this.credHubOperations.credentials()) + .willReturn(credHubCredentialOperations); + + given(this.credHubCredentialOperations.generate(any(), eq(PasswordCredential.class))) + .willReturn(Mono.just(credentialDetails)); + + StepVerifier.create(generator.generateString("foo", "bar", "hello", 12, false, false, false, false)) + .assertNext(password -> assertThat(password).isEqualTo("password")) + .verifyComplete(); + } + + @Test + void deleteUser() { + given(this.credHubOperations.credentials()) + .willReturn(credHubCredentialOperations); + + given(this.credHubCredentialOperations.deleteByName(new SimpleCredentialName("foo", "bar", "hello"))) + .willReturn(Mono.empty()); + + StepVerifier.create(generator.deleteUser("foo", "bar", "hello")) + .verifyComplete(); + + verify(credHubCredentialOperations).deleteByName(new SimpleCredentialName("foo", "bar", "hello")); + verifyNoMoreInteractions(credHubOperations); + verifyNoMoreInteractions(credHubCredentialOperations); + } + + @Test + void deleteString() { + given(this.credHubOperations.credentials()) + .willReturn(credHubCredentialOperations); + + given(this.credHubCredentialOperations.deleteByName(new SimpleCredentialName("foo", "bar", "hello"))) + .willReturn(Mono.empty()); + + StepVerifier.create(generator.deleteString("foo", "bar", "hello")) + .verifyComplete(); + + verify(credHubCredentialOperations).deleteByName(new SimpleCredentialName("foo", "bar", "hello")); + verifyNoMoreInteractions(credHubOperations); + verifyNoMoreInteractions(credHubCredentialOperations); + } + +} \ No newline at end of file