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.
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
# 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 (
operations-admin), some users (
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
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
prod; or teams split up into categories like
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
- !policy id: backend owner: !group security_admin
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
prod.yml policy files.
- !policy id: dev owner: !group /developers-admin
- !policy id: ci owner: !group /operations-admin
- !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:
- !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.
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.
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.
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.
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
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.