# AWS onboarding with Terraform

> Configure an AWS management account for the CloudBolt Platform with Terraform instead of CloudFormation, and connect it

---

Site [llms.txt](/llms.txt)

---

<div class="alert alert-info" role="alert"><div class="h4 alert-heading" role="heading">Beta, enabled on request</div>


Terraform onboarding is off by default. CloudBolt recommends the CloudFormation StackSet deployment described in [Configure AWS accounts](/docs/cbp/getting-started/configure-cloud-accounts/aws/), because it also covers member accounts. To use Terraform, [contact CloudBolt Support](https://support.cloudbolt.io/cloudbolt/s/) to enable it for your organization; the **Connect Terraform Deployment** option then appears on the AWS connection form.
</div>


The CloudBolt Terraform module creates, in the management (payer) account, what the `ManagementAccountOnly` deployment of the CloudFormation template creates: an S3 bucket for the cost report exports, the CloudBolt OIDC identity provider, and the `CloudBoltFocusCrossAccountRole` and `CloudBoltActionsCrossAccountRole` IAM roles. CloudBolt then creates the FOCUS 1.0, FOCUS 1.2 Hourly, FOCUS 1.2 Daily, and Cost Optimization Recommendations data exports itself once the connection is established.

<div class="alert alert-warning" role="alert"><div class="h4 alert-heading" role="heading">Scope: management account only</div>


The module doesn't deploy to member accounts. Cost data for every account in the organization still flows through the FOCUS exports of the management account, but CloudBolt doesn't collect resource inventory, Security Hub findings, or reserved-instance and savings-plan details for member accounts via this path, and member accounts appear as not configured. To cover member accounts, use the StackSet deployment types of the CloudFormation template, described in [Advanced AWS account configuration](/docs/cbp/reference/aws-account-config/advanced-aws-config/).
</div>


## Prerequisites

Complete these in the **management/payer account** before applying. They match the CloudFormation path described in [Configure AWS accounts](/docs/cbp/getting-started/configure-cloud-accounts/aws/).

- **Cost Explorer** is enabled. Go to **Billing and Cost Management** > **Cost Explorer** and open it once to enable it.
- **AWS Compute Optimizer** is opted in for the account and all member accounts. Cost Optimization Hub sources its rightsizing recommendations from Compute Optimizer, so enable it first. Go to **Compute Optimizer** > **Get started**, or run:

  ```bash
  aws compute-optimizer update-enrollment-status --status Active --include-member-accounts
  ```

  CloudBolt doesn't verify this enrollment. A connection succeeds without it, but your rightsizing recommendations are incomplete.
- **Cost Optimization Hub** is opted in for the organization, including member accounts. Go to **Billing and Cost Management** > **Cost Optimization Hub**, choose **Enable Cost Optimization Hub for this account and all member accounts**, then select **Enable**. Alternatively, run:

  ```bash
  aws cost-optimization-hub update-enrollment-status --status Active --include-member-accounts --region us-east-1
  ```

- The `AWSServiceRoleForBCMDataExports` service-linked role exists. Go to **Billing and Cost Management** > **Data Exports** > **Create** to provision it automatically, or run:

  ```bash
  aws iam create-service-linked-role --aws-service-name bcm-data-exports.amazonaws.com
  ```

- Terraform 1.5 or later, the AWS provider 5.81.0 or later, and credentials that can manage IAM, S3, and STS in the management account. Configure the provider for **us-east-1**: AWS Data Exports operate only there.
- The account doesn't already carry the CloudBolt CloudFormation stack. Role names are fixed, so applying the module next to the stack fails with `EntityAlreadyExists`.
- A CloudBolt Platform user with the **Operations** or **Admin** role, in an organization where Terraform onboarding is enabled. Refer to [Manage roles](/docs/cbp/getting-started/manage-organizations-users-roles/roles/).

## Get the module

1. Log in to the CloudBolt Platform.
1. Go to **Settings** > **Connections**.
1. In the **AWS** section, select **Add Connection**, review the **ACTION REQUIRED** dialog, then select **Got it**.
1. Select **Connect Terraform Deployment**, then select **Download Terraform module**. Your browser saves `cloudbolt-aws-terraform.zip`.
1. Unzip it. The archive contains the module at the top level, a ready-to-apply root under `examples/management-only/`, a README, and the generated IAM policy documents under `policies/`.

Leave the CloudBolt tab open; you return to it after the apply.

## Apply the module

Either apply the example root as-is, or call the module from a Terraform root you already maintain. Either way, store the Terraform state in a remote backend; later updates and `terraform destroy` depend on it. All variables are listed under [Variables](#variables).

### Option A: apply the example root (recommended)

1. Change to `cloudbolt-aws-terraform/examples/management-only/`.
1. Copy `terraform.tfvars.example` to `terraform.tfvars` and set the following values.

   | Variable | Value |
   | --- | --- |
   | `focus_bucket_name` | Name of the S3 bucket for the cost report exports. With `create_bucket = true` (default) Terraform creates it; with `create_bucket = false` it must already exist. |
   | `tenant_hosted_region` | `US` (default) or `EU`, matching where your CloudBolt tenant is hosted. Selects the CloudBolt identity provider the roles trust. |
   | `resource_name_prefix` | Prefix on the managed-policy names and on the export names CloudBolt creates. Default `CloudBolt`. |
   | `deployment_name` | Suffix on those names. Takes the place of the CloudFormation stack name. Default `CloudBoltOnboarding`. |

1. Run the following commands:

   ```bash
   terraform init
   terraform plan
   terraform apply
   terraform output -json
   ```

### Option B: call the module from your own Terraform root

Place the unzipped module directory next to your existing configuration and add a module block. The module declares no provider, so it uses yours; keep that provider in `us-east-1`.

```hcl
module "cloudbolt_onboarding" {
  source               = "./cloudbolt-aws-terraform"
  focus_bucket_name    = "cloudbolt-cost-reports-123456789012"
  tenant_hosted_region = "US"
}

output "cloudbolt_onboarding" {
  value = module.cloudbolt_onboarding
}
```

## Connect in CloudBolt

1. Return to the AWS connection form (**Settings** > **Connections** > **Add Connection**) and enter a **Connection Name**.
1. Select **Connect Terraform Deployment**. The CloudFormation options are hidden, and a **Terraform output** box appears, with the `terraform output -json` command above it (select the copy icon to copy it).
1. Run `terraform output -json` in the directory you applied and paste everything it prints into **Terraform output**. CloudBolt fills in the **Account ID** and shows the other values it read: **FOCUS Bucket**, **Resource Prefix**, and **Deployment Name**. Select **Edit values** to correct one, or **Enter the values manually instead** if you don't have the output.
1. Select **Connect Terraform Deployment**.

The Resource Prefix and Deployment Name must match the applied values exactly. CloudBolt names the cost report exports `{Resource Prefix}-<export>-{Deployment Name}`. To switch back to the CloudFormation options, select **Use CloudFormation instead**.

## What happens next

As soon as the connection is created, CloudBolt finishes setup automatically:

1. Assumes the `CloudBoltFocusCrossAccountRole` the module created.
1. Verifies the account prerequisites.
1. Creates any of the cost report exports that don't already exist in the bucket.

The connection card shows progress, then displays **Connected**. If setup can't be completed, the card displays a specific error and a **Retry** action. FOCUS data appears once AWS delivers the first export, which can take up to 24 hours.

## Manage the connection

- **Test Connection:** Runs health checks and refreshes the status.
- **Re-Sync:** Re-verifies the cost report exports using the stored FOCUS Bucket, Resource Prefix, and Deployment Name, and creates any that are missing. Use after correcting a value, or if exports were deleted.
- **Manage:** Edit the connection **Name**, **FOCUS Bucket**, **Resource Prefix**, and **Deployment Name**. Correct a typo, save, then select **Re-Sync**.

The connection card shows **Configured via: Terraform**. Visit [Manage connections](/docs/cbp/how-to-guides/manage-connections/) to view, test, edit, or disconnect connections.

## Variables

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `focus_bucket_name` | string | required | Bucket for the cost report exports. 3–63 lowercase letters, digits, or hyphens. |
| `create_bucket` | bool | `true` | Create the bucket with versioning, public access block, and SSE-S3. `false` reuses an existing bucket named `focus_bucket_name`. |
| `create_bucket_policy` | bool | `true` | Attach the policy that lets `bcm-data-exports.amazonaws.com` write the exports. Set `false` only if the bucket already has an equivalent policy. |
| `tenant_hosted_region` | string | `"US"` | `US` or `EU` (`auth.cloudbolt.io` or `auth.eu.cloudbolt.io`). |
| `resource_name_prefix` | string | `"CloudBolt"` | Prefix on managed-policy names and on the export names CloudBolt creates. 1–32 characters. |
| `deployment_name` | string | `"CloudBoltOnboarding"` | Suffix on managed-policy names and export names. Stands in for the CloudFormation stack name. 1–64 characters. |
| `manage_oidc_providers` | bool | `true` | Create the CloudBolt OIDC providers. `false` looks up existing providers by URL instead (AWS allows one provider per URL per account). |
| `additional_trusted_account_arns` | list(string) | `[]` | Extra IAM root, role, or user ARNs allowed to assume both roles. |
| `additional_oidc_trusts` | list(object) | `[]` | Extra OIDC providers to create (or look up) and trust: `{ url, client_id }`. The URL must start with `https://` and end with `/`; the built-in CloudBolt URLs are rejected. |
| `tags` | map(string) | `{}` | Tags applied to every created resource. |

The example root under `examples/management-only/` adds one input, `aws_region` (default `us-east-1`), for its provider block.

## Outputs

`terraform output -json` prints these; CloudBolt reads the first four from the pasted document and ignores the rest.

| Name | Description |
| --- | --- |
| `account_id` | Management account ID. Fills **Account ID**. |
| `focus_bucket_name` | Bucket receiving the exports. Fills **FOCUS Bucket**. |
| `resource_name_prefix` | Prefix CloudBolt uses on export names. Fills **Resource Prefix**. |
| `deployment_name` | Suffix CloudBolt uses on export names. Fills **Deployment Name**. |
| `focus_role_arn` | `CloudBoltFocusCrossAccountRole` ARN. |
| `actions_role_arn` | `CloudBoltActionsCrossAccountRole` ARN. |
| `oidc_provider_arns` | Identity provider ARNs in use, keyed `us`, `eu`, and any extra URLs. |

## Created resources

| Resource | Purpose |
| --- | --- |
| S3 bucket (optional) | Stores the cost report exports. Versioning, public access block, SSE-S3. |
| Bucket policy (optional) | Lets AWS Data Exports (`bcm-data-exports.amazonaws.com`) write to the bucket, scoped to this account. |
| OIDC identity provider(s) | Trust for the CloudBolt SaaS tenant (US, plus EU when selected). |
| `CloudBoltFocusCrossAccountRole` | Billing data access and export management. Attaches the FOCUS policy and AWS `ReadOnlyAccess`. |
| `CloudBoltActionsCrossAccountRole` | Inventory, Security Hub, and commitment reads in the management account. Attaches two action policies. |

Managed-policy names follow `{resource_name_prefix}-FocusCommonPolicy-{deployment_name}`, `{resource_name_prefix}-ActionCommonPolicy1-{deployment_name}`, and `...ActionCommonPolicy2...`. Role names are fixed. Role permissions are described in [IAM roles](/docs/cbp/reference/aws-account-config/iam-roles/); they are generated from the same policy documents as those in the CloudFormation template.

## Update and remove

- **Update:** Download the current zip from the connection form, replace the module files, and run `terraform apply`. Policy changes apply in place.
- **Remove:** Disconnect the account in CloudBolt first so it stops creating exports, then run `terraform destroy`. With `create_bucket = true` the bucket is deleted only if it's empty; move or delete the exports first.

## Troubleshooting

| Problem | Solution |
| --- | --- |
| **Connect Terraform Deployment** isn't on the AWS connection form | Terraform onboarding isn't enabled for your organization. [Contact CloudBolt Support](https://support.cloudbolt.io/cloudbolt/s/). |
| `EntityAlreadyExists` on a role or the OIDC provider during `terraform apply` | The account already carries the CloudBolt CloudFormation stack, or the OIDC provider exists from an earlier deployment. Role names are fixed, so the stack and the module can't coexist: delete the stack (keep the bucket and set `create_bucket = false` if it holds exports you want to retain) or import the existing roles into Terraform state. For the provider alone, set `manage_oidc_providers = false`. |
| No CloudBolt outputs found in the pasted text | Paste the whole document that `terraform output -json` prints (or the plain `terraform output` listing). It must contain `account_id`, `focus_bucket_name`, `resource_name_prefix`, and `deployment_name`. |
| Setup is taking longer than expected. Check that the Terraform roles exist and trust CloudBolt, then retry | Confirm `terraform apply` completed in the account you connected, and that `tenant_hosted_region` matches where your CloudBolt tenant is hosted (US or EU). Correct the value, re-apply, then select **Retry** on the connection card. |
| The `AWSServiceRoleForBCMDataExports` service-linked role is missing | Create the service-linked role, then select **Re-Sync** on the connection card. See [Prerequisites](#prerequisites). |
| Cost Optimization Hub is not enabled for this account | Opt in for the organization, including member accounts, then select **Re-Sync** on the connection card. See [Prerequisites](#prerequisites). |
| An export couldn't be created because the table already has 2 of 2 exports | AWS allows two data exports per table. The message names the exports holding the slots. Delete one you no longer need, or, if the message reports an incompatible format, update that export so that **Format** and **Compression** are both set to **PARQUET**. Then select **Re-Sync**. |

<div class="alert alert-info" role="alert">


[Contact CloudBolt Support](https://support.cloudbolt.io/cloudbolt/s/) if you require assistance with your deployment.
</div>


## Related topics

- [Configure AWS accounts](/docs/cbp/getting-started/configure-cloud-accounts/aws/): The recommended CloudFormation path.
- [Connect AWS](/docs/cbp/getting-started/connect-cloud-accounts/aws/): Connecting a CloudFormation deployment.
- [Advanced AWS account configuration](/docs/cbp/reference/aws-account-config/advanced-aws-config/): StackSet deployment types that cover member accounts.
- [IAM roles](/docs/cbp/reference/aws-account-config/iam-roles/): Details on the roles the module creates.
- [CloudFormation template parameters](/docs/cbp/reference/aws-account-config/cloudformation-template-parameters/): The CloudFormation equivalents of the module variables.
- [CloudFormation template outputs](/docs/cbp/reference/aws-account-config/cloudformation-template-outputs/): The CloudFormation equivalents of the module outputs.
