All the states are belong to us
Find a file
Alexander Fortin 9ebf6438ef
All checks were successful
Deploy Terraform state backend / deploy (push) Successful in 26s
chore: add gitignore, update readme, add path filters to ci/cd
2026-07-06 15:07:24 +03:00
.forgejo/workflows chore: add gitignore, update readme, add path filters to ci/cd 2026-07-06 15:07:24 +03:00
cloudformation feat: first commit 2026-07-06 14:08:44 +03:00
.gitignore chore: add gitignore, update readme, add path filters to ci/cd 2026-07-06 15:07:24 +03:00
justfile feat: first commit 2026-07-06 14:08:44 +03:00
lefthook.yml feat: first commit 2026-07-06 14:08:44 +03:00
LICENSE feat: first commit 2026-07-06 14:08:44 +03:00
README.md chore: add gitignore, update readme, add path filters to ci/cd 2026-07-06 15:07:24 +03:00

Terraform state bucket

This repository contains a CloudFormation template that provisions the AWS resources needed to use an S3 bucket as a Terraform remote state backend, shared by multiple projects.

  • tries to solve the problem of also managing the S3 backend bucket via IaC and keeping it in a place that I can easily access and manage (and won't lose/forget easily)
  • leverages Forgejo's OIDC identity provider to authenticate with AWS so no long-lived access keys are needed

Resources

The template creates:

  • a KMS key used to encrypt the state files
  • an S3 bucket used to store the state files
  • an IAM managed policy granting the minimal permissions needed to use the backend from Terraform (attach it to the user/role that runs Terraform)
  • Forgejo registered as an OIDC identity provider, plus an IAM role that Forgejo Actions assumes via OIDC — so the deploy pipeline uses short-lived credentials and there are no long-lived access keys to rotate

Bootstrap

The deploy workflow bootstraps itself. On its first run it authenticates with static AWS access keys (no OIDC role exists yet) and creates the stack — which provisions the Forgejo OIDC provider and the deployer role. From then on every run authenticates through OIDC.

It uses the AWS_DEPLOYER_ROLE_ARN repository variable as the bootstrap signal:

  • unset → not bootstrapped → deploy with access keys (creates the role)
  • set → bootstrapped → deploy through OIDC
  1. Provide AWS access keys

    In Settings → Actions → Secrets add AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY for an IAM principal that can create everything in the stack (S3 bucket, KMS key, IAM roles/policies and the OIDC provider) — e.g. the root account or an admin user. They are only needed for the first run.

  2. Set the Forgejo instance host

    Use env.FORGEJO_SERVER_URL by default, overridable with MY_FORGEJO_PUBLIC_URL — the host of the Forgejo instance, without scheme or trailing slash (e.g. codeberg.org).

  3. Run the workflow (bootstrap)

    Trigger it with a push to master or a manual dispatch. Since AWS_DEPLOYER_ROLE_ARN is not set yet, it runs in bootstrap mode with the access keys and creates the stack and deployer role.

  4. Point the workflow at the deployer role

    Copy the DeployerRoleArn value from just outputs and set it as the repository variable AWS_DEPLOYER_ROLE_ARN.

  5. Done

    Every subsequent run authenticates through OIDC. You can now delete the AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY secrets.

Alternative: you can also bootstrap locally with just deploy <host> <repo> <bucket name> using credentials configured in your AWS profile, then set AWS_DEPLOYER_ROLE_ARN and let the workflow take over.

Trust boundary: the deployer role manages the whole stack, including its own permissions policy. Anyone who can push to ForgejoBranch can therefore change what the role is allowed to do — protect that branch accordingly.

Using the backend from Terraform

terraform {
  backend "s3" {
    bucket       = "<bucket name>"                  # `just outputs | grep -A 1 BucketName` to fetch the bucket name
    key          = "project-name/terraform.tfstate"
    region       = "us-east-2"
    use_lockfile = true
    encrypt      = true
    kms_key_id   = "alias/terraform-state"
  }
}

Development

Prerequisites

  • cfn-lint — CloudFormation template linter
  • lefthook — Git hooks manager (already configured in lefthook.yml)

Setup

Install the Git hooks so cfn-lint runs on every commit:

lefthook install

Lint manually

just lint

References