shoc-backend/terraform/README.md
Adam Moussa 25c2e84e8f feat(terraform): adopt live deployment roles safely
Add import-only dev and staging state with least-privilege HCP authentication and plan safety guards.
2026-08-28 19:12:34 -04:00

170 lines
6 KiB
Markdown

# Terraform deployment infrastructure
The root module remains the isolated `tf-poc` Elastic Beanstalk and SQL Server
stack in `seahaven-external-dev` (`396287094661`). It must never be reused for
dev or staging state.
Live adoption is deliberately smaller. [`live/`](live/) imports only the
existing GitHub Actions deploy roles and inline policies. All application,
environment, database, certificate, DNS, network, runtime IAM, and secret
resources remain external and are read only as inventory.
The Terraform roots are:
- `./`: isolated POC workload in `shoc-backend-tf-poc`.
- `bootstrap/`: consolidated POC/dev/staging HCP role bootstrap in
`shoc-backend-bootstrap`.
- `live/dev/`: import-only dev deploy-role ownership in
`shoc-backend-dev`.
- `live/staging/`: import-only staging deploy-role ownership in
`shoc-backend-staging`.
IAM lives in this repo, not org-baseline. App CD never runs a bootstrap root.
Auto-apply stays off for every workspace.
The stabilized POC bootstrap deliberately leaves both POC HCP roles read only
and removes `ViewOnlyAccess` plus the broad workload-mutation inline policy.
The POC GitHub deploy role remains unchanged until a separately approved
narrowing or teardown. Future POC teardown runs directly under the approved
SSO administrator session, not the locked HCP apply role.
## Locked names
- Hostname: `tf-poc.api.dev.seahaven.com` (zone `Z07671212N75U4YLPWZR8`)
- EB application / environment: `shoc-backend-tf-poc`
- RDS identifier: `shoc-backend-tf-poc`
- Catalog: `shoc_tf_poc` (create this database once after RDS is available)
- Deploy role: `arn:aws:iam::396287094661:role/tf-managed/githubdeploy-shoc-backend-tf-poc`
- GitHub Environment: `tf-poc` (OIDC `environment:tf-poc`)
- HCP org `seahaven`, project `seahaven-external-dev`
## HCP layout
All SHOC backend environments live in AWS account `396287094661` and HCP
The POC workspaces remain isolated until teardown. The live bootstrap owns
`hcptf-shoc-backend-{dev,staging}` and matching `-plan` roles. The live
environment workspaces own only their existing GitHub deploy role and inline
policy. See [`live/README.md`](live/README.md).
## Console setup (once)
1. In HCP Terraform, create project `seahaven-external-dev` if it does not exist.
2. Create workspace `shoc-backend-bootstrap`:
- VCS later, or CLI-driven until the branch is pushed
- Working directory: `terraform/bootstrap`
- Execution mode: **Local**
- Auto-apply: off
3. Create workspace `shoc-backend-tf-poc`:
- Same branch
- Working directory: `terraform`
- Execution mode: **Remote**
- Auto-apply: off
- Speculative plans: on
4. Do **not** use HCP "Quick setup AWS dynamic credentials".
## Discovery (before first workload apply)
```bash
export AWS_PROFILE=seahaven-external-dev
aws elasticbeanstalk describe-environments \
--environment-names shoc-backend-dev \
--region us-east-1 \
--query 'Environments[0].{Vpc:EndpointURL}'
aws elasticbeanstalk describe-configuration-settings \
--application-name shoc-backend \
--environment-name shoc-backend-dev \
--region us-east-1 \
--query "ConfigurationSettings[0].OptionSettings[?Namespace=='aws:ec2:vpc']"
```
Copy `vpc-REPLACE_ME` and subnet lists into a local `terraform/terraform.tfvars`
(gitignored). Confirm:
- `app.terraform.io` OIDC exists (`create_tfc_oidc_provider=false` in bootstrap).
If the data source fails, set `create_tfc_oidc_provider=true`.
- `external-dev-execution-boundary` exists.
- `aws-elasticbeanstalk-service-role` exists.
- GitHub OIDC provider `token.actions.githubusercontent.com` exists.
## Bootstrap apply (Adam)
SSO AdministratorAccess in this account is subject to the external-dev SCP, so
both `hcptf-*` roles set `permissions_boundary` to
`external-dev-execution-boundary`.
```bash
export AWS_PROFILE=seahaven-external-dev
cd terraform/bootstrap
terraform login
terraform init
terraform apply
```
Then on workspace `shoc-backend-tf-poc`, set workspace-scoped env vars:
- `TFC_AWS_PROVIDER_AUTH=true`
- `TFC_AWS_PLAN_ROLE_ARN` = output `hcp_plan_role_arn`
- `TFC_AWS_APPLY_ROLE_ARN` = output `hcp_apply_role_arn`
Never put those in a project variable set. Set `sendgrid_api_key` as a
sensitive Terraform variable on that workspace when you want mail to work.
## Workload apply
HCP Manual apply on `shoc-backend-tf-poc`. Confirm `api.dev.seahaven.com` still
serves live before and after.
After RDS is available, create the catalog once (SQL Server Express does not
accept `db_name` on `aws_db_instance`):
```sql
CREATE DATABASE [shoc_tf_poc];
```
Then first app deploy can run migrations into that catalog.
## App deploy
GitHub Actions is the real CD path. `.github/workflows/deploy.yml` maps:
- `dev` → `dev`
- `staging` → `staging`
- `main` → `prod`
The live roots import the roles already referenced by the `dev` and `staging`
GitHub Environment secrets. The role ARNs do not change during adoption.
The POC local fallback remains available until teardown:
```bash
export AWS_PROFILE=seahaven-external-dev
bash scripts/deploy-api-tf.sh
```
The script refuses live names (`shoc-backend-dev`, `api.dev.seahaven.com`).
## After POC confirmation
1. Create `shoc-backend-dev` and `shoc-backend-staging` workspaces. Do not
reuse POC state.
2. Apply `bootstrap/` only after approval to create the four narrowly scoped
live HCP roles.
3. Follow the no-op import and controlled-update sequence in
[`live/README.md`](live/README.md).
4. Retire CDK deploy-role ownership only after both role imports are proven.
5. Destroy the POC workload last. Bootstrap state remains.
Do not apply this module as `environment=dev` without imports.
## Local CI equivalent
```bash
terraform -chdir=terraform fmt -check -recursive
terraform -chdir=terraform init -backend=false && terraform -chdir=terraform validate
terraform -chdir=terraform/bootstrap init -backend=false && terraform -chdir=terraform/bootstrap 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
```