Skip to content

A simple example of an integration between a Spring MVC application powered Spring Boot 2 and Spring Cloud Finchley RC2 and Hashicorp Vault.

Notifications You must be signed in to change notification settings

pacphi/cloudfoundry-with-vault-demo

 
 

Repository files navigation

Using HashiCorp Vault with Cloud Foundry

This is a simple example of an integration between a Spring MVC application powered Spring Boot 2 and Spring Cloud Finchley RC2 and Hashicorp Vault.

Credit to Zoltan Altfatter.

Prerequisites

  • CF CLI 6.37.0 or better if you want to push the application to a Cloud Foundry (CF) instance
  • httpie 0.9.9 or better to simplify interaction with API endpoints
  • Hashicorp Vault 0.12.0 or better
  • Java JDK 1.8u172 or better
  • jq 1.5 or better to
  • Maven 3.5.3 or better
  • ngrok 2.28 or better to expose local servers behind NATs and firewalls to the public internet over secure tunnels

Clone

git clone https://github.com/pacphi/cloudfoundry-with-vault-demo.git 

How to build

cd cloudfoundry-with-vault-demo mvn clean package 

How to start and prepare Vault

  1. Start vault
vault server -config inmemory.conf
  1. In another terminal set the VAULT_ADDR before initializing Vault
export VAULT_ADDR=http://127.0.0.1:8200
  1. Initialize vault
vault init -key-shares=5 -key-threshold=2
  1. Copy the Initial Root Token

We will need it later.

export VAULT_TOKEN=<token>

Vault requires an authenticated access to proceed from here on. Vault uses tokens as generic authentication on its transport level.

  1. Vault is in sealed mode, let's unseal it
vault operator unseal <key> vault operator unseal <key> 
  1. Verify that Vault is in unsealed mode
vault status | grep Sealed Sealed: false
  1. Write a secret into the secret backend
vault write secret/vault-demo message='I find your lack of faith disturbing.'

How to run Locally

Token based authentication

We'll authenticate using the root token we exported as an environment variable.

Don't do this in production!

  1. Start the application in another terminal
export VAULT_ADDR=http://127.0.0.1:8200 export VAULT_TOKEN=<token> java -jar target/cloudfoundry-with-vault-demo-0.0.1-SNAPSHOT.jar --spring.cloud.vault.token=`echo $VAULT_TOKEN`
  1. Make a GET request to http://localhost:8080
http :8080 message: I find your lack of faith disturbing.
  1. Update the secret inside Vault
vault write secret/vault-demo message='Now, young Skywalker, you will die.'
  1. Verify that the application still has the old secret
http :8080 message: I find your lack of faith disturbing.
  1. Send refresh command to the application
http post :8080/actuator/refresh
  1. Verify that the application knows about the latest secret
http :8080 message:'Now, young Skywalker, you will die.'

AppRole based authentication

The AppRole auth method allows machines or apps to authenticate with Vault-defined roles.

  1. Enable the AppRole auth method
vault auth enable approle
  1. Set policy to allow AppRole to access key-value pairs
vault policy write vault-demo vault-demo.hcl
  1. Setup a new role
vault write auth/approle/role/vault-demo \ secret_id_ttl=120m \ token_num_uses=10 \ token_ttl=60m \ token_max_ttl=120m \ secret_id_num_uses=10 \ policies="vault-demo"
  1. Get role-id and secret-id
vault read auth/approle/role/vault-demo/role-id vault write -f auth/approle/role/vault-demo/secret-id
  1. Capture role-id and secret-id as environment variables
export VAULT_ROLE=bde2076b-cccb-3cf0-d57e-bca7b1e83a52 export VAULT_SECRET=66eec22c-50c7-04e3-f86a-10623aebf163

Replace role-id and secret-id above with your own

  1. Restart application
java -jar target/cloudfoundry-with-vault-demo-0.0.1-SNAPSHOT.jar \ --spring.cloud.vault.authentication=APPROLE \ --spring.cloud.vault.app-role.role-id=`echo $VAULT_ROLE` \ --spring.cloud.vault.app-role.secret-id=`echo $VAULT_SECRET`
  1. Verify that the application knows about the latest secret
http :8080 message:'Now, young Skywalker, you will die.'

How to run on Cloud Foundry

Login via an API endpoint e.g., Pivotal Web Services

cf login -a api.run.pivotal.io

AppRole based authentication

  1. Deploy the Vault client application
cf push --random-route --no-start -f manifest.approle.yml
  1. Expose the locally running vault via ngrok
