---
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.
