153 lines
5.2 KiB
Markdown
153 lines
5.2 KiB
Markdown
|
---
|
||
|
layout: "docs"
|
||
|
page_title: "Sentinel Examples"
|
||
|
sidebar_current: "docs-vault-enterprise-sentinel-examples"
|
||
|
description: |-
|
||
|
An overview of how Sentinel interacts with Vault Enterprise.
|
||
|
|
||
|
---
|
||
|
|
||
|
# Examples
|
||
|
|
||
|
Following are some examples that help to introduce concepts. If you are
|
||
|
unfamiliar with writing Sentinel policies in Vault, please read through to
|
||
|
understand some best practices.
|
||
|
|
||
|
## MFA and CIDR Check on Login
|
||
|
|
||
|
The following Sentinel policy requires the incoming user to successfully
|
||
|
validate with an Okta MFA push request before authenticating with LDAP.
|
||
|
Additionally, it ensures that only users on the 10.20.0.0/16 subnet are able to
|
||
|
authenticate using LDAP.
|
||
|
|
||
|
```python
|
||
|
import "sockaddr"
|
||
|
|
||
|
# We expect logins to come only from our private IP range
|
||
|
cidrcheck = rule {
|
||
|
sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
|
||
|
}
|
||
|
|
||
|
# Require Ping MFA validation to succeed
|
||
|
ping_valid = rule {
|
||
|
mfa.methods.ping.valid
|
||
|
}
|
||
|
|
||
|
main = rule when strings.has_prefix(request.path, "auth/ldap/login") {
|
||
|
ping_valid and cidrcheck
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Note the `rule when` construct on the `main` rule. This scopes the policy to
|
||
|
the given condition.
|
||
|
|
||
|
Vault takes a default-deny approach to security. Without such scoping, because
|
||
|
active Sentinel policies must all pass successfully, the user would be forced
|
||
|
to start with a passing status and then define the conditions under which
|
||
|
access is denied, breaking the default-deny concept.
|
||
|
|
||
|
By instead indicating the conditions under which the `main` rule (and thus, in
|
||
|
this example, the entire policy) should be evaluated, the policy instead
|
||
|
describes the conditions under which a matching request is successful. This
|
||
|
keeps the default-deny feeling of Vault; if the evaluation condition isn't met,
|
||
|
the policy is simply a no-op.
|
||
|
|
||
|
## Allow Only Specific Identity Entities or Groups
|
||
|
|
||
|
```python
|
||
|
main = rule {
|
||
|
identity.entity.name is "jeff" or
|
||
|
identity.entity.id is "fe2a5bfd-c483-9263-b0d4-f9d345efdf9f" or
|
||
|
"sysops" in identity.groups.names or
|
||
|
"14c0940a-5c07-4b97-81ec-0d423accb8e0" in keys(identity.groups.by_id)
|
||
|
}
|
||
|
```
|
||
|
|
||
|
This example shows accessing Identity properties to make decisions, showing
|
||
|
that for Identity values IDs or names can be used for reference.
|
||
|
|
||
|
In general, it is more secure to use IDs. While convenient, entity names and
|
||
|
group names can be switched from one entity to another, because their only
|
||
|
constraint is that they must be unique. Using IDs guarantees that only that
|
||
|
specific entity or group is sufficient; if the group or entity are deleted and
|
||
|
recreated with the same name, the match will fail.
|
||
|
|
||
|
## Instantly Disallow All Previously-Generated Tokens
|
||
|
|
||
|
Imagine a break-glass scenario where it is discovered that there have been
|
||
|
compromises of some unknown number of previously-generated tokens.
|
||
|
|
||
|
In such a situation it would be possible to revoke all previous tokens, but
|
||
|
this may take a while for a number of reasons, from requiring revocation of
|
||
|
generated secrets to the simple delay required to remove many entries from
|
||
|
storage. In addition, it could revoke tokens and generated secrets that later
|
||
|
forensic analysis shows were not compromised, unnecessarily widening the impact
|
||
|
of the mass revocation.
|
||
|
|
||
|
In Vault's ACL system a simple deny could be put into place, but this is a very
|
||
|
coarse-grained control and would require forethought to ensure that a policy
|
||
|
that can be modified in such a way is attached to every token. It also would
|
||
|
not prevent access to login paths or other unauthenticated paths.
|
||
|
|
||
|
Sentinel offers much more fine-grained control:
|
||
|
|
||
|
```python
|
||
|
import "time"
|
||
|
|
||
|
main = rule when not request.unauthenticated {
|
||
|
time.load(token.creation_time).unix >
|
||
|
time.load("2017-09-17T13:25:29Z").unix
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Created as an EGP on `*`, this will block all access to any path Sentinel
|
||
|
operates on with a token created before the given time. Tokens created after
|
||
|
this time, since they were not a part of the compromise, will not be subject to
|
||
|
this restriction.
|
||
|
|
||
|
## Delegate EGP Policy Management Under a Path
|
||
|
|
||
|
The following policy gives token holders with this policy (via their tokens or
|
||
|
their Identity entities/groups) the ability to write EGP policies that can only
|
||
|
take effect at Vault paths below certain prefixes. This effectively delegates
|
||
|
policy management to the team for their own key-value spaces.
|
||
|
|
||
|
```python
|
||
|
import "strings"
|
||
|
|
||
|
data_match = func() {
|
||
|
# Make sure there is request data
|
||
|
if length(request.data else 0) is 0 {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
# Make sure request data includes paths
|
||
|
if length(request.data.paths else 0) is 0 {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
# For each path, verify that it is in the allowed list
|
||
|
for strings.split(request.data.paths, ",") as path {
|
||
|
# Make it easier for users who might be used to starting paths with
|
||
|
# slashes
|
||
|
sanitizedPath = strings.trim_prefix(path, "/")
|
||
|
if not strings.has_prefix(sanitizedPath, "dev-kv/teama/") and
|
||
|
not strings.has_prefix(sanitizedPath, "prod-kv/teama/") {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
# Only care about writing; reading can be allowed by normal ACLs
|
||
|
precond = rule {
|
||
|
request.operation in ["create", "update"] and
|
||
|
strings.has_prefix(request.path, "sys/policies/egp/")
|
||
|
}
|
||
|
|
||
|
main = rule when precond {
|
||
|
strings.has_prefix(request.path, "sys/policies/egp/teama-") and data_match()
|
||
|
}
|
||
|
```
|