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:

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 "myname@email.com"
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 a service 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.