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
This commit is contained in:
Roy Clarkson
2018-12-11 13:44:13 -05:00
committed by Scott Frederick
parent c33bb39953
commit c504576793
12 changed files with 422 additions and 133 deletions

View File

@@ -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);
}
}

View File

@@ -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);
});
}

View File

@@ -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<String, String> generateUser(String applicationId, String serviceInstanceId,
int length, boolean includeUppercaseAlpha,
boolean includeLowercaseAlpha, boolean includeNumeric,
boolean includeSpecial);
Mono<Tuple2<String, String>> 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<String> 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<Void> deleteUser(String applicationId, String serviceInstanceId, String descriptor) {
return Mono.empty();
}
default void deleteString(String applicationId, String serviceInstanceId) {
default Mono<Void> deleteString(String applicationId, String serviceInstanceId, String descriptor) {
return Mono.empty();
}
}

View File

@@ -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<String, String> 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<Tuple2<String, String>> 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<String> 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));
}
}

View File

@@ -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<CredentialGenerationConfig> {
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<BackingApplication> addCredentials(BackingApplication backingApplication,
String serviceInstanceGuid) {
Pair<String, String> 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<BackingApplication> 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<String, String> 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<Tuple2<String, String>> 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<String, String> user) {
Map<String, String> 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<Void> addUserToEnvironment(BackingApplication backingApplication, Tuple2<String, String> 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();
});
}
}

View File

@@ -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<SpringSecurityOAuth2CredentialProviderFactory.Config> {
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<BackingApplication> 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<Pair<String, String>> generateCredentials(Config config,
BackingApplication backingApplication,
String serviceInstanceGuid) {
String id = generateClientId(config, backingApplication, serviceInstanceGuid);
String secret = generateClientSecret(config, backingApplication, serviceInstanceGuid);
Pair<String, String> client = Pair.of(id, secret);
return Mono.just(client);
private Mono<Tuple2<String, String>> 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<Pair<String, String>> addClientToEnvironment(Config config,
private Mono<Tuple2<String, String>> addClientToEnvironment(Config config,
BackingApplication backingApplication,
Pair<String, String> client) {
Tuple2<String, String> client) {
Map<String, String> 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<String> 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<String> 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<CreateOAuth2ClientResponse> createOAuth2Client(Config config, Pair<String, String> client) {
private Mono<CreateOAuth2ClientResponse> createOAuth2Client(Config config, Tuple2<String, String> 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());

View File

@@ -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<String, String> 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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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<Tuple2<String, String>> 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<String> 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<Void> deleteUser(String applicationId, String serviceInstanceId, String descriptor) {
return credHubOperations.credentials().deleteByName(new SimpleCredentialName(applicationId, serviceInstanceId, descriptor));
}
@Override
public Mono<Void> 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();
}
}

View File

@@ -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());
}
}

View File

@@ -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<UserCredential> credentialDetails = new CredentialDetails<UserCredential>("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<PasswordCredential> credentialDetails = new CredentialDetails<PasswordCredential>("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);
}
}