Connect AWS with Terraform instead of CloudFormation

A Terraform equivalent of the generated AWS template, with an Organization variant and a way to compare it to the wizard.

Connect AWS with Terraform instead of CloudFormation

Create the same IAM role, S3 bucket and Cost and Usage export that the CostGlass wizard template creates, but as Terraform you can review and keep in version control.

Where in CostGlassSettings → Cloud Accounts → AWS wizard
PlanAll plans
Who can do thisOwner or Admin in CostGlass; Terraform access to AWS
Time neededAbout 20 minutes, plus up to 24 hours for the first data
In short
  • CostGlass generates a CloudFormation template for AWS. It does not generate Terraform. This page gives a hand-written Terraform equivalent.
  • You still start the AWS wizard in CostGlass, because it creates your connection, your External ID and your CostGlass account ID.
  • Nothing secret is hard-coded: the CostGlass account ID and the External ID are variables you fill in.
  • You then paste the RoleArn and BucketName outputs into the wizard, exactly as with CloudFormation.
  • Because it is maintained by hand, compare its permissions with the wizard template before you apply. A comparison script is below.
Hand-maintained equivalent
The CloudFormation template that the wizard generates is the source of truth. This Terraform matches it as of this guide's publication (checked with terraform validate on AWS provider 5.100). If CostGlass adds a permission to the template later, this page may lag behind. Always run the comparison in the last section before applying.
Before you begin
  • Terraform 1.5 or newer and AWS provider hashicorp/aws version ~> 5.0. The export resource (aws_bcmdataexports_export) needs a 5.x release that includes it; if init says the resource type is unknown, run terraform init -upgrade.
  • AWS credentials for the account that pays the bill (the management account for an Organization), allowed to create IAM roles, S3 buckets and Billing Data Exports.
  • The CostGlass wizard open at the Deploy step (explained below).

Get your two values from CostGlass

The Terraform needs two values that belong to your connection. Never type them from memory or share them in chat.

1
Start the wizard
Go to Settings → Cloud Accounts, click the AWS card, choose the same Account type and the same features you will use in Terraform, then click Generate my template.
2
Copy the External ID
On Deploy the CloudFormation template it is shown as External ID (pre-filled in the template). This is the value for external_id.
3
Copy the CostGlass account ID
In the template preview (or in the file from Download) find the parameter CostGlassAccountId and copy its Default value, a 12-digit number. This is the value for costglass_account_id.
4
Keep the wizard open
Do not click "I have deployed it" until Terraform has finished. Do not start a different connection, because that produces a different External ID.
Important
Tick the same features in the wizard that you enable in Terraform. CostGlass records the wizard's selection, and shows it on the account card.

Single account

Create a folder with these files. Everything is applied in us-east-1, like the CloudFormation template.

versions.tf
variables.tf
main.tf
outputs.tf
terraform.tfvars (example, do not commit the real one)

Variables

VariableWhat it doesDefault
costglass_account_idThe CostGlass AWS account allowed to assume the role.None. Required.
external_idYour connection's External ID. Marked sensitive.None. Required.
bucket_name_prefixBucket is named <prefix>-<your account ID>.costglass-cur-exports
export_nameName of the Cost and Usage export.CostGlassCURExport
s3_prefixFolder in the bucket.cur
enable_instance_schedulerAdds the only write permissions (start, stop, scale).false
enable_rightsizingAdds CloudWatch metrics and Compute Optimizer reads.false
enable_uptime_monitoringAdds status check, alarm and target health reads.false
Tip
Pass the External ID with an environment variable instead of a file: export TF_VAR_external_id='...'. The value is also stored in Terraform state, so protect your state file like a password.

Run it and finish in CostGlass

1
Initialise
Run terraform init.
2
Plan
Run terraform plan and read it. You should see the role, its policies, the bucket and its settings, and the export. Nothing is created yet.
3
Apply
Run terraform apply and confirm. When it finishes it prints RoleArn, BucketName and ExportArn.
4
Back in the wizard
Click I have deployed it. Paste RoleArn into Role ARN (Outputs: RoleArn) and BucketName into S3 bucket (Outputs: BucketName). Leave Prefix as cur/ (change it to match s3_prefix, with a trailing slash, if you changed that) and Region as us-east-1.
5
Verify
Click Verify & connect. From here everything is identical to the CloudFormation guide, including the wait of up to 24 hours for the first export.

AWS Organization

In the wizard choose AWS Organization. Run the single-account Terraform above in the management account: it creates the bucket, the export and the role CostGlassCURReader. Member accounts also need the role CostGlassReadOnly. Choose one of two ways.

Option A: a StackSet (closest to the wizard template)

Add this file to the same folder. It creates a service-managed StackSet that deploys the member role to every member account, now and when accounts join later. Do the one-time prerequisite first: in the management account, CloudFormation → StackSets → Enable trusted access with AWS Organizations.

stackset.tf

Option B: one Terraform module call per member account

Use this if you want Terraform itself to own the roles, or you only connect a few accounts. Terraform cannot create providers in a loop, so each account needs its own provider alias and module block. The management account assumes OrganizationAccountAccessRole, which AWS Organizations creates in accounts it created; accounts you invited need an equivalent role.

members.tf
modules/member_role/main.tf

Set member_account_ids in your variables, for example ["111111111111", "222222222222"], and add a provider and module pair for each extra account.

Note
Use Option A or Option B, not both. The management account itself uses CostGlassCURReader only, the same as the CloudFormation template.

Check it matches the wizard

Download the YAML from the wizard (Download), save it next to your Terraform, then compare the permission lists. Tick the same features on both sides first.

compare.sh

Empty output means the two lists are equal. Expected differences: for an Organization template the YAML also lists organizations:ListRoots and organizations:DescribeOrganization (used by a helper function that Option A replaces with a Terraform lookup). For Option B, member-account policies appear in the plan of the module, so compare those separately against the member template inside the YAML. Any other permission appearing on one side only means the Terraform is out of date, or you ticked different features.

Also compare the Billing export columns: they live in local.cur_columns in main.tf and must equal the SELECT list in the template's QueryStatement.

FAQ

Why does Terraform use us-east-1?
The Cost and Usage export resource only exists in us-east-1. The role and bucket are created there as well, exactly like the CloudFormation template.
Can I use the CloudFormation template for AWS and Terraform for Azure and Google Cloud?
Yes. Each cloud is independent. See Azure and Google Cloud.
The plan fails with an unknown resource type aws_bcmdataexports_export.
Your AWS provider is too old. Run terraform init -upgrade with the ~> 5.0 constraint.
The S3 bucket policy is rejected.
The configuration applies the public access block before the policy to avoid a known timing problem. If you split the files, keep that dependency.
How do I remove it?
Remove the connection in CostGlass, empty the bucket, then run terraform destroy. See Connect AWS, removing access.

Related guides