Illustration for Understanding Conjur Policy - Conjur Policy as Trees. Illustration by Ty Paulhus

Understanding Conjur Policy

Conjur Policy as Trees

Published by Geri I. Jennings.

Understanding Conjur Policy

If this article is the first time you’re hearing about CyberArk Conjur, you’ll probably want to read some of the earlier blog posts in our series to learn more about getting started with Conjur or about our security policy as code model. Conjur is a secure vault for your applications’ credentials, and access to the credentials stored in Conjur is governed by Conjur policy. Conjur policy assigns roles to your organization’s users, groups, machines, and web services, and uses those roles to control access to your secrets.

When you get started using Conjur, the first thing you do after creating your default account (and its admin user) is start writing and loading policies. How should policies be organized? What is the optimal way to group the policy components to make them easy to manage? We’ll try to break this down and make it easy to conceptualize.

Conjur Users

The human users can be defined in Conjur policy in a variety of ways, most popularly via sync with an external LDAP server or by creating a users.yml policy file that declares a list of users and groups them into convenient categories. Here is a sample users.yml file:

# declare the groups needed
- !group
  id: security_admin
  owner: !user admin

- !group
  id: developers
  owner: !group security_admin

- !group
  id: developers-admin
  owner: !group security_admin

- !group
  id: operations-admin
  owner: !group security_admin

# list the users
- !user
  id: angela.mcconnell
  owner: !group security_admin

- !user
  id: harper.sarka
  owner: !group security_admin

- !user
  id: dave.nagi
  owner: !group security_admin

# add users to groups
- !grant
  role: !group developers-admin
  members:
    - !user angela.mcconnell

- !grant
  role: !group operations-admin
  members:
    - !user dave.nagi

- !grant
  role: !group developers
  members:
    - !member
      role: !group developers-admin
    - !user harper.sarka

This file defines some groups (security_admin, developers, and operations-admin), some users (angela.mcconnell, dave.nagi, etc), and then grants the users membership in the appropriate groups.

Conjur users and groups are generally defined by writing a policy file as above or by syncing with an external LDAP server. User entitlements (eg access privileges that entitle users to view or update policy or secret values) are generally also managed on their own, for example in an entitlements.yml file.

Conjur Policy as a Tree

Every Conjur installation comes with a root policy, owned by an admin user or group. It’s typically in this root policy where the users, groups, and entitlements are loaded; you can think of those policy components as the soil or nutrients that will feed the rest of the policy tree. If you stop here and just load all of your variables into the root policy, your policy tree will remain a stump. A better way is to create policy branches: sub-policies that contain the subsets of users, groups, hosts, and secrets required for specific applications or services.

Usually, access to secrets falls along natural organizational lines: apps broken up into dev, staging, ci, and prod; or teams split up into categories like backend and frontend and then broken down by deployment environment. Conjur policies can be organized the same way; for example, you can create a backend policy branch by loading this policy snippet (in a backend.yml file, for example) into your root policy:

- !policy
  id: backend
  owner: !group security_admin

Running conjur policy load root backend.yml as the admin Conjur user will create the backend policy branch owned by the security_admin group, so that you have to be a member of the security_admin group to make changes to this policy.

I can add more branches to this policy tree by loading dev.yml, ci.yml, and prod.yml policy files. dev.yml:

- !policy
  id: dev
  owner: !group /developers-admin

ci.yml:

- !policy
  id: ci
  owner: !group /operations-admin

prod.yml:

- !policy
  id: prod
  owner: !group /operations-admin

These policy files can be loaded into the backend policy branch using commands of the form conjur policy load backend dev.yml, and loading these policy files creates new branches of the policy tree that are children of the backend policy branch. The owner of the parent backend policy (the security_admin group) will inherit ownership privileges on the child dev policy, and thus will have privilege to create new policy branches under the dev policy as well as to update the dev policy itself.

It is typical to next load policy files that define components that are specific to an application or service. So you may have a policy file for your users microservice that connects to its own database of user information: users-app.yml:

- !policy
  id: users-app
  body:
    - &variables
      - !variable db-username
      - !variable db-password

    - !group secrets-users

    # secrets-users can read and execute
    - !permit
      resource: *variables
      privileges: [ read, execute ]
      role: !group secrets-users