ngrok http 8200 Forwarding http://3db1eef2.ngrok.io -> localhost:8200 Forwarding https://3db1eef2.ngrok.io -> localhost:8200 
  1. Open a browser and verify ngrok's web interface is available at http://localhost:4040

  2. Set the following environment variables

VAULT_ADDR=<ngrok_url>
  1. Configure the environment variables
cf set-env vault-demo VAULT_ADDR ${VAULT_ADDR} cf set-env vault-demo VAULT_ROLE ${VAULT_ROLE} cf set-env vault-demo VAULT_SECRET ${VAULT_SECRET}
  1. Start the Vault client application
cf start vault-demo
  1. Verify that the application has started successfully
cf logs --recent vault-demo
  1. Verify that the application knows about the latest secret
http vault-demo-terrific-fossa.cfapps.io message:'Now, young Skywalker, you will die.'

Replace application URL above with your own

Token based authentication w/ Service Broker

The service broker implementation is currently limited to token-based authentication scheme

  1. Get the Open Service Broker API implementation from HashiCorp
git clone https://github.com/hashicorp/vault-service-broker
  1. You will to change the DefaultServiceID and DefaultServiceName in the main.go file

  2. Deploy the broker

cf push my-vault-broker-service -m 256M --random-route --no-start 

The --no-start makes sure it is not started after it is deployed.

  1. Expose the locally running vault via ngrok
ngrok http 8200 Forwarding http://3db1eef2.ngrok.io -> localhost:8200 Forwarding https://3db1eef2.ngrok.io -> localhost:8200 
  1. Open a browser and verify ngrok's web interface is available at http://localhost:4040

  2. Set the following environment variables

VAULT_ADDR=<ngrok_url> VAULT_TOKEN=<token>

The broker is configured to use basic authentication

VAULT_USERNAME=vault VAULT_PASSWORD=secret

You'll want to replace the username and password values above with your own

  1. Configure the environment variables
cf set-env my-vault-broker-service VAULT_ADDR ${VAULT_ADDR} cf set-env my-vault-broker-service VAULT_TOKEN ${VAULT_TOKEN} cf set-env my-vault-broker-service SECURITY_USER_NAME ${VAULT_USERNAME} cf set-env my-vault-broker-service SECURITY_USER_PASSWORD ${VAULT_PASSWORD}
  1. Verify the configured environment variables
cf env my-vault-broker-service
  1. Start the broker
cf start my-vault-broker-service
  1. Check the logs to verify a successful start
cf logs --recent my-vault-broker-service
  1. Verify in the Ngrok Inspect UI the activity requests sent to the exposed Vault broker
GET /v1/sys/mounts PUT /v1/auth/token/renew-self POST /v1/sys/mounts/cf/broker GET /v1/cf/broker
  1. The service broker created a new mount
vault mounts ... cf/broker/ generic generic_4c6ea7ec n/a system system false replicated ... 
  1. View the running broker
cf apps name requested state instances memory disk urls my-vault-broker-service started 1/1 256M 1G vault-demo-twiggiest-sennit.cfapps.io 
  1. Get the broker url
VAULT_BROKER_URL=$(cf app my-vault-broker-service | grep routes: | awk '{print $2}')
  1. Get the catalog information
curl ${VAULT_USERNAME}:${VAULT_PASSWORD}@${VAULT_BROKER_URL}/v2/catalog | jq
{ "services": [ { "id": "42ff1ff1-244d-413a-87ab-b2334b801134", "name": "my-hashicorp-vault", "description": "HashiCorp Vault Service Broker", "bindable": true, "tags": [ "" ], "plan_updateable": false, "plans": [ { "id": "42ff1ff1-244d-413a-87ab-b2334b801134.shared", "name": "shared", "description": "Secure access to Vault's storage and transit backends", "free": true } ] } ] }
  1. Create a service broker
cf service-brokers cf create-service-broker my-vault-service-broker "${VAULT_USERNAME}" "${VAULT_PASSWORD}" "https://${VAULT_BROKER_URL}" --space-scoped

You need to specify the --space-scoped and the service ids and service name must be unique. See https://docs.cloudfoundry.org/services/managing-service-brokers.html

  1. Create a service instance
cf create-service my-hashicorp-vault shared my-vault-service

Note that the first parameter to cf create-service must match the value of DefaultServiceName that you set in Step 3 above

  1. Verify the result
cf services name service plan bound apps last operation my-vault-service my-hashicorp-vault shared create succeeded
  1. Verify the HTTP requests sent the exposed Vault service using the Ngrok Inspect UI:
