Policy
Last updated
Was this helpful?
Last updated
Was this helpful?
Policy-as-code is the idea of expressing rules using a high-level programming language and treating them as you normally treat code, which includes version control as well as continuous integration and deployment. This approach extends the infrastructure-as-code approach to also cover the rules governing this infrastructure, and the platform that manages it.
Spacelift as a development platform is built around this concept and allows defining policies that involve various decision points in the application. User-defined policies can decide:
Login: to your Spacelift account and with what level of access;
Access: and with what level of access;
Approval: and how a run can be approved;
Initialization: ;
Plan: ;
Push: ;
Task: ;
Trigger: ;
Please refer to the following table for information on what each policy types returns, and the rules available within each policy.
Type
Purpose
Types
Returns
Rules
Allow or deny login, grant admin access
Positive and negative
boolean
allow, admin, deny, deny_admin
Grant or deny appropriate level of stack access
Positive and negative
boolean
read, write, deny, deny_write
Who can approve or reject a run and how a run can be approved
Positive and negative
boolean
approve, reject
Negative
set<string>
deny
Negative
set<string>
deny, warn
Determines how a Git push event is interpreted
Positive and negative
boolean
track, propose, ignore, ignore_track, notrigger
Negative
set<string>
deny
Positive
set<string>
trigger
You can think of policies as snippets of code that receive some JSON-formatted input and are allowed to produce some output in a predefined form. This input normally represents the data that should be enough to make some decision in its context. Each policy type exposes slightly different data, so please refer to their respective schemas for more information.
Multiple policies of the same type can be attached to a single stack, in which case they are evaluated separately to avoid having their code (like local variables and helper rules) affect one another. However, once these policies are evaluated against the same input, their results are combined. So if you allow user login from one policy but deny it from another, the result will still be a denial.
To keep policies functionally pure and relatively snappy, we disabled some Rego built-ins that can query external or runtime data. These are:
http.send
opa.runtime
rego.parse_module
time.now_ns
trace
Policies must be self-contained and cannot refer to external resources (e.g., files in a VCS repository).
There are currently eight types of supported policies and while each of them is different, they have a lot in common. In particular, they can fall into one of the two groups based on what rules are expected to return.
Boolean
Set of Strings
Here's a practical difference between the two types:
For the policies that generate a set of strings, you want these strings to be both informative and relevant, so you'll see this pattern a lot in the examples:
The following helper functions can be used in Spacelift policies:
Name
Description
output := sanitized(x)
output
is the string x
sanitized using the same algorithm we use to sanitize secrets.
result := exec(x)
On the other hand, if you want to create a policy in the UI, here's how you could go about that. Note that you must be a Spacelift admin to manage policies. First, go to the Policies screen in your account view, and click the Add policy button:
Once you're done, click on the Create policy button to save it. Don't worry, policy body is mutable so you'll always be able to edit it if need be.
Policy Attachment Example
In the example below, the policy will be automatically attached to all stacks/modules with the label production
.
Wildcard Policy Attachments
In addition to being able to automatically attach policies using a specific label, you can also choose to attach a policy to stacks/modules in the account using a wildcard, for example using autoattach:*
as a label on a policy, will attach the policy to all stacks/modules.
In the web UI attaching policies is done in the stack management view, in the Policies tab:
One thing we've noticed while working with policies in practice is that it takes a while to get them right. This is not only because the concept or the underlying language introduce a learning curve, but also because the feedback cycle can be slow: write a plan policy, make a code change, trigger a run, verify policy behavior... rinse and repeat. This can easily take hours.
Each of Spacelift's policies supports an additional boolean rule called sample
. Returning true
from this rule means that the input to the policy evaluation is captured, along with the policy body at the time and the exact result of the policy evaluation. You can for example just capture every evaluation with a simple:
You can also sample a certain percentage of policy evaluations. Given that we don't generally allow nondeterministic evaluations, you'd need to depend on a source of randomness internal to the input. In this example we will use the timestamp - note that since it's originally expressed in nanoseconds, we will turn it into milliseconds to get a better spread. We'll also want to sample every 10th evaluation:
Capturing all evaluations sounds tempting but it will also be extremely messy. We're only showing 100 most recent evaluations from the past 7 days, so if you capture everything then the most valuable samples can be drowned by irrelevant or uninteresting ones. Also, sampling adds a small performance penalty to your operations.
In order to get to the policy workbench, first click on the Edit button in the upper right hand corner of the policy screen:
Then, click on the Show simulation panel link on the right hand side of the screen:
If your policy has been used evaluated and sampled, your screen should look something like this:
On the left hand side you have the policy body. On the right hand side there's a dropdown with timestamped evaluations (inputs) of this policy, color-coded for their ultimate outcome. Selecting one of the inputs allows you to simulate the evaluation:
While running simulations, you can edit both the input and the policy body. If you edit the policy body, or choose an input that has been evaluated with a different policy body, you will get a warning like this:
Clicking on the Show changes link within that warning shows you the exact difference between the policy body in the editor panel, and the one used for evaluating the selected input:
Once you're happy with your new policy body, you can click on the Save changes button to make sure that the new body is used for future evaluations.
Last but not least, the policy workbench - including access to previous inputs - is only available to Spacelift account administrators.
The whole point of policy-as-code is being able to handle it as code, which involves everyone's favorite bit - testing. Testing policies is crucial because you don't want them accidentally allow the wrong crowd to do the wrong things.
You'll see that we simply mock out the input
received by the policy:
We can then test it in the console using opa test
command (note the glob, which captures both the source and its associated test):
In the respective test, we will check that the set return by the deny rule either has the expected element for the matching input, or is empty for non-matching one:
Again, we can then test it in the console using opa test
command (note the glob, which captures both the source and its associated test):
We suggest you always unit test your policies and apply the same continuous integration principles as with your application code. You can set up a CI project using the vendor of your choice for the same repository that's linked to the Spacelift project that's defining those policies, to get an external validation.
Blocks suspicious before they
Gives feedback on after phase
Blocks suspicious from running
Selects for which to trigger a
Spacelift uses an open-source project called and its rule language, , to execute user-defined pieces of code we call Policies at various decision points. Policies come in different flavors that we call types, with each type being executed at a different decision point.
Except for that are global, all other policy types operate on the level, and they can be attached to multiple stacks, just as are, which both facilitates code reuse and allows flexibility. Policies only affect stacks they're attached to. Please refer to the for more information about attaching policies.
- the language that we're using to execute policies - is a very elegant, Turing incomplete data query language. It takes a few hours (tops) to get your head around all of its quirks but if you can handle SQL and the likes of , you'll find Rego pretty familiar. For each policy, we also give you plenty of examples that you can tweak to achieve your goals, and each of those examples comes with a link allowing you to execute it in .
Disabling time.now_ns
may seem surprising at first - after all, what's wrong with getting the current timestamp? Alas, depending on the current timestamp will make your policies impure and thus tricky to test - and we encourage you to ! You will notice though that the current timestamp in Rego-compatible form (Unix nanoseconds) is available as request.timestamp_ns
in every policy payload, so please use it instead.
and policies expect rules to return a boolean value (true or false). Each type of policy defines its own set of rules corresponding to different access levels. In these cases, various types of rules can be positive or negative - that is, they can explicitly allow or deny access.
The second group of policies (, , and ) is expected to generate a that serve as direct feedback to the user. Those rules are generally negative in that they can only block certain actions - it's only their lack that counts as an implicit success.
Executes the command x
. result
is an object containing status
, stdout
and stderr
. Only applicable for run initialization policies for .
There are two ways of creating policies - through the web UI and through the . We generally suggest the latter as it's much easier to manage down the line and . Here's how you'd define a plan policy in Terraform and attach it to a stack (also created here with minimal configuration for completeness):
This takes you to the policy creation screen where you can choose the type of policy you want to create, and edit its body. For each type of policy you're also given an explanation and a few examples. We'll be creating an that gives members of the Engineering GitHub team read access to a stack:
Policies, with the exception of , can be automatically attached to stacks using the autoattach:label
special label where label
is the name of a label attached to stacks and/or modules in your Spacelift account you wish the policy to be attached to.
Enter policy workbench. Policy workbench allows you to capture policy evaluation events so that you can adjust the policy independently and therefore shorten the entire cycle. In order to make use of the workbench, you will first need to .
If that feels a bit simplistic and spammy, you can adjust this rule to capture only certain types of inputs. For example, in this case we will only want to capture evaluations that returned in an empty least for deny
reasons (eg. with a or policy):
In order to show you how to work with the policy workbench, we are going to use a that whitelists just two tasks - an innocent ls
, and tainting a particular resource. It also only samples successful evaluations, where the list of deny
reasons is empty:
This example comes from our , which gives you hands-in experience with most Spacelift functionalities within 10-15 minutes, depending on whether you like to RTFM or not. We strongly recommend you give it a go.
Yes, policy sampling is perfectly safe. Session data may contain some personal information like username, name and IP, but that data is only persisted for 7 days. Most importantly, in the inputs hash all the string attributes of resources, ensuring that no sensitive data leaks through this means.
In the examples for each type of policy we invite you to play around with the policy and its input . While certainly useful, we won't consider it proper unit testing.
Luckily, Spacelift uses a well-documented and well-supported open source language called Rego, which has built-in support for testing. Testing Rego is extensively covered in so in this section we'll only look at things specific to Spacelift.
Let's define a simple that denies access to , and write a test for it:
Testing policies that provide feedback to the users is only slightly more complex. Instead of checking for boolean values, you'll be testing for set equality. Let's define a simple that denies commits to a particular branch (because why not):