A Terraform module for exporting:
- FOCUS (FinOps Open Cost and Usage Specification).
- Cost Optimisation recommendations.
- Carbon Emissions data.
Exports are delivered to an S3 bucket, which replicates securely to Government Digital Services (GDS), over the AWS internal network.
The encrypted (SSE-S3) S3 bucket has the following:
- Bucket Policy which grants permissions to the BCM Data Exports service to write the report data files.
- Lifecycle Policy which removes non-current versions after 1 day. Latest versions are kept for 7 days before being removed.
- Service Linked IAM role allowing the S3 service to read objects from the bucket and replicate them to the destination bucket.
- Replication rule that uses the IAM role above to do the replication. The IAM role is authorised on the destination GDS S3 bucket to allow the sender to only replicate the data, and to an isolated drop-zone.
@jonodrew reviewed this package on 2025-02-13 and found no significant concerns. The package:
- Creates a new S3 bucket for storing cost exports
- Sets up replication to a central S3 bucket
- Sets up AWS cost exports to create a daily report and store it in the bucket created in step 1
- Sets up a cleanup on the bucket to remove old / deleted files after 7 days
The Bucket's Role has permissions to replicate any Object that is put in it, so hosting teams must ensure that nothing is accidentally placed there.
If this library is deployed through a continuous deployment (CD) pipeline, deploying teams should thoroughly check any changes to this codebase before they are deployed.
The module creates an S3 bucket policy that grants the BCM Data Exports service the permissions it needs to write report data. This policy is always present and cannot be removed.
By default, the module applies only the BCM grant. Upgrading to a new version of the module will not change this policy — existing deployments see no Terraform plan changes.
Use additional_policy_statements to add your own IAM statements on top of the default BCM grant. This is the recommended approach — it avoids creating a second aws_s3_bucket_policy resource that would conflict with the module's own.
Example — adding DenyNonSSLRequests:
module "focus" {
source = "github.com/co-cddo/terraform-aws-focus?ref=v2.0.2"
destination_account_id = var.cddo_destination_account_id
destination_bucket_name = var.cddo_destination_bucket_name
additional_policy_statements = [
{
sid = "DenyNonSSLRequests"
effect = "Deny"
actions = ["s3:*"]
resources = [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*",
]
principals = {
type = "*"
identifiers = ["*"]
}
conditions = [
{
test = "Bool"
variable = "aws:SecureTransport"
values = ["false"]
}
]
}
]
}All fields in each statement object are required. Use sid = "" if you don't need a statement ID, and conditions = [] if you have no conditions.
Note: Invalid ARNs in
resourcesorprincipals.identifierswill not causeterraform planto fail — they will be rejected by AWS atterraform applytime.
Set enforce_secure_defaults = true to add pre-built hardening to the bucket. Currently includes:
DenyNonSSLRequests— denies all S3 actions over non-HTTPS connections (bucket policy)- Public access block — enables all four settings (
block_public_acls,block_public_policy,ignore_public_acls,restrict_public_buckets)
This variable defaults to false for backward compatibility. It will default to true in a future major release. Teams are encouraged to opt in now.
module "focus" {
source = "github.com/co-cddo/terraform-aws-focus?ref=v2.0.2"
destination_account_id = var.cddo_destination_account_id
destination_bucket_name = var.cddo_destination_bucket_name
enforce_secure_defaults = true
}If you currently manage your own aws_s3_bucket_policy resource targeting the module's bucket, this continues to work. However, Terraform will conflict if both your resource and the module attempt to manage the same bucket policy. To migrate to the recommended approach:
- Move your custom statements into
additional_policy_statementson the module - Remove your external
aws_s3_bucket_policyresource from your configuration - Run
terraform state rm aws_s3_bucket_policy.<your_resource_name>to remove it from state - Run
terraform plan— you should see the policy updated in-place with no destruction
- Creates AWS Billing & Cost Management data exports for FOCUS, Carbon Emission and Cost Optimisation.
- Creates a manifest file which will keep a record of how the module has been setup.
- Creates an S3 Bucket storing export data in the AWS account.
- Configures replication to a GDS managed destination S3 Bucket.
- Creates a service-link IAM Role for use in replication to GDS.
- Enables versioning and encryption for data at rest within the S3 bucket.
- Configures an S3 Bucket Lifecycle Policy to ensure data is not retained longer than neccesary.
Terraform 1.0+
AWS CLI configured with appropriate permissions
An IAM role with sufficient permissions to create and manage S3 buckets and replication rules, and AWS BCM data exports.
| Name | Version |
|---|---|
| aws | n/a |
No modules.
| Name | Type |
|---|---|
| aws_bcmdataexports_export.carbon | resource |
| aws_bcmdataexports_export.focus | resource |
| aws_bcmdataexports_export.recommendations | resource |
| aws_iam_role.this | resource |
| aws_iam_role_policy.replicator | resource |
| aws_iam_service_linked_role.bcm_data_exports | resource |
| aws_s3_bucket.this | resource |
| aws_s3_bucket_lifecycle_configuration.this | resource |
| aws_s3_bucket_policy.this | resource |
| aws_s3_bucket_replication_configuration.this | resource |
| aws_s3_bucket_public_access_block.this | resource |
| aws_s3_bucket_versioning.this | resource |
| aws_s3_object.this | resource |
| aws_caller_identity.this | data source |
| aws_iam_policy_document.bucket | data source |
| aws_iam_policy_document.replicator | data source |
| aws_iam_policy_document.replicator_assume | data source |
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| additional_policy_statements | Additional IAM policy statements to include in the S3 bucket policy. All fields are required. Statements are appended to the default BCM grant and cannot replace or remove it. | list(object(...)) |
[] |
no |
| bucket_name | The name of the S3 bucket to be created to store reports before replication. If omitted it will create one for you. | string |
null |
no |
| bucket_tags | Map of tags to be associated with the reporting bucket | map(string) |
{} |
no |
| create_cost_recommendations_service_linked_role | Enables the creation of the required service-linked role for data exports to access cost optimisation hub | bool |
false |
no |
| destination_account_id | The account ID of the destination S3 bucket where reports will be replicated to. This will be provided as part of the onboarding process. | string |
n/a | yes |
| destination_bucket_name | The name of the destination S3 bucket where reports will be replicated to. This will be provided as part of the onboarding process. | string |
n/a | yes |
| enable_carbon_export | Enables the collection of carbon footprint report | bool |
true |
no |
| enforce_secure_defaults | When true, adds hardening to the bucket: denies non-SSL requests via bucket policy and enables all four public access block settings. Defaults to false for backward compatibility. Will default to true in a future major release. | bool |
false |
no |
| enable_cost_recommendations_export | Enables the collection of cost recommendations report | bool |
true |
no |
| tags | Tags to apply to all resources created by this module. | map(string) |
{} |
no |
| Name | Description |
|---|---|
| bucket_arn | The ARN of the bucket created to store reports before replicating to GDS |
| replication_role_arn | The ARN of the role used to replicate data from the source account to the destination account |
This module uses semantic versioning. Pin to a specific release tag to avoid unexpected changes:
module "focus" {
source = "github.com/co-cddo/terraform-aws-focus?ref=v2.0.2"
}All releases are listed on the GitHub Releases page.
This module is maintained by the OCTO Observability team at the Department for Science, Innovation & Technology.
- Questions or issues: Open a GitHub issue or email observability@dsit.gov.uk.
- Contributing: See CONTRIBUTING.md.
- Security concerns: See SECURITY.md.
Unless stated otherwise, the codebase is released under the MIT Licence. This covers both the codebase and any sample code in the documentation. The documentation is © Crown copyright and available under the terms of the Open Government Licence v3.0.
