shoc-frontend-new/terraform/README.md

367 lines
16 KiB
Markdown

# Frontend Terraform adoption runbook
This tree adopts the existing Sea Haven SHOC frontend hosting resources without
recreating them. It implements the local configuration and plan-safety tooling
only. Creating these files, formatting them, initializing with
`-backend=false`, and validating them does not authorize an AWS, HCP Terraform,
GitHub, CloudFormation, DNS, or deployment mutation.
The rollout order is `tf-poc`, dev, then staging. Production and tf-poc teardown
are separate follow-up changes.
## Fixed targets
- AWS account: `396287094661`
- AWS region: `us-east-1`
- HCP organization: `seahaven`
- HCP project: `seahaven-external-dev`
- Workspaces:
- `shoc-frontend-new-tf-poc`
- `shoc-frontend-new-dev`
- `shoc-frontend-new-staging`
- HCP auto-apply: off for all three workspaces
- tf-poc site: `frontend-tf-poc.seahaven.com`
- tf-poc API build value: `https://api.tf-poc.seahaven.com/api`
- tf-poc bucket: `seahaven-shoc-frontend-tf-poc`
- tf-poc deploy role: `githubdeploy-shoc-frontend-new-tf-poc`
- tf-poc GitHub environment: `tf-poc`
The `cloud` blocks identify the organization, project, and workspace. Auto-apply
is an HCP workspace setting and must be verified operationally before connecting
VCS or starting a run.
## Ownership boundary
`live/modules/environment-owned` owns exactly these 13 addresses:
1. `module.environment_owned.aws_s3_bucket.site`
2. `module.environment_owned.aws_s3_bucket_public_access_block.site`
3. `module.environment_owned.aws_s3_bucket_ownership_controls.site`
4. `module.environment_owned.aws_s3_bucket_server_side_encryption_configuration.site`
5. `module.environment_owned.aws_s3_bucket_versioning.site`
6. `module.environment_owned.aws_s3_bucket_policy.site`
7. `module.environment_owned.aws_cloudfront_distribution.site`
8. `module.environment_owned.aws_cloudfront_origin_access_control.site`
9. `module.environment_owned.aws_cloudfront_function.spa_rewrite`
10. `module.environment_owned.aws_route53_record.site_a`
11. `module.environment_owned.aws_route53_record.site_aaaa`
12. `module.environment_owned.aws_iam_role.github_deploy`
13. `module.environment_owned.aws_iam_role_policy.github_deploy`
Every managed resource has `prevent_destroy = true`.
`live/modules/environment-inventory` is data-only. It resolves and checks the
caller account, provider region, public hosted zone, ACM certificate, account
GitHub OIDC provider, and AWS managed `Managed-CachingOptimized` CloudFront
cache policy.
The following remain outside state:
- public hosted zones and ACM certificates
- the account-global GitHub OIDC provider
- the AWS managed CloudFront cache policy
- `CDKToolkit` resources and CDK metadata
- S3 auto-delete custom resources, provider Lambda, provider role, and log group
- hosted-zone and ACM validation internals
- CloudFront service-generated resources
## Exact live inventory
### Dev
- Bucket and all bucket subresources:
`seahaven-shoc-frontend-dev`
- Distribution: `E2CWLM1AFB964P`
- OAC: `E30VSIK87N8H64`
- OAC name:
`shocfrontenddevDistributionOrigin1S3OriginAccessControlDFC82620`
- OAC description: the API empty value, modeled as `""`
- Distribution origin ID:
`shocfrontenddevDistributionOrigin10CCD0EE1`
- Function: `us-east-1shocfrontenddevSpaRewrite58674DB8`
- A import ID: `Z07671212N75U4YLPWZR8_dev.seahaven.com_A`
- AAAA import ID: `Z07671212N75U4YLPWZR8_dev.seahaven.com_AAAA`
- Deploy role: `githubdeploy-shoc-frontend-new-dev`
- Inline policy import ID:
`githubdeploy-shoc-frontend-new-dev:GithubDeployRoleDefaultPolicyE8F540D1`
- Hosted zone: `Z07671212N75U4YLPWZR8`
- Stack: `shoc-frontend-dev`
### Staging
- Bucket and all bucket subresources:
`seahaven-shoc-frontend-staging`
- Distribution: `E2JDVEZ6EGD49J`
- OAC: `E1PF5R6QQNBZAI`
- OAC name:
`shocfrontendstagingDistributOrigin1S3OriginAccessControl82B1C17D`
- OAC description: the API empty value, modeled as `""`
- Distribution origin ID:
`shocfrontendstagingDistributionOrigin16E4628FC`
- Function: `us-east-1shocfrontendstagingSpaRewriteE9C0CBDA`
- A import ID:
`Z02602739VQWBWCAGXP4_staging.seahaven.com_A`
- AAAA import ID:
`Z02602739VQWBWCAGXP4_staging.seahaven.com_AAAA`
- Deploy role: `githubdeploy-shoc-frontend-new-staging`
- Inline policy import ID:
`githubdeploy-shoc-frontend-new-staging:GithubDeployRoleDefaultPolicyE8F540D1`
- Hosted zone: `Z02602739VQWBWCAGXP4`
- Stack: `shoc-frontend-staging`
Both live roots inventory the shared certificate:
`arn:aws:acm:us-east-1:396287094661:certificate/2b78e74f-7b65-4b82-a413-7a498b102f00`
The live roots preserve the observed pre-adoption configuration:
- `adoption_complete = false`
- `Environment`, `ManagedBy=cdk`, and `Project=shoc-frontend` tags
- the S3-only `aws-cdk:auto-delete-objects=true` tag
- the deploy-role-only
`HcpTerraformWorkspace=shoc-frontend-new-<environment>` manager tag
- current OAC names, empty descriptions, origin IDs, comments, protocols, cache
policy, certificate, trust subjects, role descriptions, and legacy deploy
policy
- the exact legacy bucket-policy grant for the S3 auto-delete helper
- the mandatory deterministic permissions boundary
`arn:aws:iam::396287094661:policy/shoc-frontend-new-<environment>-deploy-boundary`
The approved legacy-owner prerequisite narrows the dev role's exact subject
from `StringLike` to `StringEquals` before import. All roots therefore use
`StringEquals` in both modes. The HCP workspace manager tag remains on the role
in both modes and is never added to S3 or CloudFront resources.
If a prerequisite changes any live metadata before import, update the matching
root to the newly observed exact value and prove a zero-change import plan. Do
not approve that drift through the controlled-update checker.
## Prerequisites
Before any remote plan:
1. Confirm the deployment workflow for the target environment is paused while
PR validation remains active.
2. Confirm no CloudFormation/CDK update or content deployment can race the
adoption.
3. Confirm the HCP workspace is in the `seahaven-external-dev` project with
auto-apply off.
4. Confirm the org-baseline plan/apply roles and deploy-role permissions
boundary exist with exact workspace trust.
5. Attach the root's deterministic boundary through the approved legacy-owner
procedure. It is mandatory before the import plan.
6. Verify account `396287094661`, region `us-east-1`, all import IDs, current
tags, the dev `StringEquals` trust prerequisite, role description, boundary,
policies, distribution configuration, OAC configuration, function code, DNS
targets, and bucket settings using read-only queries.
7. Confirm the creator stack has retention semantics for all 13 transferred
resources and the S3 auto-delete custom resource. A synthesized template and
reviewed change set are required before mutation.
8. Capture a complete object-version inventory and smoke-test baseline.
Do not remove or replace the S3 auto-delete custom resource casually. Deleting
it while its handler is active can empty the versioned bucket. Retain it during
ownership transfer and prove the tf-poc path before touching dev.
## HCP variables
Configure dynamic AWS credentials in each workspace. Use the exact
org-baseline role ARNs for that workspace:
- environment variable `TFC_AWS_PROVIDER_AUTH=true`
- environment variable `TFC_AWS_PLAN_ROLE_ARN`
- environment variable `TFC_AWS_APPLY_ROLE_ARN`
- Terraform variable `adoption_complete=false`
Do not store AWS access keys. Mark sensitive values sensitive even when they are
not credentials. VCS working directories are:
- `terraform/live/tf-poc`
- `terraform/live/dev`
- `terraform/live/staging`
tf-poc also requires every variable in `terraform.tfvars.example`. Populate
them only from creator outputs and read-only verification. The check in the
tf-poc root blocks planning while a value is empty or starts with
`REPLACE_WITH_`.
## Local validation
From the repository root:
```powershell
terraform fmt -check -recursive terraform
python scripts/test-terraform-import-plan-check.py
```
For every root:
```powershell
terraform -chdir=terraform/live/tf-poc init -backend=false
terraform -chdir=terraform/live/tf-poc validate
terraform -chdir=terraform/live/dev init -backend=false
terraform -chdir=terraform/live/dev validate
terraform -chdir=terraform/live/staging init -backend=false
terraform -chdir=terraform/live/staging validate
```
Initialization without the backend may download providers and write lockfiles,
but it must not contact HCP state or plan against AWS.
## tf-poc flow
1. Deploy only the temporary shared stack with `tfPocPhase=zone` and record its
name servers.
2. Apply the separately approved parent-zone NS delegation and verify it
publicly.
3. Use `tfPocPhase=environment` to add the certificate and environment stack.
Do not attempt certificate creation before delegation.
4. Record creator outputs for the distribution, OAC ID/name, function, zone,
certificate, origin ID, role, inline policy, bucket auto-delete helper role,
and DNS import IDs.
5. With the frontend tf-poc HCP role gate still false, set the five org-baseline
tf-poc identifiers from those outputs and deploy the reviewed boundary
update. Confirm the creator role's existing boundary now permits only its
bucket operations and exact distribution invalidation.
6. Deploy the real SPA through GitHub environment `tf-poc`, built with
`VITE_API_URL=https://api.tf-poc.seahaven.com/api`.
7. Pass HTTPS page load, `/login`, extensionless SPA fallback, asset-reference
integrity, expected/forbidden API URL scan, cache headers, invalidation
completion, API CORS/preflight connectivity, and index rollback.
8. Populate HCP variables. Re-run read-only inventory and compare all declared
metadata.
9. Produce the import plan, export JSON, and pass the zero-change import gate.
10. Review and apply only the imports. Require an immediate second no-op plan.
11. Prepare and inspect retention for all transferred resources and the
auto-delete custom resource. Do not detach yet.
12. Set only tf-poc `adoption_complete=true`. Run the controlled-update gate
with the exact addresses below, apply after review, and require a no-op
plan. This removes the bucket policy grant while the CDK auto-delete helper
role still exists.
13. Detach the creator stack with the reviewed retention template. Verify
identifiers, every object version, DNS, HTTPS/API smoke checks, deploy-role
assumption, and a final no-op plan.
Stop on a missing output, placeholder, nonzero import action, unexpected
address, replacement, inventory mismatch, retention mismatch, or smoke failure.
## Import plan safety
Create a saved plan using the approved remote workflow, then export its JSON:
```powershell
terraform show -json path\to\saved.plan > path\to\plan.json
python scripts/check-terraform-import-plan.py path\to\plan.json --environment tf-poc
```
Use `dev` or `staging` for the corresponding root. Import mode requires:
- exactly the canonical 13 addresses and AWS types
- valid import metadata for every resource
- exact known import IDs for dev and staging
- populated, non-placeholder creator IDs for tf-poc
- zero create, update, delete, or replace actions
After import apply, export the immediate refresh plan and use the distinct
post-import mode. It requires all 13 resources to be no-op and rejects any
remaining import metadata:
```powershell
python scripts/check-terraform-import-plan.py path\to\post-import-plan.json `
--environment tf-poc `
--post-import-no-op
```
The exact controlled-adoption addresses are:
- `module.environment_owned.aws_s3_bucket.site`
- `module.environment_owned.aws_s3_bucket_policy.site`
- `module.environment_owned.aws_cloudfront_distribution.site`
- `module.environment_owned.aws_cloudfront_function.spa_rewrite`
- `module.environment_owned.aws_iam_role.github_deploy`
- `module.environment_owned.aws_iam_role_policy.github_deploy`
The bucket-policy update removes only the retained auto-delete helper grant.
The IAM role update changes ownership tags while preserving the exact
`StringEquals` subject, boundary, and HCP manager tag.
Run controlled mode by repeating the exact option:
```powershell
python scripts/check-terraform-import-plan.py path\to\plan.json `
--environment tf-poc `
--allow-update-address module.environment_owned.aws_s3_bucket.site `
--allow-update-address module.environment_owned.aws_s3_bucket_policy.site `
--allow-update-address module.environment_owned.aws_cloudfront_distribution.site `
--allow-update-address module.environment_owned.aws_cloudfront_function.spa_rewrite `
--allow-update-address module.environment_owned.aws_iam_role.github_deploy `
--allow-update-address module.environment_owned.aws_iam_role_policy.github_deploy
```
Controlled mode permits only in-place updates to the addresses explicitly
listed on that invocation. It rejects create, delete, replace, import metadata,
unapproved addresses, and unused allowlist entries. OAC and Route 53 must remain
unchanged.
If an ownership-tagged resource does not actually update because its final tags
are already present, omit that address from both the plan expectation and the
command. Never leave an unused allowlist entry.
## Dev and staging flow
Run one live environment at a time.
For dev:
1. Keep releases paused.
2. Complete and verify boundary, retention, and exact-metadata prerequisites.
3. Run and review the zero-change import plan.
4. Apply imports and require a second no-op plan.
5. Prepare and verify the retention template only after tf-poc evidence is
accepted. Do not detach yet.
6. Set `adoption_complete=true`, allow only the exact updating addresses from
the controlled list, apply after review, and require another no-op plan.
7. Detach CloudFormation with the reviewed retention template.
8. Verify IDs, object versions, DNS, TLS, API connectivity, content deployment,
invalidation, rollback, deploy identity, and a final no-op plan.
9. Re-enable dev release only after explicit approval.
Observe dev for the agreed window. Then repeat the full sequence for staging.
Staging termination protection requires a separately reviewed disable
immediately before retained stack deletion. Do not carry approval from dev into
staging.
## Evidence
Retain for each phase:
- HCP run URL and workspace settings showing auto-apply off
- saved plan JSON and checker output
- state list containing exactly the 13 managed addresses
- read-only inventory before and after each mutation
- synthesized CloudFormation template, reviewed change set, and stack events
- object-version inventory
- exact DNS, certificate, distribution, OAC, function, role, and policy IDs
- deployment, invalidation, smoke, and rollback output
- post-action no-op plan
- phase close-out with completed work, validation, risks, deviations, and
remaining work
## Rollback
- Before import apply: discard the run and correct configuration.
- After import but before the controlled ownership update: remove only the
imported Terraform state addresses under a separately reviewed state
operation. CloudFormation remains authoritative.
- After the controlled ownership update but before detachment: do not simply
remove Terraform state or redeploy CloudFormation. Either complete the
reviewed retained detachment or explicitly restore the exact pre-adoption
policy and tags under a separate rollback approval.
- After detachment: Terraform remains authoritative. Restore content from the
versioned bucket and release manifests. Do not recreate the legacy stack over
retained resources.
- Re-establishing CloudFormation ownership requires a reviewed CloudFormation
`IMPORT` change set. An ordinary create/update is not a rollback.
Any replacement, destroy, cross-environment ID, missing import, broad policy
change, or failed smoke check is a hard stop.