Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

buildscript {
ext {
springBootVersion = '2.0.3.RELEASE'
springBootVersion = '2.1.6.RELEASE'
}
repositories {
mavenCentral()
Expand Down Expand Up @@ -51,11 +51,12 @@ dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.hateoas:spring-hateoas:0.24.0.RELEASE')
compile('org.springframework.hateoas:spring-hateoas:0.25.1.RELEASE')
compile('org.springframework.credhub:spring-credhub-starter:2.0.1.RELEASE')

runtime('org.springframework.boot:spring-boot-devtools')

compile('org.springframework.cloud:spring-cloud-starter-open-service-broker-webmvc:2.0.1.RELEASE')
compile('org.springframework.cloud:spring-cloud-starter-open-service-broker:3.0.3.RELEASE')

compile('org.springframework.boot:spring-boot-starter-data-jpa')
runtime('org.hsqldb:hsqldb')
Expand Down
64 changes: 64 additions & 0 deletions deploy/cloudfoundry/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,70 @@ routes: bookstore-service-broker.apps.example.com
#0 running 2018-02-13T21:58:44Z 0.0% 290.8M of 1G 144.7M of 1G
----

== Deploy the service broker application with Secure Service Credential support

The service broker does support storing credentials in Cloud Foundry`s Credhub optionally. The broker needs UAA client_credentials, which are authorized to `/c/bookstore-service-broker/bdb1be2e-360b-495c-8115-d7697f9c6a9e/*` in Credhub, to access and store credentials securely.
More information about Secure Service Credential in Cloud Foundry can be found here: https://github.com/cloudfoundry-incubator/credhub/blob/master/docs/secure-service-credentials.md

=== Create client credentials in UAA

----
$ uaac target uaa.<system domain>
$ uaac token client get admin -s <password> # UAA Admin Client
$ uaac client add bookstore-service-broker --name bookstore-service-broker --authorized_grant_types client_credentials --authorities "credhub.write","credhub.read"
----

=== Set permissions in Credhub

A documentation how to access Credhub in Pivotal PAS from Operations Manager can be found here: https://community.pivotal.io/s/article/how-to-login-and-access-credhub-in-pcf

----
$ credhub login -s credhub.service.cf.internal:8844 --client-name credhub_admin_client --client-secret <secret> --skip-tls-validation
$ credhub set-permission -a uaa-client:bookstore-service-broker -p /c/bookstore-service-broker/bdb1be2e-360b-495c-8115-d7697f9c6a9e/* -o read,write,delete,read_acl,write_acl
----

==== Verify Credhub permissions
----
$ credhub login -s credhub.service.cf.internal:8844 --client-name bookstore-service-broker --client-secret <secret> --skip-tls-validation
$ credhub set -n /c/bookstore-service-broker/bdb1be2e-360b-495c-8115-d7697f9c6a9e/test -t value -v test
$ credhub delete -n '/c/bookstore-service-broker/bdb1be2e-360b-495c-8115-d7697f9c6a9e/test'
----

=== Push Service Broker

Modify value of `CREDHUB_CLIENT_SECRET` in `deploy/cloudfoundry/manifest-credhub.yml` to match your chosen client_secret for the service broker.
If necessary, `CREDHUB_URL`, `CREDHUB_CLIENT_ID` and `UAA_TOKEN_URI` can be configured optionally.

----
$ cf push -f deploy/cloudfoundry/manifest-credhub.yml
Pushing from manifest to org sample / space test as [email protected]...
Using manifest file deploy/cloudfoundry/manifest.yml
Getting app info...
Creating app with these attributes...
+ name: bookstore-service-broker
path: build/libs/bookstore-service-broker-0.0.1.BUILD-SNAPSHOT.jar
+ memory: 1G
env:
CREDHUB_CLIENT_SECRET
SPRING_PROFILES_ACTIVE
routes:
+ bookstore-service-broker.apps.example.com

...

name: bookstore-service-broker
requested state: started
instances: 1/1
usage: 1G x 1 instances
routes: bookstore-service-broker.apps.example.com

...

state since cpu memory disk details
#0 running 2018-02-13T21:58:44Z 0.0% 290.8M of 1G 144.7M of 1G
----


== Verify the service broker application

Note the value of the `route` row in the output from the command above.
Expand Down
8 changes: 8 additions & 0 deletions deploy/cloudfoundry/manifest-credhub.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
applications:
- name: bookstore-service-broker
memory: 1G
path: ../../build/libs/bookstore-service-broker-0.0.1.BUILD-SNAPSHOT.jar
env:
SPRING_PROFILES_ACTIVE: cloud,credhub
CREDHUB_CLIENT_SECRET: secret
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-bin.zip
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2002-2019 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
*
* https://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.sample.bookstore.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.sample.bookstore.servicebroker.credhub.CredhubCreateServiceInstanceBinding;
import org.springframework.cloud.sample.bookstore.servicebroker.credhub.CredhubDeleteServiceInstanceBinding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.credhub.autoconfig.CredHubTemplateAutoConfiguration;
import org.springframework.credhub.core.CredHubOperations;

@Configuration
@AutoConfigureAfter(CredHubTemplateAutoConfiguration.class)
@ConditionalOnClass(CredHubOperations.class)
@ConditionalOnBean(CredHubOperations.class)
public class CredHubAutoConfiguration {

@Value("${spring.application.name}")
private String appName;

@Bean
public CredhubCreateServiceInstanceBinding credhubPersistingCreateServiceInstanceAppBindingWorkflow(CredHubOperations credHubOperations) {
return new CredhubCreateServiceInstanceBinding(credHubOperations, appName);
}

@Bean
public CredhubDeleteServiceInstanceBinding credhubPersistingDeleteServiceInstanceAppBindingWorkflow(CredHubOperations credHubOperations) {
return new CredhubDeleteServiceInstanceBinding(credHubOperations, appName);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2002-2019 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
*
* https://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.sample.bookstore.servicebroker.credhub;

import org.springframework.credhub.support.ServiceInstanceCredentialName;
import reactor.core.publisher.Mono;
import reactor.util.Logger;
import reactor.util.Loggers;

class CredHubPersistingWorkflow {

private static final Logger LOG = Loggers.getLogger(CredHubPersistingWorkflow.class);

private static final String CREDENTIALS_NAME = "credentials-json";
private final String appName;

CredHubPersistingWorkflow(String appName) {
this.appName = appName;
}

Mono<ServiceInstanceCredentialName> buildCredentialName(String serviceDefinitionId, String bindingId) {
LOG.debug("Building credentials name for service_id '{}' and binding_id '{}'", serviceDefinitionId, bindingId);
return Mono.just(ServiceInstanceCredentialName.builder()
.serviceBrokerName(this.appName)
.serviceOfferingName(serviceDefinitionId)
.serviceBindingId(bindingId)
.credentialName(CREDENTIALS_NAME)
.build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package org.springframework.cloud.sample.bookstore.servicebroker.credhub;

import org.springframework.cloud.servicebroker.model.binding.BindResource;
import org.springframework.cloud.servicebroker.model.binding.CreateServiceInstanceAppBindingResponse;
import org.springframework.cloud.servicebroker.model.binding.CreateServiceInstanceBindingRequest;
import org.springframework.credhub.core.CredHubOperations;
import org.springframework.credhub.support.CredentialName;
import org.springframework.credhub.support.json.JsonCredentialRequest;
import org.springframework.credhub.support.permissions.Operation;
import org.springframework.credhub.support.permissions.Permission;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;
import reactor.util.Logger;
import reactor.util.Loggers;

public class CredhubCreateServiceInstanceBinding extends CredHubPersistingWorkflow{

private static final Logger LOG = Loggers.getLogger(CredhubCreateServiceInstanceBinding.class);

private static final String CREDHUB_REF_KEY = "credhub-ref";

private static final String CREDENTIAL_CLIENT_ID = "credential_client_id";

private final CredHubOperations credHubOperations;

public CredhubCreateServiceInstanceBinding(CredHubOperations credHubOperations, String appName) {
super(appName);
this.credHubOperations = credHubOperations;
}

public Mono<CreateServiceInstanceAppBindingResponse.CreateServiceInstanceAppBindingResponseBuilder> buildResponse(CreateServiceInstanceBindingRequest request,
CreateServiceInstanceAppBindingResponse.CreateServiceInstanceAppBindingResponseBuilder responseBuilder) {
return Mono.just(responseBuilder.build())
.flatMap(response -> {
if (!CollectionUtils.isEmpty(response.getCredentials())) {
return buildCredentialName(request.getServiceDefinitionId(), request.getBindingId())
.flatMap(credentialName -> persistBindingCredentials(request, response, credentialName)
.doOnRequest(l -> LOG.debug("Storing binding credentials with name '{}' in CredHub", credentialName.getName()))
.doOnSuccess(r -> LOG.debug("Finished storing binding credentials with name '{}' in CredHub", credentialName.getName()))
.doOnError(exception -> LOG.error("Error storing binding credentials with name '{}' in CredHub with error: {}",
credentialName.getName(), exception.getMessage())));
}
return Mono.just(responseBuilder);
});
}

private Mono<CreateServiceInstanceAppBindingResponse.CreateServiceInstanceAppBindingResponseBuilder> persistBindingCredentials(CreateServiceInstanceBindingRequest request,
CreateServiceInstanceAppBindingResponse response,
CredentialName credentialName) {
return writeCredential(response, credentialName)
.then(writePermissions(request, credentialName))
.thenReturn(buildReplacementBindingResponse(response, credentialName));
}

private Mono<Void> writeCredential(CreateServiceInstanceAppBindingResponse response,
CredentialName credentialName) {
return Mono.fromCallable(() -> {
credHubOperations.credentials()
.write(JsonCredentialRequest.builder()
.name(credentialName)
.value(response.getCredentials())
.build());
return null;
});
}

private Mono<Void> writePermissions(CreateServiceInstanceBindingRequest request,
CredentialName credentialName) {
return Mono.fromCallable(() -> {
BindResource bindResource = request.getBindResource();

if (bindResource.getAppGuid() != null) {
Permission permission = Permission.builder()
.app(bindResource.getAppGuid())
.operation(Operation.READ)
.build();
credHubOperations.permissionsV2().addPermissions(credentialName, permission);
}

if (bindResource.getProperty(CREDENTIAL_CLIENT_ID) != null) {
Permission permission = Permission.builder()
.client(bindResource.getProperty(CREDENTIAL_CLIENT_ID).toString())
.operation(Operation.READ)
.build();
credHubOperations.permissionsV2().addPermissions(credentialName, permission);
}

return null;
});
}

private CreateServiceInstanceAppBindingResponse.CreateServiceInstanceAppBindingResponseBuilder buildReplacementBindingResponse(CreateServiceInstanceAppBindingResponse response,
CredentialName credentialName) {
return CreateServiceInstanceAppBindingResponse.builder()
.async(response.isAsync())
.bindingExisted(response.isBindingExisted())
.credentials(CREDHUB_REF_KEY, credentialName.getName())
.operation(response.getOperation())
.syslogDrainUrl(response.getSyslogDrainUrl())
.volumeMounts(response.getVolumeMounts());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.springframework.cloud.sample.bookstore.servicebroker.credhub;

import org.springframework.cloud.servicebroker.model.binding.DeleteServiceInstanceBindingRequest;
import org.springframework.cloud.servicebroker.model.binding.DeleteServiceInstanceBindingResponse;
import org.springframework.credhub.core.CredHubOperations;
import org.springframework.credhub.support.CredentialName;
import org.springframework.credhub.support.ServiceInstanceCredentialName;
import reactor.core.publisher.Mono;
import reactor.util.Logger;
import reactor.util.Loggers;

public class CredhubDeleteServiceInstanceBinding extends CredHubPersistingWorkflow {

private static final Logger LOG = Loggers.getLogger(CredhubDeleteServiceInstanceBinding.class);

private final CredHubOperations credHubOperations;

public CredhubDeleteServiceInstanceBinding(CredHubOperations credHubOperations, String appName) {
super(appName);
this.credHubOperations = credHubOperations;
}

public Mono<DeleteServiceInstanceBindingResponse.DeleteServiceInstanceBindingResponseBuilder> buildResponse(DeleteServiceInstanceBindingRequest request, DeleteServiceInstanceBindingResponse.DeleteServiceInstanceBindingResponseBuilder responseBuilder) {
LOG.debug("Preparing delete of credentials for service_id '{}' and binding_id '{}'", request.getServiceDefinitionId(), request.getBindingId());

Mono<ServiceInstanceCredentialName> credentialNameMono = buildCredentialName(request.getServiceDefinitionId(), request.getBindingId());
return credentialNameMono
.filter(this::credentialExists)
.map(this::deleteBindingCredentials)
.thenReturn(responseBuilder);
}

private boolean credentialExists(CredentialName credentialName) {
if (credentialName == null) {
return false;
}
return !credHubOperations.credentials().findByName(credentialName).isEmpty();
}

private Mono<Void> deleteBindingCredentials(CredentialName credentialName) {
LOG.debug("Deleting credentials with name '{}'", credentialName.getName());
credHubOperations.credentials().deleteByName(credentialName);
return Mono.never();
}

}
Loading