Using Rich Return Types and Map Expressions in Sentinel 0.17
Sentinel 0.17 provides the ability to return non-boolean data within a policy. See examples of how to use this new functionality to improve compliance reporting capabilities.
Sentinel is a policy as code language and framework that enables fine-grained, logic-based policy decisions. Sentinel is an enterprise-only feature and is embedded in HashiCorp Consul, Nomad, Terraform, and Vault.
Recently we announced the release of Sentinel 0.17, which is a fundamental change to the Sentinel runtime. It includes several new features:
- Map Expressions: Provides the ability to create rich reporting data sets.
- Rich Return Types: Adds supports for non-boolean values in rules.
- Emptiness Comparison: Comparison expressions that provide a natural method of checking for empty collections.
- Comparison Enhancements: Maps are now comparable for equality.
- Machine Readable Tracing: Adding JSON output as a CLI option for both
sentinel apply
andsentinel test
. - Base64 Import: Enabling Sentinel policies to work with base64-encoded data.
In this post, I will demonstrate how, by using a mixture of Map Expressions and Rich Return Types, you can greatly improve the process of returning policy compliance reporting data.
» Map Expressions
From Sentinel 0.17 onward, you can use a higher-order map expression to return a list based on an input collection.
In map expressions, a list is always returned regardless of the input collection type. Upon evaluation, each element within an input collection is identified according to the map expression body, and the resulting data is added to the map.
// Input Collection
input = [
{
"name":"sentinel",
"version":"0.17.1",
"shasums":"sentinel_0.17.1_SHA256SUMS",
"shasums_signature":"sentinel_0.17.1_SHA256SUMS.sig",
},
]
// Map Expression
result = map input as _, i {
{
"name": i.name,
"version": i.version,
}
}
main = rule {
result // [{"name": "sentinel", "version": "0.17.1"}]
}
If you would like to view this policy in its entirety, visit the Sentinel Playground.
» Rich Return Types
Rules are a core feature in the Sentinel runtime and form the basis of a policy. Up until now, a rule has been a single boolean expression that returns either true
or false
to indicate a passing or failing rule.
Boolean expression will short-circuit when evaluated, which is critical in time-sensitive applications like HashiCorp Vault. For integrations like Terraform Cloud, which tend to have larger data sets, short-circuit behavior is less of a concern.
When working with large data sets, understanding why a rule evaluation failed, and identifying the offending resources is far more important and can require multiple evaluations before all policy violations are understood and remediated. Achieving the desired result can lead to the adoption of creative workarounds that tend to circumvent the standard behavior and favor round-about tricks to report meaningful data.
With the release of Sentinel 0.17, rules have been updated so that they can evaluate any expression and hold non-boolean values. By supporting all basic types, Sentinel rules can accept collections (lists and maps), strings, and numeric (floating-point and integer) values. Combined with the new higher order map expression, this allows practitioners to create rich sets of report data without having to resort to workarounds such as print
messages and rules that would otherwise serve no real purpose.
» Bringing it All Together
So far we have covered what Map Expressions and Rich Return Types are and why we have introduced them to the Sentinel runtime. Let’s take a look at how we can apply our knowledge to a real-world policy based on the following requirements:
All provisioned server instances should have a type of:
- t2.small
- t2.medium
- t2.large
All server instance configuration that violates the policy requirements should be reported and should include:
- Clear messaging that explains why the violation has occurred
- The address of the offending resource
- The value of the configuration attribute
- The list of allowed configuration values
» Filtering Violations
In order to fulfill the first requirement, we need to declare a list of allowedServerTypes
that we can cross-reference to ensure that all provisioned server instances are compliant.
import "tfplan/v2" as tfplan
// Allowed Server Instance Types
allowedServerTypes = ["t2.small", "t2.medium", "t2.large"]
We then use a filter expression to return a subset of all server instances that have a type
value that violates the values in the allowedServerTypes
list.
// Filter all instances that will be created that has a type value that is not listed in allowedServerTypes
allServerInstanceTypeViolations = filter tfplan.resource_changes as _, rc {
rc.change.actions is ["create"] and
rc.type is "fakewebservices_server" and
rc.change.after.type not in allowedServerTypes
}
We can now take the contents of the allServerInstanceTypeViolations
data set and use it to report all compliance violations.
» Violation Reporting
We can use the new map expression to build a dynamic map that contains a message
that describes why a violation has occurred, the address
of the violating resource as well as the type
value and list of allowed_types
.
// This Sentinel policy ensures that server instance type configuration does not violate a list of allowed types.
main = rule {
// Sentinel map expression providing contextual violation data
map allServerInstanceTypeViolations as _, violation {
{
"message": violation.change.after.name + " has an unsupported instance type",
"address": violation.address,
"type": violation.change.after.type,
"allowed_types": allowedServerTypes,
}
}
}
By encapsulating the expression in main
, we can take advantage of Rich Return Types as well as the enhancements that we have made to Sentinel tracing. With the redesign we have moved from a fully verbose stack-like trace, to a human-readable output that is easier to understand.
1 policies evaluated.
## Policy 1: policy.sentinel (hard-mandatory)
Result: false
policy.sentinel:14:1 - Rule "main"
Description:
This Sentinel policy ensures that server instance type
configuration does not violate a list of allowed types.
Value:
[
{
"message": "BESRV0 has an unsupported instance type"
"address": "fakewebservices_server.backend[0]"
"type": "t2.macro"
"allowed_types": [
"t2.small"
"t2.medium"
"t2.large"
]
}
{...}
]
If you would like to view this policy in its entirety, visit the Sentinel Playground.
» Get Started
The latest release of Sentinel includes several new features that build on previous investments in the policy authoring workflow. You can start exploring these new capabilities now by downloading the latest version of the Sentinel CLI from the Sentinel download page.
For more information on Sentinel language and specification, visit the Sentinel documentation page. If you would like to engage with the community to discuss information related to Sentinel use cases and best practices, visit the HashiCorp Community Forum.
If you would like to play with Sentinel in a safe development environment, you can do so by visiting the Sentinel Playground, which provides the ability to evaluate and share example Sentinel policies and mock data.
Sign up for the latest HashiCorp news
More blog posts like this one
5 ways to improve DevEx and security for infrastructure provisioning
Still using manual scripting and provisioning processes? Learn how to accelerate provisioning using five best practices for Infrastructure Lifecycle Management.
HCP Vault Dedicated adds secrets sync, cross-region DR, EST PKI, and more
The newest HCP Vault Dedicated 1.18 upgrade includes a range of new features that include expanding DR region coverage, syncing secrets across providers, and adding PKI EST among other key features.
Fix the developers vs. security conflict by shifting further left
Resolve the friction between dev and security teams with platform-led workflows that make cloud security seamless and scalable.