> For the complete documentation index, see [llms.txt](https://spacelift-io.gitbook.io/spacelift/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://spacelift-io.gitbook.io/spacelift/concepts/policy.md).

# Policy

## Introduction

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: [who gets to log in](/spacelift/concepts/policy/login-policy.md) to your Spacelift account and with what level of access;
* Access: [who gets to access individual Stacks](/spacelift/concepts/policy/stack-access-policy.md) and with what level of access;
* Approval: [who can approve or reject a run](/spacelift/concepts/policy/approval-policy.md) and how a run can be approved;
* Initialization: [which Runs and Tasks can be started](/spacelift/concepts/policy/run-initialization-policy.md);
* Plan: [which changes can be applied](/spacelift/concepts/policy/terraform-plan-policy.md);
* Push: [how Git push events are interpreted](/spacelift/concepts/policy/git-push-policy.md);
* Task: [which one-off commands can be executed](/spacelift/concepts/policy/task-run-policy.md);
* Trigger: [what happens when blocking runs terminate](/spacelift/concepts/policy/trigger-policy.md);

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                                              |
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------- | ------------- | -------------------------------------------------- |
| [Login](/spacelift/concepts/policy/login-policy.md)                       | Allow or deny login, grant admin access                                                                                   | Positive and negative | `boolean`     | *allow, admin, deny, deny\_admin*                  |
| [Access](/spacelift/concepts/policy/stack-access-policy.md)               | Grant or deny appropriate level of stack access                                                                           | Positive and negative | `boolean`     | *read, write, deny, deny\_write*                   |
| [Approval](/spacelift/concepts/policy/approval-policy.md)                 | Who can approve or reject a run and how a run can be approved                                                             | Positive and negative | `boolean`     | *approve, reject*                                  |
| [Initialization](/spacelift/concepts/policy/run-initialization-policy.md) | Blocks suspicious [runs](/spacelift/concepts/run.md) before they [start](/spacelift/concepts/run.md#initializing)         | Negative              | `set<string>` | *deny*                                             |
| [Plan](/spacelift/concepts/policy/terraform-plan-policy.md)               | Gives feedback on [runs](/spacelift/concepts/run.md) after [planning](/spacelift/concepts/run/proposed.md#planning) phase | Negative              | `set<string>` | *deny*, *warn*                                     |
| [Push](/spacelift/concepts/policy/git-push-policy.md)                     | Determines how a Git push event is interpreted                                                                            | Positive and negative | `boolean`     | *track, propose, ignore, ignore\_track, notrigger* |
| [Task](/spacelift/concepts/policy/task-run-policy.md)                     | Blocks suspicious [tasks](/spacelift/concepts/run/task.md) from running                                                   | Negative              | `set<string>` | *deny*                                             |
| [Trigger](/spacelift/concepts/policy/trigger-policy.md)                   | Selects [stacks](/spacelift/concepts/stack.md) for which to trigger a [tracked run](/spacelift/concepts/run/tracked.md)   | Positive              | `set<string>` | *trigger*                                          |

## How it works

Spacelift uses an open-source project called [**Open Policy Agent**](https://www.openpolicyagent.org/) and its rule language, [**Rego**](https://www.openpolicyagent.org/docs/latest/policy-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.

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.

Except for [login policies](/spacelift/concepts/policy/login-policy.md) that are global, all other policy types operate on the [stack](/spacelift/concepts/stack.md) level, and they can be attached to multiple stacks, just as [contexts](/spacelift/concepts/configuration/context.md) are, which both facilitates code reuse and allows flexibility. Policies only affect stacks they're attached to. Please refer to the [relevant section of this article](/spacelift/concepts/policy.md#attaching-policies) for more information about attaching policies.

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.

### Policy language

[Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) - 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 [`jq`](https://stedolan.github.io/jq/), 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 [the Rego playground](https://play.openpolicyagent.org/).

### Constraints

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`

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 [test your policies thoroughly](/spacelift/concepts/policy.md#testing-policies)! 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.

Policies must be self-contained and cannot refer to external resources (e.g., files in a VCS repository).

## Return Types

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**

[Login](/spacelift/concepts/policy/login-policy.md) and [access](/spacelift/concepts/policy/stack-access-policy.md) 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.

**Set of Strings**

The second group of policies ([initialization](/spacelift/concepts/policy/run-initialization-policy.md), [plan](/spacelift/concepts/policy/terraform-plan-policy.md), and [task](/spacelift/concepts/policy/task-run-policy.md)) is expected to generate a [**set of strings**](https://www.openpolicyagent.org/docs/latest/policy-language/#generating-sets) 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.

Here's a practical difference between the two types:

{% tabs %}
{% tab title="boolean.rego" %}

```perl
package spacelift

# This is a simple deny rule.
# When it matches, no feedback is provided.
deny {
  true
}
```

{% endtab %}

{% tab title="string.rego" %}

```perl
package spacelift

# This is a deny rule with string value.
# When it matches, that value is reported to the user.
deny["the user will see this"] {
  true
}
```

{% endtab %}
{% endtabs %}

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:

```perl
package spacelift

we_dont_create := { "scary", "resource", "types" }

# This is an example of a plan policy.
deny[sprintf("some rule violated (%s)", [resource.address])] {
  some resource
  created_resources[resource]
  
  we_dont_create[resource.type]
}
```

## Helper Functions

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)`      | Executes the command `x`. `result` is an object containing `status`, `stdout` and `stderr`. Only applicable for run initialization policies for [private workers](/spacelift/concepts/worker-pools.md). |

## Creating policies

There are two ways of creating policies - through the web UI and through the [Terraform provider](/spacelift/vendors/terraform/terraform-provider.md). We generally suggest the latter as it's much easier to manage down the line and [allows proper unit testing](/spacelift/concepts/policy.md#testing-policies). 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):

```perl
resource "spacelift_stack" "example-stack" {
  name       = "Example stack"
  repository = "example-stack"
  branch     = "master"
}

# This example assumes that you have Rego policies in a separate
# folder called "policies".
resource "spacelift_policy" "example-policy" {
  name = "Example policy"
  body = file("${path.module}/policies/example-policy.rego")
  type = "TERRAFORM_PLAN"
}

resource "spacelift_policy_attachment" "example-attachment" {
  stack_id  = spacelift_stack.example-stack.id
  policy_id = spacelift_policy.example-policy.id
}
```

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:

![Click the Add Policy button at the top right of the view.](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FiEqkhHAVrnOP4QGMuINd%2FScreen%20Shot%202022-06-28%20at%2010.25.21%20AM.png?alt=media\&token=1a6df500-a6d6-4ce2-a2a3-b74172eb2224)

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 [access policy](/spacelift/concepts/policy/stack-access-policy.md) that gives members of the *Engineering* GitHub team read access to a stack:

![Define your policy, optionally add labels, and click Create policy.](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FotssiIbMKXBwrEWf0nJi%2FScreen%20Shot%202022-06-28%20at%2010.30.12%20AM.png?alt=media\&token=18ef8f8b-6e91-41b2-a2f1-6f05551f2279)

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.

## Attaching policies

### Automatically

Policies, with the exception of [Login policies](/spacelift/concepts/policy/login-policy.md), 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.

**Policy Attachment Example**

In the example below, the policy will be automatically attached to all stacks/modules with the label `production`.

![Require at least one approval on all tracked runs for Stacks/Modules that have the label "needs\_approval".](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FGLQMBfzBAFCNVCybx2RU%2FScreen%20Shot%202022-06-29%20at%2012.07.11%20PM.png?alt=media\&token=2158a85d-e9bb-46b9-94fb-ff22578fa518)

**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.

### Manually

In the web UI attaching policies is done in the stack management view, in the Policies tab:

![Select a Policy and manually attach it to the Stack.](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FF6JBWkKBILr50LqomMrl%2FScreen%20Shot%202022-06-29%20at%2012.10.26%20PM.png?alt=media\&token=1a08845d-f081-4d8e-84b9-9c3ad645f596)

## Policy workbench

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.

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 [sample policy inputs](/spacelift/concepts/policy.md#sampling-policy-inputs).

### Sampling policy inputs

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:

```bash
sample { true }
```

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 [plan](/spacelift/concepts/policy/terraform-plan-policy.md) or [task](/spacelift/concepts/policy/task-run-policy.md) policy):

```bash
sample { count(deny) == 0 }
```

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:

```go
sample {
  millis := round(input.request.timestamp_ns / 1e6)
  millis % 100 <= 10
}
```

### Why sample?

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.

### Policy workbench in practice

In order to show you how to work with the policy workbench, we are going to use a [task policy](/spacelift/concepts/policy/task-run-policy.md) 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:

{% hint style="info" %}
This example comes from our [test repo](https://github.com/spacelift-io/terraform-starter), 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.
{% endhint %}

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FH6O3XMWaWnvH7Mz7e4Zt%2FScreen%20Shot%202022-06-29%20at%2012.17.46%20PM.png?alt=media\&token=c6e555a2-78bd-4b5b-bfb4-4b1764c3b4e3)

In order to get to the policy workbench, first click on the Edit button in the upper right hand corner of the policy screen:

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FivQt3bDuVYfE7iXiM59I%2FScreen%20Shot%202022-06-29%20at%2012.17.46%20PM.png?alt=media\&token=4cfc062c-510c-4026-b11a-5ce071b82a26)

Then, click on the Show simulation panel link on the right hand side of the screen:

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2Fn2r9RcGIFUx6tHWIdzri%2FScreen%20Shot%202022-06-29%20at%2012.19.27%20PM.png?alt=media\&token=ba9b7f6e-97aa-4920-a964-e9158d73670c)

If your policy has been used evaluated and sampled, your screen should look something like this:

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2Fa3LB97vHZlvVjFFhypQe%2FScreen%20Shot%202022-06-29%20at%201.52.26%20PM.png?alt=media\&token=82cdaac3-634e-412a-8cf8-621d8515c2e2)

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:

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FgFqxG6HJriJ1iZuEudp8%2FScreen%20Shot%202022-06-29%20at%201.53.12%20PM.png?alt=media\&token=0a5cc54c-230f-43a6-8ab7-6b5532c1ae65)

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:

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FZS0hXwbNIgcrLnkdhsFn%2FScreen%20Shot%202022-06-29%20at%201.53.40%20PM.png?alt=media\&token=fcb62936-1658-4f93-aa9f-c458a3839d9c)

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:

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FkOIpYe30SK28JN5qdYds%2FScreen%20Shot%202022-06-29%20at%201.54.12%20PM.png?alt=media\&token=8c01a3a2-ee94-47f0-8df1-8e84ac853030)

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.

