Taxonomy of Policies
There's a number of dimensions that can be used to describe a policy. They are:
- the values the policy accepts
- whether it depends on other policies
- and if it's calculated or not.
Respectively, these are Simple policies, Compound policies and Calculated policies. In the course of building policies to satisfy a particular control objective, all three types may be used
In ascending order of complexity: Simple, Compound, Calculated, Calculated Simple, Calculated Compound.
Simple: A policy that takes a single value without any dependent policies.
- Simple policies can have the following values:
- Enumerated values
- Text field
- OCL OCL Reference
- YAML
- HCL/Terraform
- Simple policies can have the following values:
Compound: A policy that depends on values in other policies to determine what to do. A common examples is the
Active
collections of policies. (Other examples of policy collections areApproved
,Configured
,Logging
, andPermissions
to name a few.) On its own theActive
policy dictates what to do when an inactive resource is found. It relies on theActive > Age
andActive > Last Modified
child policies to determine if the resource is active.Calculated: Calculated policies are most often used when a customer needs to implement business logic when evaluating a resource. In contrast to Simple and Compound policies, a calculated policy can implement custom logic based on CMDB information. Any policy in Guardrails can be made into a calculated policy.
Multi-query Calculated: Some calculated policies benefit from or require multiple stages of refinement of input data. For example: A control objective on a particular network resource type depends on the presence of other resources in the same virtual network. A single-query calc policy approach would grab all resources of that resource type for the entire workspace then filter that huge list in the Nunjucks template. Essentially asking "Return all resources of this resource type everywhere." In large environments, this can result in thousands or tens of thousands of
items
. Calc policy GraphQL results are hard limited to 300 results with no paging possible. The multi-query approach instead starts with the question "What Virtual Network is this resource in?" then asks "In this virtual network specifically, what network resources are available?". Now, instead of thousands ofitems
, the calc policy only has to deal with a few relevant resources. The benefit of multi-query is that policy developers are assured they get exactly the data they need with the cost of some added complexity in their policies.
Examples
Simple Policy Example
Consider
AWS > CloudWatch > Alarm > CMDB
as an example of a simple policy. It accepts one of three policy values: Skip
,
Enforce: Enabled
, Enforce: Disabled
. Selecting one of these values is all
that's required.
Compound Policy Example
Let's look at the
AWS > CloudWatch > Alarm > Active
policy collection. The patterns described below will hold for other compound
policies. The collection includes with these (truncated for readability) value
sets:
AWS > CloudWatch > Alarm > Active
SkipCheck: ActiveEnforce: Delete inactive with 1 day warningEnforce: Delete inactive with 3 days warningEnforce: Delete inactive with 7 days warning
AWS > CloudWatch > Alarm > Active > Age
SkipForce inactive if age > 1 dayForce inactive if age > 3 daysForce inactive if age > 7 days
AWS > CloudWatch > Alarm > Active > Last Modified
SkipActive if last modified <= 1 dayActive if last modified <= 3 daysActive if last modified <= 7 days
We can see that the Active
policy is the only one that asserts what Guardrails
should do with an out of compliance resource. It will Skip
, Check
or
Enforce
. The Age
and Last Modified
policies make assertions about the
state of the resource itself. Remember that Skip
means "ignore" or "do not
evaluate" to Guardrails, and that almost all policies default to Skip
.
Setting the Active
policy alone will put the Active
control into an
Invalid
state because dependent policies of Active > Age
and
Active > Last Modified
are in Skip
. There's no information for the Active
policy to make a decision on. If we set Active > Age
to some value other than
Skip
, then Active
can make a decision.
Calculated Policies
Any policy in Guardrails can be turned into a calculated policy. A calculated policy is used anytime the basic action provided by Guardrails needs to be dependent on some other information. A common example is to make a resource's tag key/value dependent on whether the resource is in a production or nonproduction environment. More complex organizational business rules are often implemented in calculated policies.
Calculated policies rely on three things: The information contained in the CMDB for the target resource, the GraphQL query to access that information and the Nunjucks template to evaluate it.
CMDB: Definitions for a resource's CMDB entry can be found in the Mods Repository documentation. For our CloudWatch Alarm example, we would start with the Alarm resource summary then go to the Alarm resource definition. The easiest way to explore is to create an instance of the resource you want to work on, then look at the data provided in the Overview tab.
GraphQL: Use the Developer console in your Guardrails workspace to explore the
resource
query. Under the hood, the calculated policy dialog is making a
similar query. Provided you have an example resource already, navigate to that
resource in the Guardrails console. Go to the Overview tab then look for id
under
the turbot
section. Include that long number in the id
field of the resource
query in the Developer Console.
When working on calculated policies, make sure to select a target resource. The below examples won't make sense if you don't pick a resource.
A simple starter query in the Developer Console might look like:
query SimpleResourceQuery { resource(id: "196925434872486") { data turbot { id akas } }}
The equivalent calculated policy query is:
{ resource { data turbot { id akas } }}
Note the implicit inclusion of the resource ID that the calculated policy takes care of for you.
Nunjucks: The
Nunjucks docs describe the
full capabilities of what it can do. Developers familiar with string
manipulation, if-else
, for
and range
should be able to orient quickly. A
deep discussion of how to script with Nunjucks is outside the scope of this
guide.
Familiarity with the CMDB data for the target resource, GraphQL and Nunjucks is essential to quickly making progress on calculated policies. Be aware that sometimes a deep understanding of how the cloud provider represents a resource is required. Data that appears in intuitive places in the provider's UI may not be intuitively placed in the underlying data structure.
Compound Calculated Policies
Calculated compound policies: Any policy in Guardrails can be made into a calculated
policy. A common compound policy configuration is to have the Active
policy
statically set to Check
or Enforce
then have the Active > Age
policy be
calculated. However, this can sometimes result in the Active
control going
into an invalid
state, as they require at least one of the sub policies not be
in Skip
. To solve this problem, a calc policy can be placed on the Active
policy with similar logic to the Active > Age
policy. This way, evaluation of
the Active
control as a whole will go to Skipped
and avoid the invalid
state.
Multi-query Calculated Policies
The below Terraform excerpt shows the YAML array of GraphQL+Nunjucks templates.
- | { account { turbot { id } } }- | { account { turbot { id } } trails: resources(filter: "resourceId:{{ $.account.turbot.id }} resourceTypeId:'tmod:@turbot/aws-cloudtrail#/resource/types/trail' $.Status.IsLogging:true $.IsMultiRegionTrail:true limit:300") { items { trailName: get(path: "Name") cloudWatchLogsLogGroupArn: get(path: "CloudWatchLogsLogGroupArn") eventSelectors: get(path: "EventSelectors") } } }
Notes:
- The YAML
- |
symbols are essential as this indicates that this is an array of queries and the strings in each array element are multi-line. These- |
array indicators should be on their own lines. YAML is also particular about indentation. - Only the results of the final query are sent to the control. Any intermediate results must also be included in the final query. The example shows how this is done.
- GraphQL permits multiple
resources()
queries when they have labels. Thetrails:
label shows how this is done. - A full example can be found in the Guardrails Samples Repo.