Introduction to policies
Policies give Vault administrators the ability to configure granular control over access to their Vault deployment. This tutorial provides context for how and why policies are used in Vault. In later tutorials, you will create policies using the Vault CLI, HTTP API, UI, and Terraform.
Scenario
HashiCups is preparing for their Vault proof-of-concept (POC) and need to understand how they can secure their Vault deployment. Alice from the architect team will work with Oliver from operations to translate business and technical requirements into Vault policies based on the principal of least privilege.
Alice has designed the Vault POC to use the Vault userpass
auth method to
allow teams to authenticate to Vault with a username and password.
For secrets engines, Alice has decided to use the k/v
secrets engine during
the POC, each enabled at a unique path for the development, and SRE teams.
Alice and Oliver have identified a need for unique policies to support each team. This will ensure each team can only access the secrets at their dedicated secrets engine path.
What is a Vault policy
Policies provide a declarative way to grant or forbid access to operations in Vault. Recall from the Vault plugin architecture tutorial that everything in Vault is path based. This allows you to write policies, and give control over which operations you need to configure on a specific path.
For HashiCups, Oliver can write a policy to allow developers the necessary
operations on the secrets engine enabled at the dev-secrets
path, and the SRE
team access to the sre-secrets
path.
How policies are used
Vault attaches a policy to a token after a user or service authenticates with
Vault. In later tutorials, you will enable the userpass
auth method, and attach
the policy to a user account. The userpass
auth method is a basic, stand-alone
auth method where each user has a policy attached. This auth method is useful
for small deployments and as a backup auth method for Vault administrators. If
there is an event that causes other auth methods such as the OIDC auth method to
be unavailable, administrators can fall back on the userpass
auth method.
In more complex environments, you will likely have a combination of auth methods to support both human and machine authentication. For example, you may use an OIDC service to authenticate users from an LDAP directory, and the Kubernetes auth method to authenticate workloads. With these auth methods you create a role, and associate the policy to the role. Once the user or workload authenticates with Vault, the token will have the policy attached.
While you can update a policy at any time, there is no way to change the effective policies associated with a token once Vault issues the token. You must revoke the token and re-authenticate with Vault to received the updated policies.
Create a policy in Vault
Policies use the HashiCorp Configuration Language (HCL). The policy format uses a prefix matching system on the API path to find the effective access control. The most specific defined policy has the highest priority.
To understand how policies work, HashiCorp works with Alice to understand an example use case.
Logically, Alice wants to ensure that a user can create and update secrets at a certain path. Another requirement is Alice needs to ensure they only have read access to a second path.
Oliver maps the logical requirement to the Vault k/v
secrets engine used with
a Vault dev mode server. Oliver defines a policy that permits create
, and
update
to the secrets/data/creds
path.
For the second requirement, the policy will allow only read
at the
/secret/data/creds/confidential
path.
Oliver can write a single policy because the path ending with
/confidential
is more specific than the path ending in /creds
.
Example policy:
path "secret/data/creds" { capabilities = ["create", "update"]} path "secret/data/creds/confidential" { capabilities = ["read"]}
Lines 1-2 allow create
and update
on the secret/data/creds
path.
Lines 5-6 allow only read
on the secret/data/creds/confidential
path.
In the scenario section, Alice determined HashiCups would need two
policies - one each for development and SRE teams. The policy needs to allow
access to the k/v
secrets engine each team will use during the POC to store
static credentials.
Based on Alice's Vault design, Oliver will enable the k/v
secrets engine at
dev-secrets
, and the SRE secrets engine at sre-secrets
.
The first line in the policy will define the path. In the case of the k/v
secrets engine that path will be dev-secrets/data/creds
, represented on line
one of the policy below.
path "dev-secrets/data/creds" { capabilities = ["create", "list", "read", "update"]}
Tip
The data
portion of the path used in this example is specific to version two
of the k/v
secrets engine. Specific secrets engine configuration requirements
will be covered in later tutorials.
With this policy, a user could perform some subset of actions to secret/data/creds
path.
The second line in the policy describes the capabilities, or actions, that is allowed on the path.
path "dev-secrets/data/*" { capabilities = ["create", "list", "read", "update"]}
The capabilities
line will allow a user or workload with this policy to
create
, list
, read
, and update
secrets stored at the path defined on line one.
What would happen if you changed the policy to capabilities = ["read"]
?
Only actions specifically defined in the policy are allowed. Any user or workload would be able to read secrets stored at the path defined in the policy.
Now that you have a basic understanding of policies, it is also important to learn how to comment to your policies so others can understand the policies intent.
Comments in HCL start with the #
character, the same character used in Bash or
PowerShell comments.
# Vault policy to allow access to the dev-secrets k/v v2 secrets enginepath "dev-secrets/data/*" { capabilities = ["create", "list", "read", "update"]}
A well commented policy can help with troubleshooting, or to help new team members understand common patterns used in your Vault deployment.
Alice has defined the need for two policies - one each for development and SRE teams. This is because policies default to deny. Any access to an unspecified path is not allowed.
What would happen if Alice decided to use a single policy, with paths to the secrets engine for both the development and SRE teams?
# Vault policy to allow access to the dev-secrets k/v v2 secrets enginepath "dev-secrets/data/creds" { capabilities = ["create", "list", "read", "update"]} path "sre-secrets/data/creds" { capabilities = ["create", "list", "read", "update"]}
The policy would allow any user or workload with access to the secrets engines enabled at both paths.
This would not follow the principal of least privilege. Developers can access secrets stored in the SRE team's secrets engine, and SRE's can access secrets stored in the development team's secrets engine using this policy.
Rather that writing this as a single policy, Oliver will create one policy each for the development team, and one for the SRE team. Oliver can then attach the policy needed for each team member.
# Vault policy to allow access to the sre-secrets k/v v2 secrets enginepath "sre-secrets/data/creds" { capabilities = ["create", "list", "read", "update"]}
Use wildcards in Vault policies
Special characters give you the ability to write flexible policies that can apply to different paths.
In this tutorial, you have reviewed the policy for the development team, allowing
access to the dev-secrets
path.
# Vault policy to allow access to the dev-secrets k/v v2 secrets enginepath "dev-secrets/data/creds" { capabilities = ["create", "list", "read", "update"]}
This policy will allow someone with this policy to write one or more key/value
pairs to the path ending in creds
. What if you wanted to allow the team to
create secrets at path matching their username, such as
dev-secrets/data/creds-danielle
?
The policy would not allow a user or workload to write to the desired path because it does not match the policy.
Without the use of special characters, you may have to write a unique policy for
each user. By using a glob (*
) at the end of the path, you have more
flexibility in managing secrets at a desired path.
Tip
Use of the glob character is only supported at the end of a path.
# Vault policy to allow access to the dev-secrets k/v v2 secrets enginepath "dev-secrets/data/creds*" { capabilities = ["create", "list", "read", "update"]}
This policy would allow users or workloads to add to the string defined in the
path. You can now create a secret at dev-secrets/data/creds-webapp
or
dev-secrets/data/creds-danielle
.
A common mistake is to treat the glob as a wildcard inside
the path that would match any directory - for example dev-secrets/*/creds
.
This would not provide a match, and the policy would not work.
When you want a policy to match some part of a path, use the Vault wildcard
character +
. The wildcard allows you to match a specific part of a path. For
example, you can simplify the k/v v2
policy by replacing data
with the
wildcard.
# Vault policy to allow access to the dev-secrets k/v v2 secrets enginepath "dev-secrets/+/creds" { capabilities = ["create", "list", "read", "update"]}
Special characters give you flexibility in crafting policies. Be sure to test policies to ensure you are not allowing unauthorized access to a path because of the special character.
Use templates in Vault policies
You can use certain identity information in your Vault policy.
In the special characters section, you reviewed an example
use case of allowing people to have more control over where they write a secret.
A glob (*
) character at the end of a policy path allows you to write unique
strings at the end of the path.
While this can work in practice, it also means any user with that policy can access secrets stored at a path another person creates. When you require more restrictive controls, you can use templates based on the identity of the user authenticating to Vault in the policy.
Consider the earlier example where you want to allow individual users to be able to store secrets in the same secrets engine. You also require that each users have access to a specific path. Rather than authoring policies for each user, use the identity of the user in the policy path.
# Vault policy to allow access to the dev-secrets k/v v2 secrets enginepath "dev-secrets/+/creds/{{identity.entity.name}}" { capabilities = ["create", "list", "read", "update"]}
On line 2, you update the path to be specific to the name of the user
authenticating to Vault. Now, when Danielle logs in, they can write to
dev-secrets/data/creds/danielle
and Oliver can write to
dev-secrets/data/creds/oliver
. Even though a single policy is in use, each
user will only be able to access secrets at the path matching their username.
Templating allows you to follow the principal of least privilege while authoring
a single policy.
There are many templates available, which you will explore in more detail in a later tutorial.
Add constraints to Vault policies
Certain use cases may require you to validate the secrets you are writing to Vault include certain information. For the HashiCups POC, they want teams to be able to store credentials used to access other applications instead of hard coding the secrets in the source code.
The HashiCups application requires a username and password to authenticate to
the database backend. What would happen if Danielle updates the application to
look for a key named username
but secret stored in Vault is user_name
? This
can cause the application to fail to connect to the database, potentially
introducing unplanned downtime.
By using a required parameter constraint, Alice can create an application design
that uses a standard secret key of username
. Oliver can then add the
constraint to the Vault policy so teams will require a secret with the key name
of username
in the secret.
# Vault policy to allow access to the dev-secrets k/v v2 secrets enginepath "dev-secrets/+/creds" { capabilities = ["create", "list", "read", "update"] required_parameters = ["username"]}
With this policy in place, any write to dev-secrets/data/creds
will require a
key named username
.
In addition to required_parameters
, you can also use allowed_parameters
,
denied_parameters
, and allowed_policies
.
Use explicit deny in Vault policies
Earlier in the tutorial Alice and Oliver discovered that policies deny by default. Generally this means any capability not defined for a path is not allowed.
There may be cases where more permissive policies meet the majority of the use cases, with a few exceptions.
Consider an application that needs to read and update secrets
from the dev-secrets
path. You can use the glob character in the policy to
allow the desired capabilities.
# Vault policy to allow access to the dev-secrets k/v v2 secrets enginepath "dev-secrets/+/*" { capabilities = ["create", "list", "read", "update"]}
This policy would allow the capabilities in the policy on everything stored at
the dev-secrets/data/
path. What if you needed to store a secret at this path
that development team is not permitted to have access to, but is authorized
from another policy?
You can add an explicit deny capability on the specific path(s) instead of writing policies for each unique secrets engine path.
# Vault policy to allow access to the dev-secrets k/v v2 secrets enginepath "dev-secrets/+/*" { capabilities = ["create", "list", "read", "update"]} path "dev-secrets/+/root" { capabilities = ["deny"]}
In this example, the policy allows the create
, list
, read
, and update
capabilities on everything except dev-secrets/data/root
. Using an explicit
deny capability can help simplify your policies when you need to restrict select
paths.
Identify Vault policy conflicts
During the course of the HashiCups POC, Alice has identified a potential risk - what happens policies have a conflict?
Earlier in the tutorial you learned that Vault will use the most specific path defined to determine the effective results of a policy.
Review the example with the explicit deny
capability.
# Vault policy to allow access to the dev-secrets k/v v2 secrets enginepath "dev-secrets/+/*" { capabilities = ["create", "list", "read", "update"]} path "dev-secrets/+/root" { capabilities = ["deny"]}
In this example, there are two paths:
Because the dev-secrets/+/root
path is more specific, the deny
capability is
the effective policy on dev-secrets/+/root
, even though dev-secrets/+/*
may
otherwise allow access to the /root
path.
In addition to how specific a path is, deny
will always take precedence over
other capabilities. What would be the outcome in this example where both paths
are the same?
# Vault policy to allow access to the dev-secrets k/v v2 secrets enginepath "dev-secrets/+/root" { capabilities = ["read"]} path "dev-secrets/+/root" { capabilities = ["deny"]}
The deny
part of the policy would take precedence over the read
policy.
When explicit deny
policies are not used, and the path is the same on
different policies, the effective result is a combination of both policies.
# Vault policy to allow access to the dev-secrets k/v v2 secrets enginepath "dev-secrets/+/root" { capabilities = ["list", "read"]} path "dev-secrets/+/root" { capabilities = ["create", "update"]}
In this example, the effective policy would be a combination of list
, and
read
combined with create
, and update
.
Sentinel policies
Another important type of policy are Sentinel policies.
The Sentinel policy framework is an advanced form of policies available with HCP Vault Dedicated and Vault Enterprise.
Sentinel adds condtion-based policies, or ensures other authentication rules have passed before allowing an action.
Sentinel is beyond the scope of this tutorial, but it is important to understand it exists. You can compare Sentinel capabilities against your business and technical requirements when determining which edition of Vault is right for your use case.
Summary
Polices define what actions you want to allow by an authenticated entity. You can write policies to allow certain actions and explicitly deny other actions. When policies include deny statements, they will always take precedence over other statements that would allow the action.