POST /v1/sys/mounts/cf/0b24f466-9a54-4215-852e-2bcfab428a82/secret PUT /v1/cf/broker/0b24f466-9a54-4215-852e-2bcfab428a82 GET /v1/sys/mounts POST /v1/sys/mounts/cf/0b24f466-9a54-4215-852e-2bcfab428a82/transit POST /v1/sys/mounts/cf/be7eedf8-c813-49e1-98f8-2fc19370ee4d/secret POST /v1/sys/mounts/cf/5f7b0811-d90a-47f2-a194-951eb324f867/secret PUT /v1/sys/policy/cf-0b24f466-9a54-4215-852e-2bcfab428a82 PUT /v1/auth/token/roles/cf-0b24f466-9a54-4215-852e-2bcfab428a82

When  a new service instance is provisioned using the broker, the following paths will be mounted:

  • Mount the generic backend at /cf/<organization_id>/secret/
  • Mount the generic backend at /cf/<space_id>/secret/
  • Mount the generic backend at /cf/<instance_id>/secret/
  • Mount the transit backend at /cf/<instance_id>/transit/

A policy named cf-<instance_id> is also created for this service instance which grants read-only access to cf/<organization_id>/*, read-write access to cf/<space_id>/* and full access to cf/<instance_id>/*

  1. Create a service key
cf create-service-key my-vault-service my-vault-service-key cf service-keys my-vault-service
  1. Verify the received requests for Vault using the Ngrok Inspect UI
PUT /v1/auth/token/renew-self PUT /v1/auth/token/renew-self PUT /v1/cf/broker/0b24f466-9a54-4215-852e-2bcfab428a82/5cf104c9-4515-40f3-94de-a63ab77cb84b POST /v1/auth/token/create/cf-0b24f466-9a54-4215-852e-2bcfab428a82
  1. Retrieve credentials for this instance
cf service-key my-vault-service my-vault-service-key
{ "address": "https://1f81e0d3.ngrok.io/", "auth": { "accessor": "3705e5b2-c0bb-6398-ecff-e05a9e6a7b28", "token": "d5971c27-cf77-6ff0-f5c9-430fdfe07066" }, "backends": { "generic": "cf/0b24f466-9a54-4215-852e-2bcfab428a82/secret", "transit": "cf/0b24f466-9a54-4215-852e-2bcfab428a82/transit" }, "backends_shared": { "organization": "cf/be7eedf8-c813-49e1-98f8-2fc19370ee4d/secret", "space": "cf/5f7b0811-d90a-47f2-a194-951eb324f867/secret" } }
  1. Deploy the Vault client application
cf push --random-route --no-start -f manifest.token.yml

In the application, we can leverage these services using the following configuration in the bootstrap.yml file. Note that we are only able to access the exposed backends.

spring: application: name: vault-demo cloud: vault: token: ${vcap.services.my-vault-service.credentials.auth.token} uri: ${vcap.services.my-vault-service.credentials.address:http://localhost:8200} generic: backend: ${vcap.services.my-vault-service.credentials.backends.generic:secret}
  1. Bind the my-vault-service to the vault-demo application
cf bind-service vault-demo my-vault-service
  1. Start the Vault client application
cf start vault-demo
  1. Verify that the application has started successfully
cf logs --recent vault-demo

Make a note of the organization id in the log output. You will need this value for the next step. Consult the LeaseAwareVaultPropertySource in sample log output below.

2018-06-12T06:55:36.26-0700 [APP/PROC/WEB/0] OUT 2018-06-12 13:55:36.259 INFO 14 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource {name='vault', propertySources=[LeaseAwareVaultPropertySource {name='cf/0b24f466-9a54-4215-852e-2bcfab428a82/secret/vault-demo/cloud'}, LeaseAwareVaultPropertySource {name='cf/0b24f466-9a54-4215-852e-2bcfab428a82/secret/vault-demo'}, LeaseAwareVaultPropertySource {name='cf/0b24f466-9a54-4215-852e-2bcfab428a82/secret/application/cloud'}, LeaseAwareVaultPropertySource {name='cf/0b24f466-9a54-4215-852e-2bcfab428a82/secret/application'}]} 
  1. Let's write a secret into the Vault to the given generic backend and send a refresh command
vault write cf/0b24f466-9a54-4215-852e-2bcfab428a82/secret/vault-demo message='Vault Rocks' http post https://vault-demo-twiggiest-sennit.cfapps.io/actuator/refresh

Replace application URL with your own

  1. We can verify that the secret is retrieved via
http get http://vault-demo-twiggiest-sennit.cfapps.io message: Vault Rocks

Replace application URL with your own

Resources

About

A simple example of an integration between a Spring MVC application powered Spring Boot 2 and Spring Cloud Finchley RC2 and Hashicorp Vault.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 92.3%
  • HCL 7.7%