a3dfde5cec
* conversion stage 1 * correct image paths * add sidebar title to frontmatter * docs/concepts and docs/internals * configuration docs and multi-level nav corrections * commands docs, index file corrections, small item nav correction * secrets converted * auth * add enterprise and agent docs * add extra dividers * secret section, wip * correct sidebar nav title in front matter for apu section, start working on api items * auth and backend, a couple directory structure fixes * remove old docs * intro side nav converted * reset sidebar styles, add hashi-global-styles * basic styling for nav sidebar * folder collapse functionality * patch up border length on last list item * wip restructure for content component * taking middleman hacking to the extreme, but its working * small css fix * add new mega nav * fix a small mistake from the rebase * fix a content resolution issue with middleman * title a couple missing docs pages * update deps, remove temporary markup * community page * footer to layout, community page css adjustments * wip downloads page * deps updated, downloads page ready * fix community page * homepage progress * add components, adjust spacing * docs and api landing pages * a bunch of fixes, add docs and api landing pages * update deps, add deploy scripts * add readme note * update deploy command * overview page, index title * Update doc fields Note this still requires the link fields to be populated -- this is solely related to copy on the description fields * Update api_basic_categories.yml Updated API category descriptions. Like the document descriptions you'll still need to update the link headers to the proper target pages. * Add bottom hero, adjust CSS, responsive friendly * Add mega nav title * homepage adjustments, asset boosts * small fixes * docs page styling fixes * meganav title * some category link corrections * Update API categories page updated to reflect the second level headings for api categories * Update docs_detailed_categories.yml Updated to represent the existing docs structure * Update docs_detailed_categories.yml * docs page data fix, extra operator page remove * api data fix * fix makefile * update deps, add product subnav to docs and api landing pages * Rearrange non-hands-on guides to _docs_ Since there is no place for these on learn.hashicorp, we'll put them under _docs_. * WIP Redirects for guides to docs * content and component updates * font weight hotfix, redirects * fix guides and intro sidenavs * fix some redirects * small style tweaks * Redirects to learn and internally to docs * Remove redirect to `/vault` * Remove `.html` from destination on redirects * fix incorrect index redirect * final touchups * address feedback from michell for makefile and product downloads
156 lines
5.3 KiB
Markdown
156 lines
5.3 KiB
Markdown
---
|
|
layout: "docs"
|
|
page_title: "Sentinel Examples"
|
|
sidebar_title: "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"
|
|
import "mfa"
|
|
import "strings"
|
|
|
|
# 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()
|
|
}
|
|
```
|