### Is it safe?

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 [plan policies](/spacelift/concepts/policy/terraform-plan-policy.md) the inputs hash all the string attributes of resources, ensuring that no sensitive data leaks through this means.

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FJioXLiBAxFmSKidmBmmZ%2FScreen%20Shot%202022-06-29%20at%201.54.48%20PM.png?alt=media\&token=db0ab975-b2af-4587-957a-4450199d68b3)

Last but not least, the policy workbench - including access to previous inputs - is only available to **Spacelift account administrators**.

## Testing policies

{% hint style="info" %}
In the examples for each type of policy we invite you to play around with the policy and its input [in the Rego playground](https://play.openpolicyagent.org/). While certainly useful, we won't consider it proper unit testing.
{% endhint %}

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.&#x20;

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 [their documentation](https://www.openpolicyagent.org/docs/latest/policy-testing/) so in this section we'll only look at things specific to Spacelift.

Let's define a simple [login policy](/spacelift/concepts/policy/login-policy.md) that denies access to [non-members](/spacelift/concepts/policy/login-policy.md#account-membership), and write a test for it:

{% code title="deny-non-members.rego" %}

```perl
package spacelift

deny { not input.session.member }
```

{% endcode %}

You'll see that we simply mock out the `input` received by the policy:

{% code title="deny-non-members\_test.rego" %}

```perl
package spacelift

test_non_member {
    deny with input as { "session": { "member": false } }
}

test_member_not_denied {
    not deny with input as { "session": { "member": true } }
}
```

{% endcode %}

We can then test it in the console using `opa test` command (note the glob, which captures both the source and its associated test):

```bash
❯ opa test deny-non-members*
PASS: 2/2
```

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 [run initialization policy](/spacelift/concepts/policy/run-initialization-policy.md) that denies commits to a particular branch (because why not):

{% code title="deny-sandbox.rego" %}

```perl
package spacelift

deny[sprintf("don't push to %s", [branch])] {
  branch := input.commit.branch
  branch == "sandbox"
}
```

{% endcode %}

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:

{% code title="deny-sandbox\_test.rego" %}

```perl
package spacelift

test_sandbox_denied {
  expected := { "don't push to sandbox" }

  deny == expected with input as { "commit": { "branch": "sandbox" } }
}

test_master_not_denied {
  expected := set()

  deny == expected with input as { "commit": { "branch": "master" } }
}
```

{% endcode %}

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):

```bash
❯ opa test deny-sandbox*
PASS: 2/2
```

{% hint style="success" %}
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.
{% endhint %}
