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 reference in our documentation.
Please note that the policy examples included in this post use the syntax from Conjur API version 5; earlier versions 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 the CyberArk Commons to comment and tagging me @izgerij
.
Geri Jennings, PhD is an Engineering Manager on the Conjur team. She enjoys learning new things, and usually comes out with a blog post when there’s an idea she can’t shake. Follow her on twitter at @izgerij.