> ## Documentation Index
> Fetch the complete documentation index at: https://docs.plerion.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Manage custom checks with Terraform

> Create, change, and preview Plerion custom checks from Terraform with the Plerion provider

With the **[Plerion Terraform provider](https://registry.terraform.io/providers/plerionhq/plerion/latest/docs)**, you can manage your custom checks as code. Policy changes go through your pull requests, `terraform plan` shows exactly what will change, and a dry run can stop a broken or noisy policy before it reaches your tenant. The provider works with Terraform 1.5 or later.

***

## Before you start

You need a tenant API key. See [API keys](https://docs.plerion.com/guides/administration/api-keys.md) to create one. The key's role must include these permissions:

| Permission | Needed for |
| - | - |
| `CustomCheck:Read` | Planning, refreshing state, and looking checks up |
| `CustomCheck:Modify` | Creating, changing, and deleting checks, and running dry runs |
| `Asset:Read` | Dry runs, on the integration the dry run uses |

You also need the API address of your region. API keys only work in the region where they were created.

| Region | API address |
| - | - |
| Australia | `https://au.api.plerion.com` |
| India | `https://in1.api.plerion.com` |
| Singapore | `https://sg1.api.plerion.com` |
| United States | `https://us1.api.plerion.com` |

***

## Steps to create a custom check

**1. Configure the provider**

Add the provider to your configuration and set the API address for your region.

```hcl theme={"system"}
terraform {
  required_providers {
    plerion = {
      source  = "plerionhq/plerion"
      version = "~> 0.1"
    }
  }
}

provider "plerion" {
  api_url = "https://us1.api.plerion.com"
}
```

Set the API key with the `PLERION_API_KEY` environment variable rather than writing it in a file.

**2. Write the policy**

Save the Rego policy in its own file, for example `policies/github-oidc-roles.rego`. This policy reports every IAM role that GitHub Actions can assume from any repository.

```rego theme={"system"}
package custom

default result := []

result := [{"id": role.id, "status": "FAILED"} |
  role := input.assets[_]
  statement := role.data.AssumeRolePolicyDocument.Statement[_]
  statement.Effect == "Allow"
  contains(statement.Principal.Federated, "token.actions.githubusercontent.com")
  not statement.Condition.StringLike["token.actions.githubusercontent.com:sub"]
]
```

See [What a policy receives and returns](#what-a-policy-receives-and-returns) for the full contract.

**3. Add the check**

```hcl theme={"system"}
resource "plerion_custom_check" "github_oidc_roles" {
  slug  = "github-oidc-roles-must-pin-a-repository"
  title = "GitHub OIDC roles must pin a repository"

  target = {
    asset_type = "AWS::IAM::Role"
    scope      = "batch"
  }

  defaults = {
    severity_level = "HIGH"
    message        = "The role trusts GitHub OIDC without limiting which repository can assume it."
    remediation    = "Add a StringLike condition on token.actions.githubusercontent.com:sub that names your repository."
  }

  body = file("${path.module}/policies/github-oidc-roles.rego")
}
```

* `slug` must be unique in the tenant. You can rename it later without replacing the check.
* `scope = "batch"` evaluates the assets in chunks, which suits checks that look at one asset at a time. `scope = "all"` evaluates every asset of the type together, for checks that compare assets with each other.
* `defaults` applies to every finding unless the policy's result sets a value itself.

**4. Apply the change**

* Run `terraform init` to download the provider.
* Run `terraform plan` to review the change.
* Run `terraform apply` to create the check.

Plerion runs the check straight away against your integrations from the matching cloud provider, then again after each scan. Its findings appear alongside your other findings.

***

## Preview findings before you apply

The `plerion_custom_check_dry_run` data source runs a policy against one integration and returns the findings it would produce, without saving them. Terraform reads it during the plan, and the read fails if the policy cannot be evaluated, so a broken policy stops the plan.

To check a change before it reaches your tenant, keep the check's settings in `locals` and give the same values to the check and to the dry run. A `precondition` on the check then stops the apply when the preview isn't what you expect.

```hcl theme={"system"}
locals {
  github_oidc_roles = {
    target   = { asset_type = "AWS::IAM::Role", scope = "batch" }
    defaults = { severity_level = "HIGH", message = "The role trusts GitHub OIDC without limiting which repository can assume it." }
    body     = file("${path.module}/policies/github-oidc-roles.rego")
  }
}

data "plerion_custom_check_dry_run" "github_oidc_roles" {
  integration_id = var.production_integration_id
  target         = local.github_oidc_roles.target
  defaults       = local.github_oidc_roles.defaults
  body           = local.github_oidc_roles.body
}

resource "plerion_custom_check" "github_oidc_roles" {
  slug     = "github-oidc-roles-must-pin-a-repository"
  title    = "GitHub OIDC roles must pin a repository"
  target   = local.github_oidc_roles.target
  defaults = local.github_oidc_roles.defaults
  body     = local.github_oidc_roles.body

  lifecycle {
    precondition {
      condition     = data.plerion_custom_check_dry_run.github_oidc_roles.total_findings <= 5
      error_message = "The policy would flag more than 5 roles. Review it before applying."
    }
  }
}
```

> [!WARNING]
>
> Don't point the dry run at the check's own attributes, such as `plerion_custom_check.github_oidc_roles.body`. When the check has changes, Terraform waits until the apply to read the dry run, which is after the check has already changed.

A new dry run starts every time Terraform reads the data source, which is usually on every plan. It evaluates the assets Plerion collected in its most recent scan of the integration.

***

## Bring existing checks under Terraform

Checks created in the Plerion app or through the API can be imported by their slug or their ID.

**1. Add an import block**

```hcl theme={"system"}
import {
  to = plerion_custom_check.github_oidc_roles
  id = "github-oidc-roles-must-pin-a-repository"
}
```

**2. Generate the configuration**

Run `terraform plan -generate-config-out=generated.tf`. Terraform reads the check and writes a matching `plerion_custom_check` block to `generated.tf`. Terraform marks this option as experimental, so review the file before you keep it.

**3. Apply the import**

Run `terraform apply`. The plan should show one import and no changes. Move the policy into its own file if you like, then remove the import block.

***

## What a policy receives and returns

A policy must declare `package custom` and define a rule named `result`. Plerion evaluates the rule and reads its value as a list of verdicts.

The policy receives:

| Field | Contents |
| - | - |
| `input.assets` | The assets of the check's target asset type. Each asset has an `id`, and the configuration Plerion collected from the cloud provider is under `data`. |
| `input.additionalAssets` | Every asset of each type listed in `additional_assets`, keyed by asset type, for example `input.additionalAssets["AWS::IAM::Role"]`. |
| `input.context` | The `tenantId`, `organizationId`, and `integrationId` the check is running for. |

Each verdict in `result` is an object:

| Field | Required | Description |
| - | - | - |
| `id` | Yes | The `id` of an asset in `input.assets` |
| `status` | Yes | `FAILED` or `PASSED` |
| `severityLevel` | No | Replaces `defaults.severity_level` for this finding |
| `message` | No | Replaces `defaults.message` for this finding |
| `informational` | No | Replaces `defaults.informational` for this finding |
| `remediation` | No | Replaces `defaults.remediation` for this finding. Used on failed findings only. |
| `meta` | No | An object merged over `defaults.meta` for this finding |

Every verdict becomes a finding, so an asset left out of `result` has no finding. If any verdict has another `status`, or names an asset that isn't in `input.assets`, Plerion rejects the whole evaluation.

The [provider documentation](https://registry.terraform.io/providers/plerionhq/plerion/latest/docs) lists every attribute of the resource and data sources. For one-off tasks from a terminal, see the [`plerion custom-checks`](https://docs.plerion.com/cli-reference/commands/custom-checks) CLI commands.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.