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.
"--delete"updates the passed policies and executes
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
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.
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
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
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.
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
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:
- layers.yml - Contains the application code layers declaration - policies.yml - Contains the application code sub-nodes declaration
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.
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.