You can load the users.yml policy file into all three backend policy branches, so that each environment will have its own database credentials:

conjur policy load backend/dev users-app.yml
conjur policy load backend/ci users-app.yml
conjur policy load backend/prod users-app.yml

Loading these three commands creates a users-app branch in each of the backend policy branches.

Building your Conjur policy tree continues from there, as you add policy branches for all of the services and applications that make up your organization.

Adding Entitlements

Once we have built out our Conjur policy tree, we have defined the structure of our organization and its secrets but we have still not granted anyone access to secrets. Continuing the tree metaphor, adding these entitlements is in effect creating a pathway from the soil to the branches of the tree. In this section, we will talk more about how to do this in practice.

In the users-app.yml example above, you may want to grant your developer users membership to the backend/dev/users-app/secrets-users group so that they can access the secret values in the applications they are developing. You can do this by updating entitlements.yml to include:

- !grant
  role: !group backend/dev/users-app/secrets-users
  member: !group developers

and having the admin user run conjur policy load root entitlements.yml.

For the ci and prod users-app services, the servers running the application should have host identities in Conjur policy. There are different ways to accomplish this, but one possibility is to update the users-app.yml file to include a Host Factory:

- !policy
  id: users-app
  body:
    - &variables
      - !variable db-username
      - !variable db-password

    - !group secrets-users

    - !layer apps-layer

    - !host-factory
      id: apps
      layers: [ !layer apps-layer ]

    # secrets-users can read and execute
    - !permit
      resource: *variables
      privileges: [ read, execute ]
      role: !group secrets-users

    - !grant
      role: !group secrets-users
      member: !layer apps-layer

A Host Factory streamlines the process of providing identity to a machine provisioned through automation (ex. AWS Auto Scaling). The users-app service can be updated to auto-enroll its servers into the Host Factory using a Host Factory token, and once enrolled each Host will automatically become a member of the users-app/secrets-users group and have access to the users-app database credentials.

Policy File Organization

Now that we understand how to organize the Conjur policy tree, let’s talk for a minute about how, in practice, to organize all of the data that comes with it.

When you are creating the policy tree, you have a number of files! In the example above with a single users-app service, you would have:

users.yml
entitlements.yml
backend.yml
frontend.yml
dev.yml
ci.yml
prod.yml
users-app.yml

The best practice is to store these files in version control, which provides a mechanism to track policy changes over time and to formalize a request and approval process for policy changes. As your policy tree changes, the policy files will need to be reloaded into Conjur, and you may find it convenient to have a bash script to simplify this process. For example, after you initially load the policies you might create a script called load_policy.sh that runs the following commands:

cat users.yml entitlements.yml frontend.yml backend.yml | conjur policy load --replace root -
cat dev.yml ci.yml prod.yml | conjur policy load frontend -
cat dev.yml ci.yml prod.yml | conjur policy load backend -
conjur policy load backend/dev users-app.yml
conjur policy load backend/ci users-app.yml
conjur policy load backend/prod users-app.yml

We use the --replace flag in the command above when reloading the root policy because it includes the entitlements and users; it is important to ensure that when a user or entitlement is updated in or (especially) removed from this policy file, that the policy tree is updated accordingly.

You may prefer storing the individual policy files in a document tree that mimics the configuration of your policy tree. Storing the policy files in this way would make it easier to grok the Conjur policy tree by looking at the files, but it would make it more difficult to maintain the files when you have multiple copies of the users-app.yml file (for example) that you need to maintain and sync as changes are required.

In Summary

Hopefully visualizing your Conjur policy as a tree is helpful and makes it easier to understand how to organize the components of your policies.

For more information about Conjur policies and how they work, please check out our policy references in our Enterprise documentation and our Open Source Documentation.

Please note that the policy examples included in this post use the syntax from Conjur version 5; earlier versions (and our current EE documentation) may use a slightly different syntax.

I wrote this post to help you understand Conjur policies. I really care about whether you found it interesting, accessible, and helpful, so if you have any feedback or suggestions, please let me know by joining our Conjur Slack Channel to comment and tagging me @Geri.