# Initialization policy

{% hint style="warning" %}
This feature is deprecated as we're planning to move the pre-flight checks to the worker node, thus allowing customers to block suspicious-looking jobs on their end.
{% endhint %}

## Purpose

Initialization policy can prevent a [Run](../run) or a [Task](../run/task) from being [initialized](../../run#initializing), thus blocking any custom code or commands from being executed. It superficially looks like a [plan policy](terraform-plan-policy) in that it affects an existing Run and prints feedback to logs, but it does not get access to the plan. Instead, it can be used to [protect your stack from unwanted changes](#protect-your-stack-from-unwanted-changes) or [enforce organizational rules](#enforce-organizational-rules) concerning how and when runs are supposed to be triggered.

{% hint style="warning" %}
Server-side initialization policies are being deprecated. We will be replacing them with [worker-side policies](../../worker-pools#configuration-options) that can be set by using the launcher run initialization policy flag (`SPACELIFT_LAUNCHER_RUN_INITIALIZATION_POLICY`).

For a limited time period we will be running both types of initialization policy checks but ultimately we're planning to move the pre-flight checks to the worker node, thus allowing customers to block suspicious looking jobs on their end.
{% endhint %}

Let's create a simple initialization policy, attach it to the stack, and see what gives:

```perl
package spacelift

deny["you shall not pass"] {
  true
}
```

...and boom:

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LshwwDZmA4HXN0k9e8O%2F-Lxv0GwaHvbmUbmRrG2n%2F-LxvFaBb1T2IYw0cFb0y%2FInitial_commit_%C2%B7_Stack_managed_by_Spacelift.png?alt=media\&token=06420ec5-ceca-4571-8b61-665b5587a671)

## Rules

Initialization policies are simple in that they only use a single rule - **deny** - with a string message. A single result for that rule will fail the run before it has a chance to start - as we've just witnessed above.

## Data input

This is the schema of the data input that each policy request will receive:

```javascript
{
  "commit": {
    "author": "string - GitHub login if available, name otherwise",
    "branch": "string - branch to which the commit was pushed",
    "created_at": "number  - creation Unix timestamp in nanoseconds",
    "message": "string - commit message"
  },
  "request": {
    "timestamp_ns": "number - current Unix timestamp in nanoseconds"
  },
  "run": {
    "based_on_local_workspace": "boolean - whether the run stems from a local preview",
    "created_at": "number - creation Unix timestamp in nanoseconds",
    "runtime_config": {
      "before_init": ["string - command to run before run initialization"],
      "project_root": "string - root of the Terraform project",
      "runner_image": "string - Docker image used to execute the run",
      "terraform_version": "string - Terraform version used to for the run"
    },
    "triggered_by": "string or null - user or trigger policy who triggered the run, if applicable",
    "type": "string - PROPOSED or TRACKED",
    "updated_at": "number - last update Unix timestamp in nanoseconds"
    "user_provided_metadata": ["string - blobs of metadata provided using spacectl or the API when interacting with this run"]
  },
  "stack": {
    "administrative": "boolean - is the stack administrative",
    "autodeploy": "boolean - is the stack currently set to autodeploy",
    "branch": "string - tracked branch of the stack",
    "labels": ["string - list of arbitrary, user-defined selectors"],
    "locked_by": "optional string - if the stack is locked, this is the name of the user who did it",
    "name": "string - name of the stack",
    "namespace": "string - repository namespace, only relevant to GitLab repositories",
    "project_root": "optional string - project root as set on the Stack, if any",
    "repository": "string - name of the source GitHub repository",
    "state": "string - current state of the stack",
    "terraform_version": "string or null - last Terraform version used to apply changes"
  }
}
```

### Helpers

In addition to our [global helper functions](..#helper-functions), we also provide the following helpers for initialization policies:

* `commit` - an alias for `input.commit`.
* `run` - an alias for `input.run`.
* `runtime_config` - an alias for `input.run.runtime_config`.
* `stack` - an alias for `input.stack`.

## Use cases

There are two main use cases for run initialization policies - [protecting your stack from unwanted changes](#protect-your-stack-from-unwanted-changes) and [enforcing organizational rules](#enforce-organizational-rules). Let's look at these one by one.

### Protect your stack from unwanted changes

While specialized, Spacelift is still a CI/CD platform and thus allows running custom code before Terraform initialization phase using [`before_init` ](../../configuration/runtime-configuration#before_init-scripts)scripts. This is a very powerful feature, but as always, with great power comes great responsibility. Since those scripts get full access to your Terraform environment, how hard is it to create a commit on a feature branch that would run [`terraform destroy -auto-approve`](https://www.terraform.io/docs/commands/destroy.html)? Sure, all Spacelift runs are tracked and this prank will sooner or later be tracked down to the individual who ran it, but at that point do you still have a business?

That's where initialization policies can help. Let's explicitly blacklist all Terraform commands if they're running as [`before_init`](../../configuration/runtime-configuration#before_init-scripts) scripts. OK, let's maybe add a single exception for a formatting check.

```perl
package spacelift

deny[sprintf("don't use Terraform please (%s)", [command])] {
  command := input.run.runtime_config.before_init[_]
  
  contains(command, "terraform")
  command != "terraform fmt -check"
}
```

Feel free to play with this example in [the Rego playground](https://play.openpolicyagent.org/p/V0sr5abgWI).

OK, but what if someone gets clever and creates a [Docker image](../../integrations/docker) that symlinks something very innocent-looking to `terraform`? Well, you have two choices - you could replace a blacklist with a whitelist, but a clever attacker can be really clever. So the other choice is to make sure that a known good Docker is used to execute the run. Here's an example:

```perl
package spacelift

deny[sprintf("unexpected runner image (%s)", [image])] {
  image := input.run.runtime_config.runner_image
  
  image != "spacelift/runner:latest"
}
```

Here's the above example in [the Rego playground](https://play.openpolicyagent.org/p/VxIREPOS0d).

{% hint style="danger" %}
Obviously, if you're using an image other than what we control, you still have to ensure that the attacker can't push bad code to your Docker repo. Alas, this is beyond our control.
{% endhint %}

### Enforce organizational rules

While the previous section was all about making sure that bad stuff does not get executed, this use case presents run initialization policies as a way to ensure best practices - ensuring that the right things get executed the right way and at the right time.

One of the above examples explicitly whitelisted Terraform formatting check. Keeping your code formatted in a standard way is generally a good idea, so let's make sure that this command always gets executed first. Note that as per [Anna Karenina principle](https://en.wikipedia.org/wiki/Anna_Karenina_principle) this check is most elegantly defined as a _negation_ of another rule matching the required state of affairs:

```perl
package spacelift

deny["please always run formatting check first"] {
  not formatting_first
}

formatting_first {
  input.run.runtime_config.before_init[i] == "terraform fmt -check"
  i == 0
}
```

Here's this example [in the Rego playground](https://play.openpolicyagent.org/p/ghtWZGhbgP).

This time we'll skip the mandatory "don't deploy on weekends" check because while it could also be implemented here, there are probably better places to do it. Instead, let's enforce a feature branch naming convention. We'll keep this example simple, requiring that feature branches start with either `feature/` or `fix/`, but you can go fancy and require references to Jira tickets or even look at commit messages:

```perl
package spacelift

deny[sprintf("invalid feature branch name (%s)", [branch])] {
	branch := input.commit.branch

  input.run.type == "PROPOSED"
  not re_match("^(fix|feature)\/.*", branch)
}
```

Here's this example [in the Rego playground](https://play.openpolicyagent.org/p/qNMygC4i9K).
