diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fbc8ef5..0a972240 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- [Terraform] S3 state bucket versioning and documented state rollback procedure in `infra-terraform/README.md` + ### Changed - Updated architecture diagram with latest logos (`docs/architecture-diagram/FAST-architecture-20260403.png`) +- [Terraform] Switched state backend from DynamoDB-based locking to S3 native locking (`use_lockfile`); Terraform >= 1.11 now required. Migration guidance for existing deployments in `infra-terraform/README.md` + +### Security + +- [Terraform] Public-access-block recommended on state bucket prerequisites ## [0.4.1] - 2026-03-25 diff --git a/docs/TERRAFORM_DEPLOYMENT.md b/docs/TERRAFORM_DEPLOYMENT.md index d46607f9..0fb479b1 100644 --- a/docs/TERRAFORM_DEPLOYMENT.md +++ b/docs/TERRAFORM_DEPLOYMENT.md @@ -8,7 +8,7 @@ This guide walks you through deploying the Fullstack AgentCore Solution Template Before deploying, ensure you have: -- **Terraform** >= 1.5.0 (see [Install Terraform](https://developer.hashicorp.com/terraform/install)) +- **Terraform** >= 1.11.0 (see [Install Terraform](https://developer.hashicorp.com/terraform/install)). Required for S3 native state locking -- see [State Management](../infra-terraform/README.md#state-management) for remote backend setup. - **AWS CLI** configured with credentials (`aws configure`) - see [AWS CLI Configuration guide](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html) - **Python 3.11+** (for the frontend deployment script) - **Docker** (only required for `backend_deployment_type = "docker"`) - see [Install Docker Engine](https://docs.docker.com/engine/install/). Verify with `docker ps`. Alternatively, [Finch](https://github.com/runfinch/finch) can be used on Mac. See [below](#docker-cross-platform-build-setup-required-for-non-arm-machines) if you have a non-ARM machine. diff --git a/infra-terraform/README.md b/infra-terraform/README.md index a5f28bb3..8da84137 100644 --- a/infra-terraform/README.md +++ b/infra-terraform/README.md @@ -143,26 +143,86 @@ The modules are deployed in this order: ## State Management -By default, Terraform uses **local state** (`terraform.tfstate`). For team collaboration, use the S3 backend: +By default, Terraform uses **local state** (`terraform.tfstate`). For team collaboration, use the S3 backend with native locking (`use_lockfile`, requires Terraform >= 1.11) and bucket versioning: ```bash -# 1. Create S3 bucket & DynamoDB table (one-time) +# 1. Create the state bucket (one-time) aws s3 mb s3://YOUR-BUCKET-NAME --region us-east-1 -aws dynamodb create-table --table-name terraform-locks \ - --attribute-definitions AttributeName=LockID,AttributeType=S \ - --key-schema AttributeName=LockID,KeyType=HASH \ - --billing-mode PAY_PER_REQUEST --region us-east-1 -# 2. Copy and edit the backend config +# 2. Enable versioning (keeps prior state objects for rollback) +aws s3api put-bucket-versioning \ + --bucket YOUR-BUCKET-NAME \ + --versioning-configuration Status=Enabled + +# 3. Block public access on the state bucket +aws s3api put-public-access-block \ + --bucket YOUR-BUCKET-NAME \ + --public-access-block-configuration \ + BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true + +# 4. Copy and edit the backend config cp backend.tf.example backend.tf # Edit backend.tf with your bucket name -# 3. Migrate state +# 5. Migrate state terraform init -migrate-state ``` See `backend.tf.example` for the full configuration. +### Rolling back state + +With bucket versioning enabled, a prior `terraform.tfstate` object can be restored without `terraform state` surgery. **Only do this when no operator is running Terraform against the stack** -- coordinate before restoring. + +```bash +# 1. List historical versions of the state object +aws s3api list-object-versions \ + --bucket YOUR-BUCKET-NAME \ + --prefix fast/terraform.tfstate + +# 2. Restore a prior version by copying it over the current object +aws s3api copy-object \ + --bucket YOUR-BUCKET-NAME \ + --key fast/terraform.tfstate \ + --copy-source "YOUR-BUCKET-NAME/fast/terraform.tfstate?versionId=" + +# 3. Run plan first to verify the drift matches expectations before any apply +terraform plan +``` + +Versioning is **not retroactive**: only state objects written after versioning was enabled can be recovered. Enable versioning on day one, not after an incident. + +### Migrating from DynamoDB-only locking + +Earlier versions of this template used a DynamoDB `terraform-locks` table. To migrate, use a two-phase rollout so teams running both old and new configs remain safe. Every operator must be on Terraform >= 1.11 before starting. + +**Phase 1 -- dual-lock transition.** Temporarily keep both locks active so any `apply` from either config is protected: + +```hcl +terraform { + backend "s3" { + bucket = "YOUR-BUCKET-NAME" + key = "fast/terraform.tfstate" + region = "us-east-1" + dynamodb_table = "terraform-locks" # keep during transition + use_lockfile = true # new + encrypt = true + } +} +``` + +Run `terraform init -reconfigure` and have every operator pull the dual-lock config before phase 2. + +If versioning was not enabled on the existing bucket, enable it now with the `put-bucket-versioning` command above. Prior state objects are not recoverable retroactively. + +**Phase 2 -- cut over to S3-only.** Once everyone is on the dual-lock config, remove `dynamodb_table` (matching the shipped `backend.tf.example`), run `terraform init -reconfigure`, then delete the table: + +```bash +aws dynamodb delete-table --table-name terraform-locks --region us-east-1 +``` + +The `dynamodb_table` argument is deprecated in Terraform 1.11 and may be removed in a future major release, so do not stay on the dual-lock config indefinitely. + ## Resource Reference | Resource Type | Terraform Resource | diff --git a/infra-terraform/backend.tf.example b/infra-terraform/backend.tf.example index 5641ec6d..b4d220c5 100644 --- a/infra-terraform/backend.tf.example +++ b/infra-terraform/backend.tf.example @@ -5,28 +5,37 @@ # S3 Remote Backend Configuration # ============================================================================= # +# Uses S3 native locking (use_lockfile), which requires Terraform >= 1.11. +# DynamoDB is no longer needed -- locking and versioning are handled by S3. +# # To enable remote state storage: -# 1. Create S3 bucket and DynamoDB table (see commands below) +# 1. Create the S3 bucket with versioning and public-access-block (see below) # 2. Copy this file: cp backend.tf.example backend.tf -# 3. Update bucket/table names below +# 3. Update the bucket name below # 4. Run: terraform init -migrate-state # # Prerequisites (run once): -# aws s3 mb s3://YOUR-BUCKET-NAME --region us-east-1 -# aws dynamodb create-table \ -# --table-name terraform-locks \ -# --attribute-definitions AttributeName=LockID,AttributeType=S \ -# --key-schema AttributeName=LockID,KeyType=HASH \ -# --billing-mode PAY_PER_REQUEST \ -# --region us-east-1 +# aws s3 mb s3://YOUR-TERRAFORM-STATE-BUCKET --region us-east-1 +# +# aws s3api put-bucket-versioning \ +# --bucket YOUR-TERRAFORM-STATE-BUCKET \ +# --versioning-configuration Status=Enabled +# +# aws s3api put-public-access-block \ +# --bucket YOUR-TERRAFORM-STATE-BUCKET \ +# --public-access-block-configuration \ +# BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true +# +# Already using DynamoDB-based locking? See the "Migrating from DynamoDB-only +# locking" section in infra-terraform/README.md for the two-phase procedure. # ============================================================================= terraform { backend "s3" { - bucket = "YOUR-TERRAFORM-STATE-BUCKET" # Change this - key = "fast/terraform.tfstate" - region = "us-east-1" - dynamodb_table = "terraform-locks" - encrypt = true + bucket = "YOUR-TERRAFORM-STATE-BUCKET" # Change this + key = "fast/terraform.tfstate" + region = "us-east-1" + use_lockfile = true + encrypt = true } } diff --git a/infra-terraform/modules/amplify-hosting/versions.tf b/infra-terraform/modules/amplify-hosting/versions.tf index a0c25550..babf4a64 100644 --- a/infra-terraform/modules/amplify-hosting/versions.tf +++ b/infra-terraform/modules/amplify-hosting/versions.tf @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 terraform { - required_version = ">= 1.5.0" + required_version = ">= 1.11.0" required_providers { aws = { diff --git a/infra-terraform/modules/backend/versions.tf b/infra-terraform/modules/backend/versions.tf index 8d1657eb..40e2ae88 100644 --- a/infra-terraform/modules/backend/versions.tf +++ b/infra-terraform/modules/backend/versions.tf @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 terraform { - required_version = ">= 1.5.0" + required_version = ">= 1.11.0" required_providers { aws = { diff --git a/infra-terraform/modules/cognito/versions.tf b/infra-terraform/modules/cognito/versions.tf index a0c25550..babf4a64 100644 --- a/infra-terraform/modules/cognito/versions.tf +++ b/infra-terraform/modules/cognito/versions.tf @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 terraform { - required_version = ">= 1.5.0" + required_version = ">= 1.11.0" required_providers { aws = { diff --git a/infra-terraform/versions.tf b/infra-terraform/versions.tf index 89e69d00..fe005359 100644 --- a/infra-terraform/versions.tf +++ b/infra-terraform/versions.tf @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 terraform { - required_version = ">= 1.5.0" + required_version = ">= 1.11.0" required_providers { aws = {