Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreIn this article, we will dive in how to bind a Spring application to HashiCorp’s Vault service broker on Cloud Foundry.
Spring Boot provides a lot of autoconfiguration and external binding features, some of which are relevant to Cloud Foundry, and many of which are not. Spring Cloud Connectors is a library that you can use in your application if you want to create your own components programmatically, but it doesn’t do anything “magical” by itself.
Spring Cloud Connectors lays the foundation for connectors that integrate with various Cloud services. It has ships components that allow correct middleware configuration. Using CloudFoundry’s java buildpack comes with an “auto-reconfiguration” feature that tries to ease the burden of moving simple applications to the cloud.
The key to correctly configuring middleware services, like HashiCorp’s Vault, is to understand what each of these tools provides, how they influence each other at runtime. The goal should be a smooth transition from local execution of an application on a developer’s desktop to a test environment in Cloud Foundry, and ultimately to production in CloudFoundry (or otherwise) with no changes in source code or packaging, per the twelve-factor application guidelines.
Spring Cloud Vault connectors ship with four libraries:
spring-cloud-vault-connector-core
provides common bits used by the other three libraries.
spring-cloud-vault-spring-connector
configures Spring Framework and Spring Boot (Spring Cloud)-based applications with a Vault connector.
spring-cloud-vault-cloudfoundry-connector
is required to pick up CloudFoundry’s service configuration and provide the service configuration to the actual Spring connector.
spring-cloud-vault-localconfig-connector
lets you provide a property-based configuration if you want to configure the service yourself, e.g. within a CI or local runtime environment without providing additional code to distinguish between cloud and non-cloud runtimes.
If you’re using Spring Boot and Spring Cloud Vault, then you can actually get away with not doing anything else besides dropping in the Spring and CloudFoundry connector.
The Spring connector comes with a bootstrap configuration that uses the token and endpoint from the service configuration to configure Spring’s Vault client. Spring Cloud Vault configures a property source by default that points to ${spring.application.name}/${profile-name}
. HashiCorp’s service broker configures generic backends in the application, space and organization namespace. The connector picks up the backends and shared backends (in the mentioned order) to obtain its configuration properties from these backends.
@SpringBootApplication
public class HelloWorldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWorldApplication.class, args);
}
static class MyComponent {
public MyComponent(@Value("${some-property}") String someProperty, (1)
VaultOperations vaultOperations) { (2)
// …
}
}
}
Inject a configuration property directly. Spring attempts to resolve the property from its Environment
that is configured with Vault’s PropertySource
s.
Inject VaultOperations
configured to use the connected Vault service.
If your application is purely Spring Framework-based or a Spring Boot application without Spring Cloud Vault, then enabling @ServiceScan
on one of your configuration classes is sufficient to pick up authentication and endpoint.
public class CloudFoundryApplication {
@Configuration
@ServiceScan (1)
static class VaultConfig {
}
public static void main(String[] args) {
GenericApplicationContext ctx =
new AnnotationConfigApplicationContext(VaultConnectorsConfig.class,
VaultConfig.class);
ctx.start();
VaultOperations vaultOperations = ctx.getBean(VaultOperations.class); (2)
ctx.stop();
}
}
@ServiceScan
enables scanning for services that are bound to your application. It creates beans for all found services.
VaultOperations
was created by the service scan so you can use it directly in your application.
Do you rather want to configure your bean yourself without activating @ServiceScan
or your want to provide a customized client configuration? Then use Cloud
directly. The Cloud
object gives you access to the service information and lets you create VaultTemplate
.
@Configuration
class VaultConfig {
@Bean
public Cloud cloud() { (1)
return new CloudFactory().getCloud();
}
@Bean
public SslConfiguration sslConfiguration() { (2)
return SslConfiguration.forTrustStore(…);
}
@Bean
VaultOperations vaultOperations() { (3)
VaultServiceInfo vaultServiceInfo = (VaultServiceInfo) cloud()
.getServiceInfos(VaultOperations.class).get(0);
VaultServiceConnectorConfig config = VaultServiceConnectorConfig.builder()
.sslConfiguration(sslConfiguration())
.build();
return cloud().getSingletonServiceConnector(VaultOperations.class, config);
}
}
The Cloud
object parses bound service. Registering it as bean requires parsing onlly once without the need to re-parse the services each time you need a Cloud
object.
Going forward with your configuration, it can be required to customize SSL or client configuration options. Here’s an example how to supply a trust store for Spring’s Vault client.
The cloud object exposes methods to create service connectors. Pass the VaultServiceConnectorConfig
along the VaultOperations
service type to these methods to create a template API instance. The Cloud
object gives you also access to the service information carrying details about authentication and the managed Vault backends.
HashiCorp provides a service broker to configure Vault services that can be bound to your application. It uses Token-based authentication and spins up a couple of backends. These are, in particular:
a generic
secret backend exclusive to the application instances
a transit
backend exclusive to the application instances
a shared generic
secret backend in the scope of your space
a shared generic
secret backend in the scope of your organization
a set of policies per application to e.g. restrict the shared org backend to read-only for the application.
Details about the service instance are provided via VCAP_SERVICES
, an environment variable visible to your application. Typically, it would look like:
{
"hashicorp-vault": [
{
"credentials": {
"address": "https://your-server:8200/",
"auth": {
"token": "00000000-0000-0000-0000-000000000000"
},
"backends": {
"generic": "cf/20fffe9d-d8d1-4825-9977-1426840a13db/secret",
"transit": "cf/20fffe9d-d8d1-4825-9977-1426840a13db/transit"
},
"backends_shared": {
"organization": "cf/1a558498-59ad-488c-b395-8b983aacb7da/secret",
"space": "cf/d007583f-5617-4b02-a5a7-550648827cfa/secret"
}
},
"label": "hashicorp-vault",
"name": "hashicorp-vault",
}
]
}
The token is managed and refreshed by the service broker and should not be touched by the application. Revoking the token will shut down access of all your application instances within the bound application.
Oh, right, thanks for the reminder. Once in a while you want to test your application locally before deploying it to the Cloud or you don’t want to use the service bindings. Spring Cloud Vault LocalConfig Connector fits exactly this purpose. It allows you to specify a configuration property that is picked up by the connector and provides the same set of configuration keys as VCAP_SERVICES
on CloudFoundry.
Bootstrap configuration requires at least two keys set:
spring.cloud.appId
: Application identifier name. Any descriptive application name (maybe even ${spring.application.name}
) will do the job.
A key below the spring.cloud.
namespace set to a HTTP/HTTPS url.
spring.application.name=…
spring.cloud.appId=your-app-id spring.cloud.my-vault-service=http://localhost:8200?token=my-token &backend.generic=cf/secret &backend.transit=cf/transit &shared_backend.space=cf/space
Or written as YAML:
spring.application.name: …
spring.cloud:
appId: your-app-id
my-vault-service: http://localhost:8200?token=my-token
&backend.generic=cf/secret
&backend.transit=cf/transit
&shared_backend.space=cf/space
You might have noticed that the URL looks a bit different than your usual Vault endpoint prefix URL. The connector requires additional configuration options which are represented as query parameters. The connector only uses scheme, host and port to configure the endpoint. The additional query parameters represent the keys which are usually transported via VCAP_SERVICES
:
token
the authentication token (required)
backend.(.*)
Context path to a dedicated backend. The property key after backend.
represents the dedicated backend name.
shared_backend.(.*)
Context path to a shared backend. The property key after shared_backend.
represents the shared backend name.
You can configure multiple backends and shared backends.
Yes, you can! In scenarios where you want to keep more control over what’s going on, or you want to omit additional dependencies there’s another possibility how you can connect to Vault. Spring Boot ships with CloudFoundryVcapEnvironmentPostProcessor
that exports CloudFoundry’s VCAP
variables through property sources to your application. These properties are prefixed with vcap.application
for VCAP_APPLICATION
respective vcap.services
for VCAP_SERVICES
. You can reference VCAP properties in your configuration like any other property.
Configuring a Vault connection through VCAP_SERVICES
requires knowledge over the actual structure of VCAP_SERVICES
. Configuring the URL and token authentication are the only two properties required to get Spring Boot with Spring Cloud Vault working. From the example above, your bootstrap configuration would look like:
spring.application.name=…
spring.cloud.vault.url=${vcap.services.hashicorp-vault.credentials.address}
spring.cloud.vault.token=${vcap.services.hashicorp-vault.auth.token}
spring.cloud.vault.generic.backend=${vcap.services.hashicorp-vault.credentials.backend.generic}
Or written as YAML:
spring.application.name: …
spring.cloud.vault:
url: ${vcap.services.hashicorp-vault.credentials.address}
token: ${vcap.services.hashicorp-vault.auth.token}
generic.backend: ${vcap.services.hashicorp-vault.credentials.backend.generic}
You have full control without any additional auto-configuration library. Using properties allows you to leverage profiles for conditional activation.
Spring Vault: https://projects.spring.io/spring-vault/
Spring Cloud Vault: https://cloud.spring.io/spring-cloud-vault/
Spring Cloud Connectors: http://cloud.spring.io/spring-cloud-connectors/
Spring Vault and Spring Cloud Vault samples: https://github.com/mp911de/spring-cloud-vault-config-samples
Project repository: https://github.com/pivotal-cf/spring-cloud-vault-connector
Dave Syer's excellent article aboud bound data services on CloudFoundry: https://spring.io/blog/2015/04/27/binding-to-data-services-with-spring-boot-in-cloud-foundry