584 lines
17 KiB
Plaintext
584 lines
17 KiB
Plaintext
---
|
|
layout: guides
|
|
page_title: Sentinel - Guides
|
|
description: |-
|
|
Vault Enterprise supports Sentinel to provide a rich set of access control
|
|
functionality. This guide walks through the creation and usage of role
|
|
governing policies (RGPs) and endpoint governing policies (EGPs).
|
|
---
|
|
|
|
# Sentinel Policies
|
|
|
|
~> **Enterprise Only:** Sentinel is a part of _Vault Enterprise Premium_.
|
|
|
|
_Sentinel_ is a language framework for policy build to be embedded in Vault
|
|
Enterprise to enable fine-grained, logic-based policy decisions which cannot be
|
|
fully handled by the ACL policies.
|
|
|
|
**Role Governing Policies (RGPs)** and **Endpoint Governing
|
|
Policies (EGPs)** can be defined using Sentinel:
|
|
|
|
- RGPs are tied to particular tokens, identity entities, or identity groups
|
|
- EGPs are tied to particular paths (e.g. `aws/creds/`)
|
|
|
|
> This guide walks you through the authoring of Sentinel policies in Vault. For
|
|
> ACL policy authoring, refer to the [Policies](/guides/identity/policies)
|
|
> guide.
|
|
|
|
## Reference Material
|
|
|
|
- [Sentinel Getting Started Guide](https://docs.hashicorp.com/sentinel/intro/getting-started/first-policy)
|
|
- [Sentinel](https://docs.hashicorp.com/sentinel/) documentation
|
|
- [Vault Sentinel](/docs/enterprise/sentinel) documentation
|
|
- [Security and Fundamentals at Scale with Vault](https://www.youtube.com/watch?time_continue=121&v=yiPbKICFkvQ)
|
|
- [Identity - Entities and Groups](/guides/identity/identity) guide
|
|
|
|
## Estimated Time to Complete
|
|
|
|
5 - 10 minutes
|
|
|
|
## Challenge
|
|
|
|
ACL policies are **_path-based_** that it has the following challenges:
|
|
|
|
- Cannot grant permissions based on logics other than paths
|
|
- Paths are merged in ACL policies which could potentially cause a conflict as
|
|
the number of policies grows
|
|
|
|
What if the policy requirement was to grant read permission on `secret/orders`
|
|
path **_only if_** the request came from an IP address within a certain CIDR?
|
|
|
|
## Solution
|
|
|
|
Use Sentinel policies (RGPs and/or EGPs) to fulfill more complex policy
|
|
requirements.
|
|
|
|
Sentinel can access properties of the incoming requests and make a decision
|
|
based on a certain set of conditions. Available properties include:
|
|
|
|
- **request** - Information about the request itself (path, operation type,
|
|
parameters, etc.)
|
|
- **token** - Information about the token being used (creation time, attached
|
|
policies, etc.)
|
|
- **identity** - Identity entities and all related data
|
|
- **mfa** - Information about successful MFA validations
|
|
|
|
## Prerequisites
|
|
|
|
To perform the tasks described in this guide, you need to have a **_Vault
|
|
Enterprise_** environment.
|
|
|
|
### Policy requirements
|
|
|
|
Since this guide demonstrates the creation of policies, log in with highly
|
|
privileged token such as **`root`**. Required permissions are:
|
|
|
|
```shell
|
|
# To list policies
|
|
path "sys/policies/*"
|
|
{
|
|
capabilities = ["list"]
|
|
}
|
|
|
|
# Create and manage EGPs
|
|
path "sys/policies/egp/*"
|
|
{
|
|
capabilities = ["create", "read", "update", "delete", "list"]
|
|
}
|
|
```
|
|
|
|
## Steps
|
|
|
|
This guide demonstrates basic Sentinel policy authoring and management tasks.
|
|
|
|
1. [Write Sentinel Policies](#step1)
|
|
1. [Test the Sentinel Policies](#step2)
|
|
1. [Deploy your EGP policies](#step3)
|
|
1. [Delete Sentinel Policies](#step4)
|
|
|
|
### Step 1: Write Sentinel Policies ((#step1))
|
|
|
|
#### Anatomy of Sentinel Policies
|
|
|
|
```hcl
|
|
import "<library>"
|
|
|
|
<variable> = <value>
|
|
|
|
main = rule {
|
|
<conditions_to_evaluate>
|
|
}
|
|
```
|
|
|
|
- **`import`** - Enables your policy to access reusable libraries. There are a
|
|
set of built-in [imports](https://docs.hashicorp.com/sentinel/imports/)
|
|
available to help define your policy rules.
|
|
|
|
- **`main`** (required) - Every Sentinel policy must have a **`main`** rule
|
|
which is evaluated to determine the result of a policy.
|
|
|
|
- **`rule`** - A first-class construct in Sentinel. It describes a set of
|
|
conditions resulting in either true or false. (NOTE: Refer to the [Boolean
|
|
Expressions](https://docs.hashicorp.com/sentinel/language/boolexpr) for the full
|
|
list of available operators in writing rules.)
|
|
|
|
- **`<variable>`** - Variables are dynamically typed in Sentinel. You can define
|
|
its value explicitly or implicitly by the host system or [function](https://docs.hashicorp.com/sentinel/language/functions).
|
|
|
|
~> **NOTE:** The Sentinel language supports many features such as functions,
|
|
loops, slices, etc. You can learn about all of this in the [complete language
|
|
guide](https://docs.hashicorp.com/sentinel/language/).
|
|
|
|
#### Policy requirements
|
|
|
|
In this guide, you are going to write Sentinel policies that fulfill the
|
|
following requirements:
|
|
|
|
1. Any incoming request against the "`secret/accounting/*`" to be performed
|
|
during the business hours (7:00 am to 6:00 pm during the work days).
|
|
|
|
1. Any `create`, `update` and `delete` operations against Key/Value secret
|
|
engine (mounted at "`secret`") **must** come from an internal IP of
|
|
`122.22.3.4/32` CIDR.
|
|
|
|
#### Sentinel Policies
|
|
|
|
Requirement #1: **`business-hrs.sentinel`**
|
|
|
|
```shell
|
|
import "time"
|
|
|
|
# Expect requests to only happen during work days (Monday through Friday)
|
|
# 0 for Sunday and 6 for Saturday
|
|
workdays = rule {
|
|
time.now.weekday > 0 and time.now.weekday < 6
|
|
}
|
|
|
|
# Expect requests to only happen during work hours (7:00 am - 6:00 pm)
|
|
workhours = rule {
|
|
time.now.hour > 7 and time.now.hour < 18
|
|
}
|
|
|
|
main = rule {
|
|
workdays and workhours
|
|
}
|
|
```
|
|
|
|
Requirement #2: **`cidr-check.sentinel`**
|
|
|
|
```shell
|
|
import "sockaddr"
|
|
import "strings"
|
|
|
|
# Only care about create, update, and delete operations against secret path
|
|
precond = rule {
|
|
request.operation in ["create", "update", "delete"] and
|
|
strings.has_prefix(request.path, "secret/")
|
|
}
|
|
|
|
# Requests to come only from our private IP range
|
|
cidrcheck = rule {
|
|
sockaddr.is_contained(request.connection.remote_addr, "122.22.3.4/32")
|
|
}
|
|
|
|
# Check the precondition before execute the cidrcheck
|
|
main = rule when precond {
|
|
cidrcheck
|
|
}
|
|
```
|
|
|
|
> **NOTE:** The **`main`** has conditional rule (`when precond`) to ensure that
|
|
> the rule gets evaluated only if the request is relevant.
|
|
|
|
~> Refer to the [Sentinel Properties](/docs/enterprise/sentinel/properties)
|
|
documentation for available properties which Vault injects to Sentinel to allow
|
|
fine-grained controls.
|
|
|
|
### Step 2: Test the Sentinel Policies ((#step2))
|
|
|
|
You can test the Sentinel policies prior to deployment in orders to validate
|
|
syntax and to document expected behavior.
|
|
|
|
1. First, you need to download the [Sentinel simulator](https://docs.hashicorp.com/sentinel/downloads).
|
|
|
|
**Example:**
|
|
|
|
```plaintext
|
|
$ wget https://releases.hashicorp.com/sentinel/0.3.1/sentinel_0.3.1_darwin_amd64.zip
|
|
$ unzip sentinel_0.3.1_darwin_amd64.zip -d /usr/local/bin
|
|
```
|
|
|
|
1. Create a sub-folder named, **`test`** where `cidr-check.sentinel` and
|
|
`business-hrs.sentinel` policies are located. Under the `test` folder, you want
|
|
to create a sub-folder for each policy: **`cidr-check`** and **`business-hrs`**.
|
|
|
|
```plaintext
|
|
$ mkdir -p test/business-hrs
|
|
$ mkdir -p test/cidr-check
|
|
```
|
|
|
|
> **NOTE:** The test should be created under `/test/<policy_name>` folder.
|
|
|
|
1. Write a passing test case in a file named, **`success.json`** under
|
|
`test/business-hrs` directory.
|
|
|
|
```plaintext
|
|
{
|
|
"global": {
|
|
"timespace": {
|
|
"weekday": 1,
|
|
"hour": 12
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Under **`global`**, you specify the mock test data. In this example, the
|
|
`weekday` is set to `1` which is **`Monday`** and `hour` is set to `12`
|
|
which is **`noon`**. Therefore, the `main` should return `true`.
|
|
|
|
1. Write a failing test in a file named, **`fail.json`** under `test/business-hrs`.
|
|
|
|
```plaintext
|
|
{
|
|
"global": {
|
|
"timespace": {
|
|
"weekday": 0,
|
|
"hour": 12
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
The mock data is set to **`Sunday`** at **`noon`**; therefore, Therefore, the `main` should return `false`.
|
|
|
|
1. Similarly, write a passing test case for `cidr-check` policy, **`test/cidr-check/success.json`**:
|
|
|
|
```plaintext
|
|
{
|
|
"global": {
|
|
"request": {
|
|
"connection": {
|
|
"remote_addr": "122.22.3.4"
|
|
},
|
|
"operation": "create",
|
|
"path": "secret/orders"
|
|
}
|
|
}
|
|
```
|
|
|
|
In this example, the `global` specifies the `create` operation is invoked on
|
|
`secret/orders` endpoint which initiated from an IP address `122.22.3.4`.
|
|
Therefore, the `main` should return `true`.
|
|
|
|
1. Write a failing test for `cidr-check` policy, **`test/cidr-check/fail.json`**.
|
|
|
|
```plaintext
|
|
{
|
|
"global": {
|
|
"request": {
|
|
"connection": {
|
|
"remote_addr": "122.22.3.10"
|
|
},
|
|
"operation": "create",
|
|
"path": "secret/orders"
|
|
}
|
|
},
|
|
"test": {
|
|
"precond": true,
|
|
"main": false
|
|
}
|
|
}
|
|
```
|
|
|
|
This test will fail because of the IP address mismatch. However, the
|
|
`precond` should pass since the requested operation is `create` and the
|
|
targeted endpoint is `secret/orders`.
|
|
|
|
> The optional **`test`** definition adds more context to why the test
|
|
> should fail. The expected behavior is that the test fails because `main`
|
|
> returns `false` but `precond` should return `true`.
|
|
|
|
1. Now, you have written both success and failure tests:
|
|
|
|
```plaintext
|
|
├── business-hrs.sentinel
|
|
├── cidr-check.sentinel
|
|
└── test
|
|
├── business-hrs
|
|
│ ├── fail.json
|
|
│ └── success.json
|
|
└── cidr-check
|
|
├── fail.json
|
|
└── success.json
|
|
```
|
|
|
|
1. Execute the test:
|
|
|
|
```plaintext
|
|
$ sentinel test
|
|
|
|
PASS - business-hrs.sentinel
|
|
PASS - test/business-hrs/success.json PASS - test/business-hrs/fail.json
|
|
PASS - cidr-check.sentinel
|
|
PASS - test/cidr-check/success.json PASS - test/cidr-check/fail.json
|
|
```
|
|
|
|
> **NOTE:** If you want to see the tracing and log output for those tests,
|
|
> run the command with `-verbose` flag.
|
|
|
|
### Step 3: Deploy your EGP policies ((#step3))
|
|
|
|
Sentinel policies has three **enforcement levels**:
|
|
|
|
| Level | Description |
|
|
| -------------- | -------------------------------------------------------------------------- |
|
|
| advisory | The policy is allowed to fail. Can be used as a tool to educate new users. |
|
|
| soft-mandatory | The policy must pass unless an override is specified. |
|
|
| hard-mandatory | The policy must pass no matter what! |
|
|
|
|
Since both policies are tied to specific paths, the policy type that you are
|
|
going to create is Endpoint Governing Policies (EGPs).
|
|
|
|
#### CLI command
|
|
|
|
1. Store the Base64 encoded `cidr-check.sentinel` policy in an environment
|
|
variable named `POLICY`.
|
|
|
|
```plaintext
|
|
$ POLICY=$(base64 cidr-check.sentinel)
|
|
```
|
|
|
|
1. Create a policy `cidr-check` with enforcement level of **hard-mandatory** to
|
|
reject all requests coming from IP addressed that are not internal.
|
|
|
|
```plaintext
|
|
$ vault write sys/policies/egp/cidr-check \
|
|
policy="${POLICY}" \
|
|
paths="secret/*" \
|
|
enforcement_level="hard-mandatory"
|
|
```
|
|
|
|
1. You can read the policy by executing the following command:
|
|
|
|
```plaintext
|
|
$ vault read sys/policies/egp/cidr-check
|
|
```
|
|
|
|
1. Repeat the steps to create a policy named `business-hrs`.
|
|
|
|
```shell
|
|
# Encode the business-hrs policy
|
|
$ POLICY2=$(base64 business-hrs.sentinel)
|
|
|
|
# Create a policy with soft-mandatory enforcement-level
|
|
$ vault write sys/policies/egp/business-hrs \
|
|
policy="${POLICY2}" \
|
|
paths="secret/accounting/*" \
|
|
enforcement_level="soft-mandatory"
|
|
|
|
# To read the policy you just created
|
|
$ vault read sys/policies/egp/business-hrs
|
|
```
|
|
|
|
#### API call using cURL
|
|
|
|
To create EGP policies, use the `/sys/policies/egp` endpoint:
|
|
|
|
```shell-session
|
|
$ curl --header "X-Vault-Token: <TOKEN>" \
|
|
--request PUT \
|
|
--data <PAYLOAD> \
|
|
<VAULT_ADDRESS>/v1/sys/policies/egp/<POLICY_NAME>
|
|
```
|
|
|
|
Where `<TOKEN>` is your valid token, and `<PAYLOAD>` includes the Base64 encoded
|
|
policy, endpoint paths, and enforcement level.
|
|
|
|
1. Store the Base64 encoded `cidr-check.sentinel` policy in an environment
|
|
variable named `POLICY`.
|
|
|
|
```plaintext
|
|
$ POLICY=$(base64 cidr-check.sentinel)
|
|
```
|
|
|
|
1. Create API request payload.
|
|
|
|
```plaintext
|
|
$ tee cidr-payload.json <<EOF
|
|
{
|
|
"policy": "${POLICY}",
|
|
"paths": ["secret/*"],
|
|
"enforcement_level": "hard-mandatory"
|
|
}
|
|
EOF
|
|
```
|
|
|
|
1. Create a policy `cidr-check` with enforcement level of **hard-mandatory** to
|
|
reject all requests coming from IP addressed that are not internal.
|
|
|
|
```plaintext
|
|
$ curl --header "X-Vault-Token: ..." \
|
|
--request PUT \
|
|
--data @cidr-payload.json \
|
|
http://127.0.0.1:8200/v1/sys/policies/egp/cidr-check
|
|
```
|
|
|
|
1. Repeat the steps to create a policy named `business-hrs` with enforcement
|
|
level of soft-mandatory.
|
|
|
|
```shell
|
|
# Encode the business-hrs policy
|
|
$ POLICY2=$(base64 business-hrs.sentinel)
|
|
|
|
# Create the request payload
|
|
$ tee buz-hrs-payload.json <<EOF
|
|
{
|
|
"policy": "${POLICY2}",
|
|
"paths": ["secret/accounting/*"],
|
|
"enforcement_level": "soft-mandatory"
|
|
}
|
|
EOF
|
|
|
|
$ curl --header "X-Vault-Token: ..." \
|
|
--request PUT \
|
|
--data @buz-hrs-payload.json \
|
|
http://127.0.0.1:8200/v1/sys/policies/egp/business-hrs
|
|
```
|
|
|
|
1. You can list the EGPs that were created.
|
|
|
|
```plaintext
|
|
$ curl --header "X-Vault-Token: ..." \
|
|
--request LIST \
|
|
http://127.0.0.1:8200/v1/sys/policies/egp | jq
|
|
```
|
|
|
|
#### Web UI
|
|
|
|
Open a web browser and launch the Vault UI (e.g. http://127.0.0.1:8200/ui) and
|
|
then login.
|
|
|
|
1. Select **Policies** and select the **Endpoint Governing Policies** tab.
|
|
|
|
1. Select **Create EGP policy**.
|
|
|
|
1. Enter **`business-hrs`** in the **Name** field.
|
|
|
|
1. Enter the [**`business-hrs.sentinel`** policy](#sentinel-policies-1) in the
|
|
**Policy** editor.
|
|
|
|
1. Select **soft-mandatory** from the **Enforcement level** drop-down list.
|
|
|
|
1. Enter **`secret/accounting/*`** in the **Paths** field, and then click
|
|
**Create Policy**.
|
|
|
|
![EGP](/img/vault-sentinel-1.png)
|
|
|
|
1. Select **Endpoint Governing Policies** again, and then **Create EGP policy**.
|
|
|
|
1. Enter **`cidr-check`** in the **Name** field.
|
|
|
|
1. Enter the [**`cidr-check.sentinel`** policy](#sentinel-policies-1) in the
|
|
**Policy** editor.
|
|
|
|
1. Leave the **Enforcement level** as hard-mandatory, and enter **`secret/*`**
|
|
in the **Paths** field.
|
|
|
|
1. Click **Create Policy**.
|
|
|
|
~> **NOTE:** Unlike ACL policies, EGPs are a _prefix walk_ which allows policies
|
|
to be applied at various points at Vault API. If you have EGPs tied to
|
|
"**`secret/orders`**", "**`secret/*`**" and "**`*`**", all EGPs will be
|
|
evaluated for a request on "**`secret/orders`**".
|
|
|
|
#### Verification
|
|
|
|
Once the policies were deployed, `create`, `update` and `delete` operations
|
|
coming from an IP address other than `122.22.3.4` will be denied.
|
|
|
|
```shell-session
|
|
$ vault kv put secret/accounting/test acct_no="293472309423"
|
|
|
|
Error writing data to secret/accounting/test: Error making API request.
|
|
|
|
URL: PUT http://127.0.0.1:8200/v1/secret/accounting/test
|
|
Code: 400. Errors:
|
|
|
|
* 1 error occurred:
|
|
|
|
* egp standard policy "cidr-check" evaluation resulted in denial.
|
|
|
|
The specific error was:
|
|
<nil>
|
|
|
|
A trace of the execution for policy "cidr-check" is available:
|
|
|
|
Result: false
|
|
|
|
Description: Check the precondition before execute the cidrcheck
|
|
|
|
Rule "main" (byte offset 442) = false
|
|
false (offset 314): sockaddr.is_contained
|
|
|
|
Rule "cidrcheck" (byte offset 291) = false
|
|
|
|
Rule "precond" (byte offset 113) = true
|
|
true (offset 134): request.operation in ["create", "update", "delete"]
|
|
true (offset 194): strings.has_prefix
|
|
```
|
|
|
|
Similarly, you will get an error if any request is made outside of the business
|
|
hours defined by the `business-hrs` policy.
|
|
|
|
!> **NOTE:** Like with ACL policies, **`root`** tokens are **_NOT_** subject to
|
|
Sentinel policy checks.
|
|
|
|
### Step 4: Delete Sentinel Policies ((#step4))
|
|
|
|
#### CLI Command
|
|
|
|
To delete EGPs:
|
|
|
|
```shell
|
|
# Delete the business-hrs EGP
|
|
$ vault delete sys/policies/egp/business-hrs
|
|
|
|
# Delete the cidr-check EGP
|
|
$ vault delete sys/policies/egp/cidr-check
|
|
```
|
|
|
|
#### API call using cURL
|
|
|
|
To delete EGPs:
|
|
|
|
```shell
|
|
# Delete the business-hrs EGP
|
|
$ curl --header "X-Vault-Token: ..." \
|
|
--request DELETE \
|
|
http://127.0.0.1:8200/v1/sys/policies/egp/business-hrs
|
|
|
|
# Delete the cidr-check EGP
|
|
$ curl --header "X-Vault-Token: ..." \
|
|
--request DELETE \
|
|
http://127.0.0.1:8200/v1/sys/policies/egp/cidr-check
|
|
```
|
|
|
|
#### Web UI
|
|
|
|
1. Select **Policies** and select the **Endpoint Governing Policies** tab.
|
|
|
|
1. Select **Delete** from the policy menu for `business-hrs`.
|
|
|
|
![Delete EGP](/img/vault-sentinel-2.png)
|
|
|
|
1. When prompted, click **Delete** again to confirm.
|
|
|
|
1. Repeat the steps to delete `cidr-check` policy.
|
|
|
|
## Next steps
|
|
|
|
Refer to the [Sentinel Properties](/docs/enterprise/sentinel/properties)
|
|
documentation for the full list of properties available in Vault to write
|
|
fine-grained policies to meet your organizational policy requirements.
|