TUTORIAL – CLOUD FOUNDRY INTEGRATION
Introduction
Cloud Foundry is an open source platform designed to make it easy for developers to run, scale, and maintain applications. Integrating with Conjur makes it easy for these apps to securely retrieve the secrets and credentials that they need.
Cloud Foundy installations are divided into orgs, which serve as accounts that can be shared amongst multiple users. Each org can have any number of spaces, which are where developer applications get deployed.
Applications can enhance their capabilities by binding with services such as Conjur. Each org has a marketplace that lists the services to applications deployed within that org. Services connect to applications through a service broker, which is a standalone application that exposes service functionality by implementing the Open Service Broker API.
Cloud Foundry apps can connect with an existing Conjur deployment using the Conjur service broker. The broker must be installed into the Cloud Foundry deployment and registered with a marketplace. Applications can then bind to the service broker, at which point Conjur will create a Host identity for the app that can be used with Conjur policy to grant the application access to secrets stored in Conjur.
We also offer the Conjur Buildpack, which can be used to automatically inject secret values into your application’s environment at runtime. Alternatively, we offer client libraries in several languages that may be used to deliver secrets to your application. For the purposes of this tutorial, we will use the buildpack.
Prerequisites
Before getting started with the tutorial, you must:
- Install Conjur locally or sign up for a hosted evaluation Conjur account
- Install the Cloud Foundry CLI
- Install CF Dev
Configure Conjur
Conjur uses role-based access control (RBAC) driven by declarative policy files to control which identities are allowed to access secrets. When working in Cloud Foundry, policy structure mimics the org, space, and application layers of a Cloud Foundry installation to provide fine-grained access to secrets at whichever scope best fits an application’s needs. For more information about the benefits of using declarative policy, please visit our blog.
First, we create our root policy. This defines a Host to represent the Conjur service broker as well as an admin group that will later be granted permission to create additional hosts. We add the service broker to this group because it needs to create Host identities for any applications that bind to it. Save the following to policy.yml
:
- !host
id: cf-service-broker
annotations:
platform: cloudfoundry
- !permit
role: !host cf-service-broker
privilege: read
resource: !host cf-service-broker
- !group
id: cf-admin-group
- !grant
role: !group cf-admin-group
member: !host cf-service-broker
Now we will load the policy, this can be done by copying the policy file to the client container you should have started while running through the Conjur setup instructions. Login to Conjur from the client container as an admin user and load the policy with the following command:
conjur policy load root policy.yml
The output will look something like this:
Loaded policy 'root'
{
"created_roles": {
"default:host:cf-service-broker": {
"id": "default:host:cf-service-broker",
"api_key": "2k60y0338tad17178xb9726pjf00jb7dzk2dgkxs43nsmb7e3txmsjc"
}
},
"version": 1
}
Take note of the API key for the cf-service-broker
shown in this response. We will need it later when configuring our service broker.
Next we load a simple policy that will act as a parent for our Cloud Foundry policies. Save the following to cf.yml
:
- !policy
id: cf
owner: !group cf-admin-group
Load the policy using:
conjur policy load root cf.yml
Now we will load a policy to creates secrets that will be scoped to the org level. All apps deployed to our sample org will be granted access to these
secrets. The policy defines two variables to hold a pair of username and password secret variables. It then defines two Groups to which Users or Hosts can be added, one that has read-only permission (secrets-users
) and one that can also write secret values to the variables (secrets-managers
). Save the following to org.yml
:
- !policy
id: org
body:
- &variables
- !variable
id: database/username
annotations:
description: Organization database username
- !variable
id: database/password
annotations:
description: Organization database password
- !group secrets-users
- !group secrets-managers
# secrets-managers has role secrets-users
- !grant
role: !group secrets-users
member: !group secrets-managers
# secrets-users can read and execute
- !permit
resource: *variables
privileges: [ read, execute ]
role: !group secrets-users
# secrets-managers can update (and read and execute, via role grant)
- !permit
resource: *variables
privileges: [ update ]
role: !group secrets-managers
Load it with:
conjur policy load cf org.yml
We will also load a policy to hold secrets scoped to the space level. Any apps deployed to our sample space will be granted access to these secrets. Save the following to space.yml
:
- !policy
id: space
body:
- &variables
- !variable
id: database/username
annotations:
description: Space database username
- !variable
id: database/password
annotations:
description: Space database password
- !group secrets-users
- !group secrets-managers
# secrets-managers has role secrets-users
- !grant
role: !group secrets-users
member: !group secrets-managers
# secrets-users can read and execute
- !permit
resource: *variables
privileges: [ read, execute ]
role: !group secrets-users
# secrets-managers can update (and read and execute, via role grant)
- !permit
resource: *variables
privileges: [ update ]
role: !group secrets-managers
Load it with:
conjur policy load cf space.yml
Now we will load a policy to hold secrets scoped to the app level. These secrets will only be available to a single application. Save the following to app.yml
:
- !policy
id: app
body:
- &variables
- !variable
id: database/username
annotations:
description: Application database username
- !variable
id: database/password
annotations:
description: Application database password
- !variable
id: stripe/private_key
annotations:
description: Stripe API key
- !group secrets-users
- !group secrets-managers
# secrets-managers has role secrets-users
- !grant
role: !group secrets-users
member: !group secrets-managers
# secrets-users can read and execute
- !permit
resource: *variables
privileges: [ read, execute ]
role: !group secrets-users
# secrets-managers can update (and read and execute, via role grant)
- !permit
resource: *variables
privileges: [ update ]
role: !group secrets-managers
Load it with:
conjur policy load cf app.yml
Finally, run the following commands to store secret values in the variables created during the previous steps:
conjur variable values add cf/org/database/username 'organization database username'
conjur variable values add cf/org/database/password 'organization database password'
conjur variable values add cf/space/database/username 'space database username'
conjur variable values add cf/space/database/password 'space database password'
conjur variable values add cf/app/database/username 'app database username'
conjur variable values add cf/app/database/password 'app database password'
conjur variable values add cf/app/stripe/private_key 'app stripe private_key'
Configure Conjur Service in Cloud Foundry
Next, we will need to start CF Dev and log in via the cf
CLI.
Note: If you already have CF Dev running and want to start with a clean installation, you can run cf dev stop
before calling the commands below.
Run cf dev start
to start a local Cloud Foundry environment. The output of this command should include a cf login
line as well as some credentials, which will look something like this:
To begin using CF Dev, please run:
cf login -a https://api.dev.cfdev.sh --skip-ssl-validation
Admin user => Email: admin / Password: admin
Regular user => Email: user / Password: pass
Copy and paste the cf login
line from your own output to login to your local Cloud Foundry environment. When prompted, provide the admin credentials and select any of the existing orgs and spaces.
Step 1: Deploy the Conjur Service Broker to its own org and space
Create and target the space and org in which the service broker will be deployed:
cf create-org cyberark-conjur-org
cf target -o cyberark-conjur-org
cf create-space cyberark-conjur-space
cf target -o cyberark-conjur-org -s cyberark-conjur-space
Install the service broker by downloading the repository and running cf push
to push the service broker to your Cloud Foundry environment:
curl -L $(curl -s https://api.github.com/repos/cyberark/conjur-service-broker/releases/latest |
grep zipball_url |
awk '{print $NF}' |
sed 's/",*//g') > conjur-service-broker.zip
unzip conjur-service-broker.zip
repo_dir=$(ls | grep cyberark-conjur-service-broker)
mv $repo_dir conjur-service-broker
# Push the Service Broker app
pushd conjur-service-broker
cf push --no-start --random-route
popd
rm -rf conjur-service-broker*
The service broker uses HTTP basic auth to talk to other Cloud Founry components. We must provide these credentials in its environment:
cf set-env conjur-service-broker SECURITY_USER_NAME TEMP_USER
cf set-env conjur-service-broker SECURITY_USER_PASSWORD TEMP_PASS
The service broker must communicate with Conjur to create identities for any applications that bind to it. We will need to configure it with the account and endpoint of your Conjur installation. The values here depend on how you chose to run Conjur.
If you followed the instructions to set up your own local Conjur instance, you should use:
cf set-env conjur-service-broker CONJUR_ACCOUNT "quick-start"
cf set-env conjur-service-broker CONJUR_APPLIANCE_URL "http://host.cfdev.sh:8080"
If you are using the hosted eval Conjur, you should use:
cf set-env conjur-service-broker CONJUR_ACCOUNT "[email protected]"
cf set-env conjur-service-broker CONJUR_APPLIANCE_URL "https://eval.conjur.org"
The service broker will use the Host identity we defined earlier in Conjur policy to login to Conjur. It will also need the API key that was printed out when loading the policy that created the Host:
cf set-env conjur-service-broker CONJUR_AUTHN_LOGIN host/cf-service-broker
cf set-env conjur-service-broker CONJUR_AUTHN_API_KEY <YOUR_API_KEY>
When the service broker binds to an application, it creates a Host identity to represent that app in Conjur policy. We need to provide the name of the policy under which these hosts should be created:
cf set-env conjur-service-broker CONJUR_POLICY cf
We should provide the Conjur version, which is 5 in this tutorial:
cf set-env conjur-service-broker CONJUR_VERSION 5
When connecting to a Conjur instance that is secured by TLS, the service broker will also need to be configured with a Conjur SSL certificate. If you are using hosted Conjur, the easiest way to do this is to copy the ~/conjur-<ACCOUNT>.pem
file that was produced when running conjur init
out of the client container and provide it like so:
cf set-env conjur-service-broker CONJUR_SSL_CERTIFICATE "$(< /path/to/conjur-<ACCOUNT>.pem)"
If you are running Conjur in a local Docker container, you can just set this environment variable to a blank string:
cf set-env conjur-service-broker CONJUR_SSL_CERTIFICATE ""
Now that the service broker environment is configured, start the service broker application with:
cf start conjur-service-broker
Now we must register the service broker application to make it available as aservice broker in the Cloud Foundry environment. Note that TEMP_USER
and TEMP_PASS
should be replaced with the HTTP basic auth credentials we set in the service broker environment during a previous step:
APP_URL="http://`cf app conjur-service-broker | grep -E -w 'urls:|routes:' | awk '{print $2}'`"
cf create-service-broker conjur-service-broker "TEMP_USER" "TEMP_PASS" $APP_URL
To make the Conjur service listing available in all orgs and spaces, run:
$ cf enable-service-access cyberark-conjur
To confirm that the service has been enabled, check the marketplace:
cf marketplace
You should be able to see the cyberark-conjur
service listed in the marketplace from any org / space:
Getting services from marketplace in org cyberark-conjur-org / space cyberark-conjur-space as admin...
OK
service plans description
cyberark-conjur community An open source security service that provides secrets management, machine-identity based authorization, and more.
Step 2: Upload the Conjur Buildpack
Once installed, an application configured to use the Conjur buildpack and bound
to a Conjur service instance will have the secrets specified in its secrets.yml
file automatically injected into its environment when it starts. To install the buildpack into your Cloud Foundry environment, run:
mkdir conjur-buildpack
pushd conjur-buildpack/
curl -L $(curl -s https://api.github.com/repos/cyberark/cloudfoundry-conjur-buildpack/releases/latest | \
grep browser_download_url | \
grep zip | \
awk '{print $NF}' | \
sed 's/",*//g') > conjur-buildpack.zip
unzip conjur-buildpack.zip
./upload.sh
popd
rm -rf conjur-buildpack
Step 3: Create an org / space for deploy the demo app
Typically the admin of a Cloud Foundry environment would set up orgs and spaces and install service brokers, but apps would be deployed by a developer. In the following section we will relinquish our admin privileges and assume the role of a humble developer to deploy our demo app.
But before switching users, let’s create an org and space for the demo app and then grant the non-admin account a developer role in our newly-created space:
cf create-org demo-org
cf target -o demo-org
cf create-space demo-space
cf target -o demo-org -s demo-space
cf set-space-role user demo-org demo-space SpaceDeveloper
Uploading the Demo Application to Cloud Foundry
The demo application that we are using is simple – it just echoes three environment variables to the browser window. It will allow us to demonstrate that the Conjur service successfully loads the secret values from Conjur into the application environment.
Step 1: Log in with the developer account
First we’ll need to login with the non-admin account that was listed when you started your Cloud Foundry environment. Run cf login
and use the non-admin credentials (typically user
/ pass
) and target the demo-org
and demo-space
when prompted:
cf login -a https://api.dev.cfdev.sh --skip-ssl-validation
Step 2: Create a Conjur service instance in the org / space for the demo app
Use cf marketplace
to verify that the Conjur service listing is available and then create a service instance like so:
cf create-service cyberark-conjur community conjur
Step 3: Entitling the org and space
Conjur policy includes a Layer primitive that can be used to group Host identities together to grant collective access to a secret. When an application binds to a Conjur service broker instance, the broker creates Layers for the app’s org and space and adds the app’s Host identity to those layers in order to grant the app access to any secrets scoped to the app’s org or space.
To enable this feature, we must entitle the newly-created org and space by adding their layers to the secrets-users
group of their respective policies. Get the org GUID by running:
cf org --guid demo-org
Save the following to org-entitlements.yml
, replacing <ORG_LAYER_NAME>
with the GUID from the previous step:
- !grant
role: !group org/secrets-users
member: !layer <ORG_LAYER_NAME>
Load it with:
conjur policy load cf org-entitlements.yml
To entitle the space, we must first retrieve the space GUID with:
cf space --guid demo-space
Since the Layer created for the space will be a child of the Layer created for the org, the name of the space in policy must include the org GUID as a prefix. That is, the space name will take the form of ORG_GUID/SPACE_GUID
. Save the following to space-entitlements.yml
, replacing <SPACE_LAYER_NAME>
with ORG_GUID/SPACE_GUID
:
- !grant
role: !group space/secrets-users
member: !layer <SPACE_LAYER_NAME>
Load it with:
conjur policy load cf space-entitlements.yml
Step 4: Deploy the demo application and bind it to the Conjur service instance
The demo application we will use is part of our demo repository, which includes demos for testing our Cloud Foundry integration locally using CF Dev or against an existing Cloud Foundry installation.
The app includes a Cloud Foundry deployment manifest that binds it to the Conjur service:
---
applications:
- name: hello-world
services:
- conjur
As soon as we cf push
the app, it will be bound to the Conjur service instance we created in the last step and it will be granted a host identity in Conjur.
curl -L https://github.com/cyberark/cloudfoundry-conjur-demo/archive/master.zip > conjur-demo.zip
unzip conjur-demo.zip
pushd cloudfoundry-conjur-demo-master/app
cp secrets.v5.yml secrets.yml
cf push --no-start
popd
rm -rf cloudfoundry-conjur-demo-master conjur-demo.zip
Note: We use cf push --no-start
here to ensure the app will not start until we have added entitlements for the application Host in Conjur policy so that it will have access to the secrets that it needs.
Step 5: Retrieve the application’s Host identity
When the app binds to the Conjur service instance during start up, the service created a Host to represent the app in Conjur policy. We can use this Host to add the app as a member of the cf/app/secrets-users
group, which will grant it access to the secrets defined in the cf/app
policy.
The Host identity can be found in the demo application’s environment under the authn_login
field in the cyberark-conjur
credentials. It will take the form of host/cf/<ORG_GUID>/<SPACE_GUID>/<APP_GUID>
and can be retrieved by running:
cf env hello-world | grep authn_login
The Host identity is the last three sections of the authn_login
value, so just the <ORG_GUID>/<SPACE_GUID>/<APP_GUID>
part with the host/cf/
prefix removed.
Step 6: Update Conjur policy to privilege the app
Save the following to app-entitlements.yml
, replacing <APP_HOST_NAME>
with the <ORG_GUID>/<SPACE_GUID>/<APP_GUID>
Host identity retrieved in the previous
step:
- !grant
role: !group app/secrets-users
member: !host <APP_HOST_NAME>
Load the policy as an admin Conjur user using the following command:
conjur policy load cf app-entitlements.yml
Step 7: Start the demo app and watch it retrieve secrets!
Start the demo app by running:
cf start hello-world
Get the URL of the hello-world app by running:
cf apps | grep hello-world
Open the URL in your browser and you should see the secret values we loaded earlier in the exercise.
Summary
Developers love Cloud Foundry for the ease of deploying applications, and the Conjur integration enables them to do so securely. Once the Conjur Service Broker and Conjur Buildpack have been installed in Cloud Foundry by an admin user and the Conjur service listing has been made available in the marketplace, it is easy for developers to bind their applications to a Conjur service instance. This gives their application a unique Host identity in Conjur, and that Host identity can be granted access to the secrets that the application needs. At runtime the Conjur Buildpack will automatically load secret values into the running application’s environment. With Cloud Foundry and Conjur together, developers can focus on releasing features while security and operations teams can rest assured that credentials are secure.