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

# Terraform Verification for AWS

> Enable read-only Terraform plan verification for AWS cloud misconfiguration fixes

## Overview

Backline can propose Terraform fixes for cloud misconfigurations and verify supported AWS changes with `terraform plan` before opening a pull request.

Verification is optional. The two Terraform reader roles are disabled by default in the AWS Cloud integration template. If you do not enable them, Backline can still generate a fix, but it opens a draft pull request pending human approval.

<Note>
  The existing `BacklineCloudIntegrationRole` remains part of the AWS Cloud integration. The two optional roles described here add narrowly scoped Terraform state and plan access.
</Note>

## Terraform Deployments

Backline treats each Terraform state used by a root module as a separate **deployment**. A deployment includes:

* The repository and root module
* One Terraform state object
* The AWS account that owns the state
* The AWS account managed by that state
* Its execution settings, such as variable files and Terraform version

A root can have several deployments, such as separate production and staging states. Backline verifies each deployment that is active when the remediation starts. A deployment discovered later is checked during a later remediation run.

## Optional AWS Roles

The AWS Cloud integration template can create two additional roles:

| Role                           | Install it in                                   | Purpose                                                                                              |
| ------------------------------ | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `BacklineTerraformStateReader` | The account that owns the S3 state bucket       | Reads only the configured state objects and uses the configured KMS keys to decrypt them when needed |
| `BacklineTerraformPlanReader`  | The account managed by the Terraform deployment | Refreshes supported AWS resources using a fixed read-only permission set                             |

Both roles:

* Default to disabled
* Trust only Backline's cloud integration service, using your tenant-specific external ID
* Have fixed names so a request cannot choose another role
* Include explicit permission ceilings that prevent other policies from expanding their access

### StateReader Permissions

StateReader is limited to:

* `s3:GetObject` and `s3:GetObjectVersion` for the configured state objects
* `s3:ListBucket` for the configured state key prefixes
* `kms:Decrypt` for the configured KMS keys, when state uses SSE-KMS

List individual state objects in `StateObjectArns` rather than a bucket-wide pattern. The bucket ARN is still required for `s3:ListBucket`, but the list is restricted to the prefixes you supply.

### PlanReader Permissions

PlanReader grants an exact list of read actions for the currently supported AWS resources: DynamoDB tables, security groups, KMS keys, RDS instances, S3 buckets, SNS topics, and SQS queues. Every action is fully qualified; wildcard actions are not allowed, and a check in Backline's own pipeline rejects them.

Backline does not use the normal cloud discovery credential for Terraform plans and never falls back to it when either reader role is unavailable.

## Supported Scope

Plan verification currently supports:

* Terraform state stored in S3
* The AWS provider, plus the local-only `random`, `null`, `time`, and `local` providers
* One managed AWS account per deployment
* The default Terraform workspace
* Registered variable files and an available Terraform version

Roots that use other providers, executable external data sources, named workspaces, or multiple managed AWS accounts can still receive a draft pull request pending human approval.

## Enable Terraform Verification

Update the existing `BacklineCloudIntegration` CloudFormation stack in each relevant account using the current template:

```text theme={null}
https://backline-integration-templates.s3.amazonaws.com/backline-aws-cloud-integration-role.yaml
```

### Template Parameters

| Parameter                    | When to set it                                                   | Value                                                             |
| ---------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------- |
| `EnableTerraformStateReader` | The account owns one or more Terraform state objects             | `true`                                                            |
| `StateObjectArns`            | StateReader is enabled                                           | Comma-separated S3 object ARNs or narrowly scoped object patterns |
| `StateBucketArns`            | StateReader is enabled                                           | Comma-separated S3 bucket ARNs                                    |
| `StateKeyPrefixes`           | StateReader is enabled                                           | Comma-separated prefixes Terraform may list                       |
| `StateKmsKeyArns`            | Any listed state object uses SSE-KMS                             | Comma-separated KMS key ARNs                                      |
| `EnableTerraformPlanReader`  | The account contains resources managed by a supported deployment | `true`                                                            |

The same stack can enable both roles when the state and managed resources are in the same account.

### Multiple State Files in One Account

Enable StateReader once and supply all permitted values as comma-separated lists. For example:

```json theme={null}
[
  {
    "ParameterKey": "ExternalId",
    "UsePreviousValue": true
  },
  {
    "ParameterKey": "EnableTerraformStateReader",
    "ParameterValue": "true"
  },
  {
    "ParameterKey": "StateObjectArns",
    "ParameterValue": "arn:aws:s3:::company-tfstate/prod/terraform.tfstate,arn:aws:s3:::company-tfstate/staging/terraform.tfstate"
  },
  {
    "ParameterKey": "StateBucketArns",
    "ParameterValue": "arn:aws:s3:::company-tfstate"
  },
  {
    "ParameterKey": "StateKeyPrefixes",
    "ParameterValue": "prod/*,staging/*,env:/*"
  },
  {
    "ParameterKey": "EnableTerraformPlanReader",
    "ParameterValue": "true"
  }
]
```

