Secret management software offers tools to help avoid plain text secrets and unencrypted storage of secrets. The increased adoption of DevOps principles and CI/CD plays a vital role in safeguarding confidential data, such as passwords, API keys, tokens, and certificates, during automation tasks.
Our previous post, Behind the Vault: Secret Management Choices, delved into why we chose HashiCorp Vault.
HashiCorp Vault uses a path-based structure to govern all aspects of its management; policies are no exception. Policies serve as the method of granting or denying access to specific paths and operations within Vault. By default, policies adopt a “deny” stance, meaning that no permissions are granted without a policy.
Policies can be created using the CLI or GUI and are associated with different Authentication Methods to provide specified access.
In this post, we will break down how policies are structured and how precedence rules determine which policy is applied in the event of a conflict. We will also consider some best practices when writing your policies.
Writing Policies
Vault policies are written in HCL format and, in their simplest form, contain a path and a list of capabilities (access rights). You may commonly see Vault policies called ACL (Access Control List) Policies.
# KV1 Secret Engine example
path "secret/path" {
capabilities = ["create", "read", "update", "patch", "delete", "list"]
}
# KV2 Secret Engine example (Requires '/data' following the KV Vault name)
path "secret/data/path" {
capabilities = ["create", "read", "update", "patch", "delete", "list"]
}
Additional parameters are also available to fine-tune your control further, but we won’t cover these for this post.
Types of Path
Paths can be absolute or use wildcards such as ‘+’ (skips directory) or ‘*’ (grants access to anything following).
Note: ‘*’ can only appear at the end of the path.
Examples:
path "a/b/c" {
capabilities = ["read", "list"]
}
# Would grant read and list access to the following path
"a/b/c"
path "a/+/c" {
capabilities = ["read", "list"]
}
# Would grant read and list access to the following paths
"a/a/c"
"a/b/c"
"a/c/c"
etc.
path "a/b/*" {
capabilities = ["read", "list"]
}
# Would grant read and list access to the following paths
"a/b/a"
"a/b/b"
"a/b/c"
"a/b/c/d"
etc.
path "a/b/c*" {
capabilities = ["read", "list"]
}
# Would grant read and list access to the following paths
"a/b/c"
"a/b/c/d"
"a/b/c1"
"a/b/c1/d"
etc.
Keep updated with the latest from Pipe Ten by subscribing below.
Capabilities
These are your access rights. Policies must always contain at least one capability. Let’s break down what each of them does:
read: Allows for data to be read at a given path.
create: Allows for data creation at a given path (It is recommended to pair this with an update).
update: Allows for data changes at a given path (It is recommended to pair this with create).
list: Allows for values to be listed at the given path. This differs from read in that it shows you a list of available resources at a path but not their contents.
patch: Allows partial updates to the data at a given path. This differs from an update as it only allows for modification of specific fields or attributes of a resource while leaving the rest of the resource unchanged. This capability is generally paired with more fine-grain control and parameter constraints.
delete: Allows data to be deleted at a given path.
sudo: Allows access to paths that are root-protected. Tokens are only permitted to interact with these paths if they have the sudo capability (in addition to the other necessary capabilities for performing an operation against that path, such as read or delete).
deny: Disallows access to a given path. This option takes priority over all other capabilities.
Key Secret Paths
Outside of the ‘KV’ secret paths for storing generic secrets, HashiCorp Vault provides several key paths for managing user access and authentication. Here are some key paths and their functionalities:
auth/
This path contains various authentication methods Vault supports, such as auth/userpass
for username/password-based authentication, auth/token
for token-based authentication, auth/approle
for application role-based authentication, and more. Each authentication method has its own sub-path for configuration.
auth/<method>/login
This path is used to authenticate users using a specific authentication method. For example, auth/userpass/login
allows users to authenticate using their username and password.
auth/<method>/users
This path is used to manage user accounts employing a particular authentication method. For example, auth/userpass/users
contains the user accounts and their associated passwords when using the userpass
authentication method.
auth/<method>/groups
If the authentication method supports grouping users, this path manages user groups. For example, auth/userpass/groups
allows you to create and manage user groups when using the userpass
authentication method.
identity/
This path is used for managing entities, which represent the actors (users, applications) in Vault’s identity system. It includes sub-paths for managing entities, groups, aliases, and more.
policy/
This path is used for managing access policies in Vault. Policies define the permissions and access control rules for various paths and operations within Vault. For example, you can create and manage policies at policy/my-policy
.
sys/policies
This path is used to manage the system policies in Vault. System policies are applied to control access to various Vault paths and operations globally.
sys/auth
This path is used for managing authentication backends in Vault. You can turn authentication methods on or off, configure their settings, and manage the associated paths and roles.
Keep updated with the latest from Pipe Ten by subscribing below.
Policy Path Conflicts
When writing and assigning policies, you will almost certainly encounter some conflicts, especially if you are using wildcards in your paths. Conflicts can occur on either the paths or the capabilities. If two policies have an identical path and different capabilities, the sum of the capabilities is used (Unless ‘deny’ is one of the capabilities; this overrules all other capabilities).
Example: Identical Paths
# Policy with read access
path "a/b/c"{
capabilities = ["read"]
}
# Policy with write access
path "a/b/c"{
capabilities = ["write"]
}
# Policies combine to have read and write access
path "a/b/c"{
capabilities = ["read", "write"]
}
Example: Identical Paths With Deny
# Policy with read access
path "a/b/c"{
capabilities = ["read"]
}
# Policy with write access
path "a/b/c"{
capabilities = ["deny"]
}
# Deny capability takes priority, Policy uses deny access
path "a/b/c"{
capabilities = ["deny"]
}
When a policy conflicts on a path, a general rule to follow is that the more specific policy path takes priority.
To understand why this is the case, we can refer to HashiCorp Vault’s five rules of precedence:
Note: P1 refers to ‘Policy 1’, P2 refers to ‘Policy 2’
- If the first wildcard ( + ) or glob ( * ) occurs earlier in P1 , P1 is lower priority
- If P1 ends in * and P2 doesn’t, P1 is lower priority
- If P1 has more + (wildcard) segments, P1 is lower priority
- If P1 is shorter, it is lower priority
- If P1 is smaller lexicographically, it is lower priority
In the case of a conflict that spans several rules, the order 1-5 in which the conflict rule is listed determines which policy takes precedence. This means that a policy that ‘Rule 1’ applies to will have lower priority than the policy which it doesn’t. If ‘Rule 1’ applies to both policies, ‘Rule 2’ becomes the decider, and so on.
Let’s break these rules down and look at some examples for each.
Rule 1 – Wildcard Position
Since there are two types of wildcards, this can be considered in two ways. Let’s assume the target path we wish access rights to be assigned on is “a/b/c/d/e”.
Target Path: “a/b/c/d/e”
Scenario A
# P1
path "a/b/+/d/e"{
capabilities = ["read"]
}
# P2
path "a/+/c/d/e"{
capabilities = ["create", "read", "update"]
}
In this scenario, P1 would apply because the ‘+’ wildcard appears later in the path than P2.
Scenario B
# P1
path "a/b/c/*"{
capabilities = ["read"]
}
# P2
path "a/b/c/d/*"{
capabilities = ["create", "read", "update"]
}
In this scenario, P2 would apply because the ‘*’ wildcard appears later in the path than P1.
Rule 2 – Ends with *
Target Path: “a/b/c/d/e”
# P1
path "a/+/c/*"{
capabilities = ["read"]
}
# P2
path "a/+/c/d/e"{
capabilities = ["create", "read", "update"]
}
In this case, P2 would apply because although there is a tie in ‘Rule 1’, ‘Rule 2’ states that the path ending in ‘*’ has lower priority.
Rule 3 – Number of Wildcards
Target Path: “a/b/c/d/e”
# P1
path "a/+/+/*"{
capabilities = ["read"]
}
# P2
path "a/+/c/*"{
capabilities = ["create", "read", "update"]
}
In this case, P2 would apply because although there is a tie based on the previous rules, ‘Rule 3’ states that the path with more wildcards has lower priority.
Rule 4 – Shorter Path
Target Path: “a/b/c/d/e”
# P1
path "a/+/c/*"{
capabilities = ["read"]
}
# P2
path "a/+/c/d/*"{
capabilities = ["create", "read", "update"]
}
In this case, P2 would apply because although there is a tie based on the previous rules, ‘Rule 4’ states that the shorter path has lower priority.
Rule 5 – lexicographically Smaller
lexicographically refers to ordering like you would find in a dictionary. i.e. “abc” appears before “acb”. Symbols such as ‘*’ and ‘+’ are ordered before letters i.e. “+abc” comes before “abc”.
Target Path: “a/b/c/d/e”
# P1
path "a/+/c/+/*"{
capabilities = ["read"]
}
# P2
path "a/+/+/d/*"{
capabilities = ["create", "read", "update"]
}
Finally, with all other rules in a tie, P1 would apply because ‘Rule 5’ states that the lexicographically smaller path has lower priority.
Keep updated with the latest from Pipe Ten by subscribing below.
General Recommendations
Now that we’ve reviewed the policy structure and conflict precedence, it is worth looking at some good practices and recommendations when running HashiCorp Vault.
Here are some guidelines to consider:
- Principle of Least Privilege: Follow the principle of least privilege by granting only the necessary permissions to each policy. Avoid providing broad access to resources not required for a specific task. Not only is this good from a security perspective, but it also aids in reducing policy conflicts and limits the need for the ‘Deny’ capability, which can become challenging to manage.
- Use Wildcards Carefully: Exercise caution when using wildcards in policy paths. Wildcards can be powerful, but they also carry the risk of unintended consequences. Be precise with your wildcard usage to avoid accidentally granting broader access than intended.
- Plan Your Path Structure: Consider how your secret paths can be incorporated into your policies before inputting secrets. Plan out your path structure and set your naming conventions. If you intend to use wildcards, structure your secret paths in a suitable way.
Example Path Structure:
├── environment
│ ├── dev
│ │ ├── system1
│ │ │ ├── ssh_root_password
│ │ │ └── application_token
│ │ └── system2
│ │ ├── ssh_root_password
│ │ └── application_token
│ └─ live
│ ├── system1
│ │ ├── ssh_root_password
│ │ └── application_token
│ └── system2
│ ├── ssh_root_password
│ └── application_token
The example above allows you to grant full access to dev
system secrets using the following path: “environment/dev/+/*” and grant access to the application_token
of system2
on both dev and live with “environment/+/system2/application_token.
- Make Use of Policy Templates: Utilize policy templates provided by HashiCorp Vault, such as the default templates for “default” and “root” policies. These templates are designed to provide a good starting point for most use cases and help enforce best practices.
- Regularly Review and Audit Policies: Perform periodic reviews of your policies to ensure they align with the current requirements and are up to date. Regularly audit the permissions granted by policies to identify any potential security risks or excessive access.
- Test Policies in a Staging Environment: Before applying policies in a production environment, thoroughly test them in a staging environment. This helps identify any issues or conflicts with existing policies, ensuring a smoother rollout and reducing the risk of disrupting production systems.
- Document Policies: Clearly document your policies, including their purpose, scope, and the resources they control. This documentation helps in understanding the intent of the policies and aids in troubleshooting and future policy management.
- Regularly Rotate and Secure Policies: Treat policies as sensitive artefacts and store them securely. Regularly rotate and update policies as needed, adhering to your organisation’s security policies and access control guidelines.
- Educate Users: Provide proper training and guidance to users who interact with Vault. Educate them about the policies in place, best practices for accessing resources, and how to request additional permissions when required.
Author: Jack Jones
Jack has been an integral part of Pipe Ten’s engineering team for over 5 years. With a long history of being immersed in Microsoft’s ecosystem, Jack embodies Pipe Ten’s provider agnostic approach and has lead the evolution of many customer solutions to integrate the benefits of public cloud, specialising in Azure and AWS. The wealth and sheer depth of Jack’s cutting edge technical knowledge and skillset has been crucial to the success and growth of many customers’ businesses.