Plan policy»
Purpose»
Info
Please note, we currently don't support importing rego.v1
Plan policies are evaluated during a planning phase after vendor-specific change preview command (eg. terraform plan
) executes successfully. The body of the change is exported to JSON and parts of it are combined with Spacelift metadata to form the data input to the policy.
Plan policies are the only ones that have access to the actual changes to the managed resources, so this is probably the best place to enforce organizational rules and best practices as well as do automated code review. There are two types of rules here that Spacelift will care about: deny and warn. Each of them must come with an appropriate message that will be shown in the logs. Any deny rules will print in red and will automatically fail the run, while warn rules will print in yellow and will at most mark the run for human review if the change affects the tracked branch and the Stack is set to autodeploy.
Here is a super simple policy that will show both types of rules in action:
1 2 3 4 5 6 7 8 9 |
|
Let's create this policy, attach it to a Stack and take it for a spin by triggering a run:
Yay, that works (and it fails our plan, too), but it's not terribly useful - unless of course you want to block all changes to your Stack in a really clumsy way. Let's look a bit deeper into the document that each plan policy receives, two possible use cases - rule enforcement and automated code review - and some cookbook examples.
Data input»
This is the schema of the data input that each policy request will receive.
If the policy is executed for the first time, the previous_run
field will be missing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
|
Aliases»
In addition to our helper functions, we provide aliases for commonly used parts of the input data:
Alias | Description |
---|---|
affected_resources |
List of the resources that will be created, deleted, and updated by Terraform |
created_resources |
List of the resources that will be created by Terraform |
deleted_resources |
List of the resources that will be deleted by Terraform |
recreated_resources |
List of the resources that will be deleted and then created by Terraform |
updated_resources |
List of the resources that will be updated by Terraform |
String Sanitization»
Sensitive properties in "before"
and "after"
objects will be sanitized to protect secret values. Sanitization hashes the value and takes the last 8 bytes of the hash.
If you need to compare a string property to a constant, you can use the sanitized(string)
helper function.
1 2 3 4 5 6 7 8 9 10 |
|
Custom inputs»
Sometimes you might want to pass some additional data to your policy input. For example, you may want to pass the configuration
data from the Terraform plan, the result of a third-party API or tool call. You can do that by generating a JSON file with the data you need at the root of your project. The file name must follow the pattern $key.custom.spacelift.json
and must represent a valid JSON object. The object will be merged with the rest of the input data, as input.third_party_metadata.custom.$key
. Be aware that the file name is case-sensitive. Below are two examples, one exposing Terraform configuration and the other exposing the result of a third-party security tool.
Tip
To learn more about integrating security tools with Spacelift using custom inputs, please refer to our blog post.
Example: exposing Terraform configuration to the plan policy»
Let's say you want to expose the Terraform configuration to the plan policy to ensure that only the "blessed" modules are used to provision resources. You would then add the following command to the list of after_plan
hooks:
1 |
|
The data will be available in the policy input as input.third_party_metadata.custom.configuration
. Note that this depends on the jq
tool being available in the runner image (it is installed by default on our standard image).
Example: passing custom tool output to the plan policy»
For this example, let's use the awesome open-source Terraform security scanner called tfsec. What you want to accomplish is to generate tfsec
warnings as JSON and have them reported and processed using the plan policy. In this case, you can run tfsec
as a before_init
hook and save the output to a file:
1 |
|
The data will be available in the policy input as input.third_party_metadata.custom.tfsec
. Note that this depends on the tfsec
tool being available in the runner image - you will need to install it yourself, either directly on the image, or as part of your before_init
hook.
Some vulnerability scanning tools, like tfsec, will return a non-zero exit code when they encounter vulnerabilities, which will result in a stack failure. As the majority of these tools provide a soft scanning option that will show all the vulnerabilities without considering the command as failed, we can leverage those.
However, if there is a tool that doesn't offer that possibility, you can easily overcome this by appending || true
at the end of the command as this will always return a zero exit code.
Use cases»
Since plan policies get access to the changes to your infrastructure that are about to be introduced, they are the right place to run all sorts of checks against those changes. We believe that there are two main use cases for those checks - hard rule enforcement preventing shooting yourself in the foot and automated code review that augments human decision-making.
Organizational rule enforcement»
In every organization, there are things you just don't do. Hard-won knowledge embodied by lengthy comments explaining that if you touch this particular line the site will go hard down and the on-call folks will be after you. Potential security vulnerabilities that can expose all your infra to the wrong crowd. Spacelift allows turning them into policies that simply can't be broken. In this case, you will most likely want to exclusively use deny rules.
Let's start by introducing a very simple and reasonable rule - never create static AWS credentials:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Here's a minimal example of this rule in the Rego playground.
If that makes sense, let's try defining a policy that implements a slightly more sophisticated piece of knowledge - that when some resources are recreated, they should be created before they're destroyed or an outage will follow. We found that to be the case with the aws_batch_compute_environment
, among others. So here it is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Here's the obligatory Rego playground example.
While in most cases you'll want your rules to only look at resources affected by the change, you're not limited to doing so. You can also look at all resources and force teams to remove certain resources. Here's an example - until all AWS resources are removed all in one go, no further changes can take place:
1 2 3 4 5 6 7 8 9 10 11 |
|
Feel free to play with a minimal example of this policy in the Rego playground.
Automated code review»
OK, so rule enforcement is very powerful, sometimes you want something more sophisticated. Instead of (or in addition to) enforcing hard rules, you can use plan policy rules to help humans understand changes better, and make informed decisions what looks good and what does not. This time we'll be adding more color to our policies and start using warn rules in addition to deny ones we've already seen in action.
You've already seen warn rules in the first section of this article but here it is in action again:
It won't fail your plan and it looks relatively benign, but this little yellow note can provide great help to a human reviewer, especially when multiple changes are introduced. Also, if a stack is set to autodeploy, the presence of a single warning is enough to flag the run for a human review.
The best way to use warn and deny rules together depends on your preferred Git workflow. We've found short-lived feature branches with Pull Requests to the tracked branch to work relatively well. In this scenario, the type
of the run
is important - it's PROPOSED for commits to feature branches, and TRACKED on commits to the tracked branch. You will probably want at least some of your rules to take that into account and use this mechanism to balance comprehensive feedback on Pull Requests and flexibility of being able to deploy things that humans deem appropriate.
As a general rule when using plan policies for code review, deny when run type is PROPOSED and warn when it is TRACKED. Denying tracked runs unconditionally may be a good idea for most egregious violations for which you will not consider an exception, but when this approach is taken to an extreme it can make your life difficult.
We thus suggest that you at most deny when the run is PROPOSED, which will send a failure status to the GitHub commit, but will give the reviewer a chance to approve the change nevertheless. If you want a human to take another look before those changes go live, either set stack autodeploy to false, or explicitly warn about potential violations. Here's an example of how to reuse the same rule to deny or warn depending on the run type:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Predictably, this fails when committed to a non-tracked (feature) branch:
...but as a GitHub repo admin you can still merge it if you've set your branch protection rules accordingly:
Cool, let's merge it and see what happens:
Cool, so the run stopped in its tracks and awaits human decision. At this point we still have a choice to either confirm or discard the run. In the latter case, you will likely want to revert the commit that caused the problem - otherwise all subsequent runs will be affected.
The minimal example for the above rule is available in the Rego playground.
Examples»
Tip
We maintain a library of example policies that are ready to use or that you could tweak to meet your specific needs.
If you cannot find what you are looking for below or in the library, please reach out to our support and we will craft a policy to do exactly what you need.
Require human review when resources are deleted or updated»
Adding resources may ultimately cost a lot of money but it's generally pretty safe from an operational perspective. Let's use a warn
rule to allow changes with only added resources to get automatically applied, and require all others to get a human review:
1 2 3 4 5 6 7 8 9 10 11 |
|
Here's a minimal example to play with.
Automatically deploy changes from selected individuals»
Sometimes there are folks who really know what they're doing and changes they introduce can get deployed automatically, especially if they already went through code review. Below is an example that allows commits from whitelisted individuals to be deployed automatically (assumes Stack is set to autodeploy):
1 2 3 4 5 6 7 8 9 |
|
Here's the playground example for your enjoyment.
Require commits to be reasonably sized»
Massive changes make reviewers miserable. Let's automatically fail all changes that affect more than 50 resources. Let's also allow them to be deployed with mandatory human review nevertheless:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Here's the above example in the Rego playground, with the threshold set to 1 for simplicity.
Back-of-the-envelope blast radius»
This is a fancy contrived example building on top of the previous one. However, rather than just looking at the total number of affected resources, it attempts to create a metric called a "blast radius" - that is how much the change will affect the whole stack. It assigns special multipliers to some types of resources changed and treats different types of changes differently: deletes and updates are more "expensive" because they affect live resources, while new resources are generally safer and thus "cheaper". As per our automated code review pattern, we will fail Pull Requests with changes violating this policy, but require human action through warnings when these changes hit the tracked branch.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
You can play with a minimal example of this policy in The Rego Playground.
Cost management»
Thanks to our Infracost integration, you can take cost information into account when deciding whether to ask for human approval or to block changes entirely.