# IaC Governance Setup

> Connect GitHub so ZopNight can check every Terraform or OpenTofu pull request against your policies. Lists the exact GitHub access to grant, what your CI sends to ZopNight, and what your CI must already have.

Source: https://zop.dev/developer-docs/integrations/iac-governance

---

**IaC Governance** checks each Terraform or OpenTofu pull request against the policies you attach
in ZopNight. Your own CI runs `plan` and sends the plan to ZopNight. ZopNight replies with a pass or
fail and a comment on the pull request. You can make that result a required check, so a failing
change can't be merged.

**Warning**

ZopNight has no access to your state bucket or your cloud account through this feature. Your CI
runs Terraform with its own credentials and sends ZopNight two kinds of JSON:

- **On every pull request:** the plan, trimmed to `planned_values` and `resource_changes`.
- **On every push to the default branch, and on a manual run:** the full output of
  `terraform show -json`, which is your **whole current state**.

Terraform state can hold secrets, and `show -json`
[prints sensitive state values in plain text](https://developer.hashicorp.com/terraform/cli/commands/show)
([OpenTofu too](https://opentofu.org/docs/cli/commands/show/)). Review this with your security
team before you merge the setup pull request.

## Before you start

| Need | Detail |
|---|---|
| A GitHub account on **github.com** | GitHub Enterprise Server and GitLab are not supported. |
| Someone who can install GitHub Apps | An organisation owner, or a repo admin if your org allows it. |
| A ZopNight **Admin** | Only Admins can connect GitHub. Editors and Admins attach policies. |
| Terraform or OpenTofu at the **repo root** | The workflow runs `init` and `plan` from the root folder. |
| Cloud and state credentials in your CI | Your runners must already be able to run `terraform plan`. |
| Outbound HTTPS from your runners | To the ZopNight API (`ZOPNIGHT_API_URL`) and to GitHub for public actions. |

## Access to grant

Connect with the **ZopNight GitHub App**. It asks for these repository permissions, and only on the
repos you pick during install. Each one is the level GitHub lists for the API call ZopNight makes,
in [Permissions required for GitHub Apps](https://docs.github.com/en/rest/authentication/permissions-required-for-github-apps).

| GitHub App permission | Access | Why ZopNight needs it |
|---|---|---|
| Metadata | Read | Required for every app. Reads the default branch. |
| Contents | Read and write | Finds the workflow file; creates the branch and the file. |
| Workflows | Read and write | Needed to add a file under `.github/workflows/`. |
| Secrets | Read and write | Stores the scan token as the repo secret `ZOPNIGHT_TOKEN`. |
| Variables | Read and write | Sets the repo variables `ZOPNIGHT_API_URL` and `ZOPNIGHT_ORG`. |
| Pull requests | Read and write | Opens the setup pull request and checks its status. |

GitHub needs **Contents** and **Workflows** together to write a workflow file. See
[Create or update file contents](https://docs.github.com/en/rest/repos/contents#create-or-update-file-contents).
The app subscribes to no webhook events and asks for no organisation or account permissions.

**Warning**

The GitHub card also accepts a personal access token. That option does not work with IaC
Governance today. Install the GitHub App instead.

### What the workflow in your repo does

The setup pull request adds `.github/workflows/zopnight-governance.yml`. It runs on pull requests,
on pushes, and when started by hand. It has two jobs:

| Job | Runs on | What it sends |
|---|---|---|
| `validate` | Every pull request | The trimmed plan, to get a pass or fail and post a comment |
| `scan` | Pushes to the default branch, and manual runs | The full state, to match your code to discovered resources |

Its job token asks for only:

```yaml
permissions:
  contents: read        # check out the code
  pull-requests: write  # post and update the verdict comment
```

Any permission not listed is
[set to `none`](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#permissions).
It reads one secret and up to three variables:

| Name | Type | Value |
|---|---|---|
| `ZOPNIGHT_TOKEN` | Secret | A scan token that can only run validations and read policies. |
| `ZOPNIGHT_API_URL` | Variable | The ZopNight API address. |
| `ZOPNIGHT_ORG` | Variable | Your ZopNight organisation ID. |
| `ZOPNIGHT_IAC_TOOL` | Variable, optional | Set it to `tofu` to use OpenTofu. ZopNight does not set it. |

## Set it up

**Install the GitHub App**

In ZopNight, open **Settings → Integrations → GitHub** and click **Connect**. Choose **Install
GitHub App**. On GitHub, pick the organisation and the repos to allow, then approve. You return to
ZopNight and see **GitHub connected**.

**Attach a policy**

Open **Governance → IaC Validations** and click **Attach policy**. Pick a rule and set it up. Set
**Enforcement** to **Mandatory** if a violation must block the merge; **Advisory** only warns. In
**Scope**, choose the connected account and the repos. Click **Create policy**.

**Merge the setup pull request**

ZopNight opens a setup pull request from the branch `zopnight/governance` in each repo. The toast
has an **Open PR** button. Review it and merge it into your default branch.

**Make the check required (recommended)**

In the repo on GitHub, open **Settings → Rulesets** (or **Settings → Branches → Add classic branch
protection rule**). Turn on **Require status checks to pass** and add **validate**. It shows as
*ZopNight IaC Governance / validate*. The check fails only on a **Mandatory** policy violation.
GitHub only offers a check that has
[completed successfully in the repo in the past seven days](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks).
See [Creating rulesets for a repository](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository).

Each repo card on the **Repositories** tab of **IaC Validations** shows where setup stands:

| Badge | Meaning |
|---|---|
| **Merge setup PR** | The setup pull request is open. Merge it. |
| **CI active** | The workflow is on the default branch and its token is valid. |
| **CI token missing** | The token was revoked. Attach a policy again to issue a new one. |
| **CI not wired** | Not set up yet, or GitHub refused access. Check the app covers this repo. |

**Note**

ZopNight opens the setup pull requests only when your organisation has exactly one git connection.
With none, or more than one, you see **Automatic CI wiring skipped**.

## Disconnect and remove access

**Settings → Integrations → GitHub → Disconnect** removes the connection from ZopNight and asks
GitHub to uninstall the app. That uninstall is best effort: if GitHub refuses it, the connection is
still removed. Check the app is gone in GitHub: for an organisation, **Settings → Third-party
Access → GitHub Apps**; for a personal account, **Settings → Applications → Installed GitHub Apps**.

Disconnecting does **not** change your repos. To remove IaC Governance fully, also delete these
from each repo:

- the file `.github/workflows/zopnight-governance.yml`
- the secret `ZOPNIGHT_TOKEN`
- the variables `ZOPNIGHT_API_URL` and `ZOPNIGHT_ORG`
- the branch `zopnight/governance`, if it is still there
- the required **validate** check, if you added one

## Troubleshooting

| Problem | Cause and fix |
|---|---|
| Pull requests from forks fail the check | [Forks get no secrets](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request). Use branches in the same repo. |
| `terraform init` fails in the workflow | The runner has no cloud or state credentials. Add them as for your other Terraform workflows. |
| The check never reaches ZopNight | Runners can't reach `ZOPNIGHT_API_URL`. Allow outbound HTTPS to it. |
| A repo is missing from the Scope list | The app is not installed on that repo. Add it on the app's **Configure** page in GitHub. |
