Tips and guidelines to manage Cyberark Conjur policies.

In this article we will explain which principles we apply in structuring a git repository that contains Conjur policies.

Tips and guidelines to manage Cyberark Conjur policies.

Core concepts

Conjur is "an open source programmatic interface, [which] integrates with popular tools to provide data encryption, identity management for humans and hosts, and role-based access control for sensitive secrets like passwords, SSH keys, and web services."

The basic building block of Conjur configuration is the policies. A policy is a yaml document that declares or removes a resource inside Conjur. The created policies are always attached to a node, and most of them also have an optional field named owner that contains reference to a resource that has full read/write access to the policy. If omitted, it is populated by the resource that is executing the load operation.

A node is a branch where the policies or other nodes reside. They are organized as a tree structure that has at the base the root node. It is important to remember that the root node cannot be deleted and is invisible unless it is queried directly. A node can always interact with the policies/nodes attached to it and if certain requirements are met it can also interact with nodes on a different level.

The policies can be loaded through the conjur-cli with the following command:

conjur policy load <LOAD FLAG> <NODE> <yaml policy statement file>

The Conjur policy load command can alternatively take one of the following flags:

  • none (unset) means that the script runs in append mode. Append only adds new policies to Conjur, it doesn't remove or update existing ones.
  • "--replace" means that the script does state reconciliation. Removes everything that isn't defined in the node and its subfolders. Updates the already existing values.
    More info.
  • "--delete" updates the passed policies and executes -delete Conjur statements.

We can wrap this all together by writing three policies:

# example.yml

# we create a group named admin. 
- !group
  id: admins
  
  
# we create a user  
- !user
  id: adm1
  owner: !group admins

# we grant membership of the user to the group admins
- !grant
  role: !group admins
  members:
    - !user adm1

Then we can load them by doing:

conjur policy load root example.yml

Git approach

Now that we have the basics down, it becomes more clear why we are using git to store policies. We can easily compare what changed between the releases of the policies, rollback in case of problems and have a gitflow-based approach for collaboration.

A basic example of a Conjur repo structure would look like this:

├── conjur // contains conjur system configurations
│   ├── authn-k8s //  configuration of enabled kubernetes clusters
│   │   ├── production-cluster
│   │   │   └── body.yml
│   │   └── policies.yml
│   ├── cluster // configuration of conjur cluster 
│   │   └── body.yml
│   ├── grants.yml
│   ├── policies.yml
│   └── seed-generation // conjur webservice that generates seeds for followers
│       └── body.yml
├── grants // root grants
│   └── conjur-production.yml
├── groups.yml 
├── hosts.yml 
├── production // production environment
│   ├── grants.yml
│   ├── layers.yml
│   ├── permits.yml
│   ├── policies.yml
│   ├── application-code // an example of an application code
│   │   └── hosts.yml
│   └── variables.yml
├── development // development environment
│   ├── grants.yml
│   ├── layers.yml
│   ├── permits.yml
│   ├── policies.yml
│   ├── application-code // an example of an application code
│   │   └── hosts.yml
│   └── variables.yml
├── permits // root permits
│   └── k8s-production-cluster.yml
└── policies.yml

In this approach the repository folders mimic the tree structure of Conjur, so we will find the conjur,production,development nodes attached to the root node. The only exception is for the permits and grants folder; these aren't present inside Conjur and just represent a logical place to aggregate similar types of policies on the root level.

With each deploy a pipeline makes sure that the Conjur policies match those of the repository by loading them with the --replace flag that makes the operation idempotent. This also allows you to freely rollback to previous versions (commits, tag-versions) if needed.

Each node loads the policy of the current level and then those of the sub-nodes.

Regarding the files, these can be split in two groups.

First group:

policies.yml - Contains the declaration of the sub-level nodes
hosts.yml - Contains the hosts to attach to the current node level. A host is an application/machine/anything non-human

layers.yml - Contains the layers to attach to the current node level. A layer group can contain one or more hosts

groups.yml - Contains the groups to attach to the current node level. A group is a container for users

webservice.yml - Contains the web services to attach to the current node level. Creates an endpoint in the Conjur cluster.

body.yml - Contains a mixed policy statement to attach to the current node level. Should only be used if there is no need to dig deeper in the node tree. Can always be split into smaller specific files

Second group:

grants.yml - Grants to attribute to statements of the same or below level. Example: give a grant that uses two sub-nodes

permits.yml - Permits to attribute to statements of the same or below level

The difference between the groups is important for the load order:

The first group is always loaded with the --replace flag.

The second group is loaded via a subsequent append command that is executed after the load of the first group and the loading of all the sub-level nodes.

Conjur folder

This is the Conjur system node; it contains all the policies that affect Conjur operativity.

The manifests on this level contain:

  • policies.yml - Definition of the sublevels that match with the folders contained in this level

  • grants.yml - A file that connects the followers to the seed-generation service; it is needed to enable followers to fetch seed-generation

The subfolders:

  • seed-generation - Contains the policies to generate Conjur followers seeds. Followers will need a grant to 'execute' this web service to fetch a read-only clone of the master. Info.

  • authn-k8s - Contains nodes that enable clusters (ex:production-cluster), more specifically these nodes:
    a) create the web service endpoint needed for application authentication
    b) define follower constraints Info.

  • cluster - Contains the policy to enable etcd autofailover on the Conjur cluster. Info.

Env folder (ex: production/development/staging)

Contains environment-specific policies.

In this folder all the application codes can be declared.

An application code corresponds to a Conjur layer and a policy statement that creates a sub-node.

Inside the sub-node the hosts (applications) and any other exclusive node specific statements will be declared.

The file structure:

First group:

- layers.yml - Contains the application code layers declaration

- policies.yml - Contains the application code sub-nodes declaration

Second group:

permits.yml - Any permits to give to the layers 

variables.yml - Any Conjur exclusive variables (secrets) that are bound to the env
grants.yml - Any grants to give to the layers

Layers need a permit statement to use k8s-authenticators. This should be done inside the permits root folder (given that is a cross node grant and the root level is the first level common to both).

As a reminder, the grant, permit, and variable policies are loaded by append after defining the node entities to avoid circular dependencies.

Conclusion

In this article we have seen how to structure a Conjur repository and how the folder structure can be a powerful tool to represent its node structure in a clear and scalable way.