vault

Using Sentinel's HTTP Import in HashiCorp Vault Enterprise

New Sentinel HTTP import capabilities in Vault Enterprise 1.5 enable new sophisticated governance policies. See it in action.

HashiCorp Vault Enterprise 1.5 added support for the Sentinel HTTP Import, which allows Sentinel policies to retrieve data from external API endpoints. This makes it possible for Vault Sentinel policies to apply sophisticated governance controls that were not previously possible with Vault’s traditional Access Control List (ACL) policies or with Vault’s Sentinel policies. And that makes Sentinel in Vault 1.5 much more powerful than it was in earlier versions.

In this blog post, I’ll describe a Vault Sentinel policy that requires all subgroups and member entities of a new Vault Group to belong to the same Vault Namespace as that group or to one of the descendant namespaces (children, grandchildren, great-grandchildren, and so on) of that namespace.

Vault Enterprise does not actually impose this restriction itself, but a customer asked me to enforce it with Sentinel to make it easier for them to do some reporting about group/entity relationships. The restriction is imposed by using the Sentinel HTTP import to call into the Vault HTTP API to retrieve a namespace map and to determine the namespaces that the subgroups and member entities of a new group belong to.

The following screenshot shows part of a Vault namespace hierarchy:

Example Vault Namespace hierarchy
Example Vault Namespace Hierarchy

I’ll also describe an auxiliary Sentinel policy that is used to build the namespace map that the primary policy reads.

Both Sentinel policies are Endpoint Governing Policies (EGPs) applied to specific Vault paths. They should both be created in the root namespace of a Vault cluster or server using the Vault UI, CLI, or HTTP API.

If you have a Vault Enterprise 1.5 server or cluster, you can test this policy yourself by following this guide.

What is Sentinel?

HashiCorp’s Sentinel is a language and framework that implements policy as code with fine-grained, logic-based policy decisions just as HashiCorp’s Terraform implements infrastructure as code. Sentinel is embedded in HashiCorp’s enterprise products including Vault Enterprise, in which Sentinel extends Vault’s native ACL policies.

The Auxiliary Policy

Let’s first discuss the auxiliary Sentinel policy, get-namespace-map.sentinel, that builds the namespace map. This policy is applied to the path, secret/get-namespace-map in the root namespace.

The policy calls the function get_namespace_map recursively starting with the root namespace. For each namespace, it uses the HTTP import to call Vault’s v1/<namespace>/sys/namespaces API endpoint to get a list of all of that namespace’s children. By repeating the calls recursively, the function is able to find all descendant namespaces of every namespace and build a complete namespace map.

Using the Sentinel HTTP import in the auxiliary policy
Using the Sentinel HTTP import in the auxiliary policy

The policy returns the namespace map as a JSON document with each key set to the name of a namespace and the corresponding value set to a list containing the namespace itself and all of its descendant namespaces.

The main rule of the policy always returns false so that the namespace map will be printed in the Sentinel output.

A Vault operator can rebuild the namespace map at any time by running this Vault CLI command:

vault read secret/get-namespace-map
vault read secret/get-namespace-map

You can see example output from running this command against a Vault cluster with a deep namespace hierarchy in the guide.

Each time the Vault operator runs the above Vault command to build the namespace map, they should also write the generated JSON document to the path secret/current-namespace-map in the root namespace with this command:

vault write secret/current-namespace-map namespace-map='<generated_namespace_map>'
vault write secret/current-namespace-map namespace-map='<generated_namespace_map>'

where <generated_namespace_map> is the JSON document returned by the previous command.

The Primary Policy

Now I can describe the primary policy, restrict-namespaces-of-group-members.sentinel, that actually restricts the subgroups and member entities of new Vault groups.

We apply the policy to all paths in all namespaces that match the regex, identity/group(.*). We accomplish this by actually applying the policy to all paths by specifying the path * but then using Sentinel's when predicate in the policy’s main rule to only do significant processing against paths that match identity/group(.*). This technique works because Vault breaks up the full path of a secret into the namespace portion, namespace.path, and the path within the namespace, request.path, before passing the path to Sentinel policies. Using this approach avoids having to list the <namespace>/identity/group path for every namespace; doing so would be operationally complex since Vault operators would need to update the policy every time they added a new namespace.

Creating the primary policy in the UI
Creating the primary policy

The primary policy first reads the namespace map generated by the auxiliary policy from the path secret/current-namespace-map.

The policy then iterates over all subgroups listed in the member_group_ids attribute of the new group being created. For each of these subgroups, it uses Sentinel’s HTTP import to make multiple calls against the Vault HTTP API endpoints v1/<namespace>/identity/group/id to determine the namespaces that the subgroups belong to. (This is done until the right namespace is found.) The policy then checks whether the namespace that subgroup belongs to is in the list of namespaces associated with the parent group's namespace in the namespace map.

The policy then performs similar operations against the entities listed in the member_entity_ids attribute of the new group being created.

Ultimately, the policy returns false if it finds any violations and true if it does not.

The end result is that all subgroups and entities of a new group must belong to the same namespace as that group or to one of that namespace’s descendant namespaces.

Conclusion

This blog post has illustrated how the addition of the Sentinel HTTP import to Vault’s Sentinel implementation in Vault 1.5 makes it possible to implement very sophisticated governance policies in Vault.

While our examples called some of Vault’s own HTTP API endpoints, you could also write Vault Sentinel policies that call public cloud HTTP API endpoints or API endpoints exposed by your company’s own internal systems.

So, if you ever found it difficult to implement specific governance controls with Vault’s native ACL policies or with Vault Sentinel policies, I think you’ll find you can now implement them by using the Sentinel HTTP import.

For more details on how to implement these Sentinel policies yourself, please check out the guide.

For more tips on writing Vault Sentinel policies, see my Validating Vault Secrets with Sentinel post.

Sign up for the latest HashiCorp news