Save the file as `backline-terraform-reader-parameters.json`, then update the stack:

```bash theme={null}
aws cloudformation update-stack \
  --stack-name BacklineCloudIntegration \
  --template-url https://backline-integration-templates.s3.amazonaws.com/backline-aws-cloud-integration-role.yaml \
  --capabilities CAPABILITY_NAMED_IAM \
  --parameters file://backline-terraform-reader-parameters.json \
  --profile <aws-profile>
```

If the state uses SSE-KMS, add `StateKmsKeyArns` with every key that encrypts the listed objects.

### State and Resources in Different Accounts

Update the stack separately in each account:

1. Make sure both accounts are connected to Backline through the AWS Cloud integration.
2. In the **state account**, enable StateReader and provide the state object, bucket, prefix, and optional KMS parameters.
3. In the **managed account**, enable PlanReader.
4. Use the tenant-specific external ID configured for each account's AWS Cloud integration.

Run the update command with the AWS CLI profile for each account. If several supported deployments manage different accounts, enable PlanReader once in each managed account.

<Warning>
  A single deployment that uses provider aliases to manage more than one AWS account is not currently plan-verified. Installing PlanReader in every account does not change this. Backline still proposes the change in a draft pull request pending human approval.
</Warning>

## How Verification Works

1. Backline generates the Terraform patch in an AI job that has no cloud credentials.
2. Backline takes a fixed snapshot of the active deployments for the root.
3. For each supported deployment, the trusted Runner identifies it by deployment ID. It does not supply bucket, state key, account, region, or role coordinates.
4. Backline resolves the bucket, state key, region, accounts, and roles from server-side deployment data.
5. Backline creates short-lived StateReader and PlanReader sessions for that verifier attempt.
6. A separate verifier job, with no AI access, runs the unmodified and modified plans and compares them locally.
7. Backline combines the results from every deployment before deciding whether the pull request is ready.

```mermaid theme={null}
flowchart TD
    P["Patch job<br/>no AWS access"] -->|Proposed Terraform change| R["Runner"]
    R -->|Deployment ID<br/>no infrastructure coordinates| B["Backline<br/>resolves bucket, key, region, roles"]
    B -->|Assumes StateReader and PlanReader| C["Short-lived read-only credentials"]
    C --> V["Verifier<br/>no AI access"]
    V -->|Original and changed plans| PL["terraform plan"]
    PL --> RES["Verification result only"]
```

Raw Terraform plan data, state values, provider output, and cloud credentials are not stored in workflow history.

### SaaS and On-Premises Runners

In SaaS, Runner requests the short-lived sessions directly from Backline's credential service. For an on-premises Runner in your VPC, Runner requests them through the authenticated Queue Adapter, which takes the tenant identity from authentication rather than from the request body.

In both modes, Backline resolves the deployment settings server-side and provides the credentials only to the verifier job.

<Warning>
  Backline's SaaS service still checks the registered state object before it creates credentials, including when Runner and the verifier run on-premises. State buckets that are reachable only through a customer VPC endpoint are not currently supported.
</Warning>

## Pull Request Outcomes

| Verification result                                                 | Outcome                                                     |
| ------------------------------------------------------------------- | ----------------------------------------------------------- |
| Every deployment produces a safe, contract-compliant plan           | The pull request can be marked ready                        |
| Some or all deployments cannot be planned                           | Backline opens a draft pull request pending human approval  |
| A plan shows unrelated or unsafe changes                            | Backline rejects the patch and does not open a pull request |
| The patch breaks a root that planned successfully before the change | Backline rejects the patch                                  |

Common reasons for a draft pull request pending human approval include:

* StateReader or PlanReader is not installed
* Terraform state is unavailable or changed during verification
* The root uses a named Terraform workspace
* A deployment spans multiple AWS accounts
* Required variable files or Terraform versions are unavailable
* The root uses an unsupported provider or an executable external data source
* The plan times out or cannot refresh a resource with the allowed read permissions

## Troubleshooting

### The Pull Request Is a Draft Pending Human Approval

Review the plan status in the pull request. It identifies whether a reader role, state registration, variable file, workspace, provider, or permission prevented verification.

### StateReader Cannot Read the State

Verify that:

* The state object ARN is exact or narrowly matched by `StateObjectArns`
* The bucket is included in `StateBucketArns`
* `StateKeyPrefixes` contains the state directory and `env:/*` when Terraform enumerates workspaces
* Every SSE-KMS key is included in `StateKmsKeyArns`
* The state account has an AWS Cloud integration using the same external ID

### PlanReader Cannot Refresh a Resource

The pull request reports that the plan was not verified rather than treating an access error as a patch failure. Contact Backline support with the missing read action so support can evaluate it for the measured PlanReader policy.
