shoc-backend/terraform/live/README.md

164 lines
8 KiB
Markdown

# Backend environment adoption
These roots adopt environment-owned infrastructure without taking ownership of
shared or Elastic Beanstalk-generated infrastructure.
## Ownership
- `dev/` and `staging/` import the existing EB environment, runtime
role/profile/policies, deploy role/policy, app-config secret metadata, and API
record.
- `tf-poc/` manages the retained rehearsal environment after its completed
CloudFormation-to-Terraform transfer, excluding the live-only webhook and
Dynamo policies. It also owns the child zone and DNS-validated ACM
certificate.
- `modules/environment-inventory/` reads and pins only shared resources.
- Org-baseline CloudFormation owns the narrowly scoped HCP Terraform plan/apply
roles.
The shared `shoc-backend` Elastic Beanstalk application and
`shoc-sqlserver-shared` RDS instance, VPC, subnets, EB service role, shared
certificate, shared RDS security group, and EB-generated SG/ALB/ASG/CloudFormation
resources must never enter an environment state. The `shoc_tf_poc` SQL catalog
is out of band.
Secret values are not Terraform resources, variables, outputs, or managed EB
settings. Terraform manages the app-config secret shell and maps approved JSON
keys through `aws:elasticbeanstalk:application:environmentsecrets` using
`secret-arn:json-key` references. Ordinary application environment settings are
limited to non-secret ASP.NET and webhook configuration. The pinned .NET 8
AL2023 platform 3.11.3 supports Secrets Manager JSON-key extraction.
## Mandatory live secret migration
Before importing dev or staging, perform a separately approved production
mutation from a trusted local session:
1. Create a temporary `OptionSettings` JSON file containing the exact
`environmentsecrets` ARN/key references configured in that root and the five
non-secret ordinary environment settings.
2. Create a temporary `OptionsToRemove` JSON file naming the old raw
secret-valued keys in `aws:elasticbeanstalk:application:environment`.
3. Run `aws elasticbeanstalk update-environment` for exactly
`shoc-backend-dev` or `shoc-backend-staging` with
`--option-settings file://...` and `--options-to-remove file://...`.
Include the provider-normalized sorted `Subnets` and `ELBSubnets` values in
this same approved update if live ordering differs.
4. Delete both files, wait for the replacement environment to become Ready and
healthy, and run `scripts/smoke-elastic-beanstalk.sh` against the exact API.
5. Verify the raw ordinary secret settings are absent before generating the
first Terraform plan.
This migration is not performed by Terraform. The first import plan remains
zero-change only after the migration succeeds.
## Mandatory role-boundary attachment
After the org baseline creates the dedicated boundary policies, perform a
separately approved production IAM mutation that attaches:
- `shoc-backend-dev-runtime-boundary` to `shoc-backend-dev`
- `shoc-backend-staging-runtime-boundary` to `shoc-backend-staging`
- `shoc-backend-dev-deploy-boundary` to `githubdeploy-shoc-backend-dev`
- `shoc-backend-staging-deploy-boundary` to
`githubdeploy-shoc-backend-staging`
Attach all four boundaries before the SCP and HCP `PutRolePolicy` exceptions
become effective. In the same approved pre-import phase, add the immutable
`HcpTerraformWorkspace` tag to each GitHub deploy role:
- `githubdeploy-shoc-backend-dev`: `shoc-backend-dev`
- `githubdeploy-shoc-backend-staging`: `shoc-backend-staging`
Verify each exact runtime and deploy boundary ARN and workspace tag from
`GetRole` before importing. Terraform requires the boundaries to be present
during `adoption_complete=false`, so the first import remains zero-change.
Terraform does not perform this pre-import mutation.
## Two-phase adoption
Each dev/staging root pins `adoption_complete=false` in reviewed code until its
initial import is proven. It is not an HCP workspace variable. The retained
tf-poc rehearsal has completed both phases and therefore pins
`adoption_complete=true`.
1. Create the HCP workspace and configure dynamic credentials.
2. Run the declarative imports.
3. Export the HCP plan as JSON and run:
```bash
python scripts/check-terraform-import-plan.py plan.json --environment dev
```
The first plan must be a no-op after import. The guard rejects updates,
creates, deletes, replacements, and managed resource types outside the
approved environment-owned boundary.
4. Apply the no-op import only after review.
5. Change the environment root to `adoption_complete=true` and
`manage_eb_settings=true` in a reviewed code change, then review the
controlled in-place role, policy, secret, and Elastic Beanstalk update:
```bash
# Dev example. Omit any address that is not updating.
python scripts/check-terraform-import-plan.py plan.json --environment dev \
--allow-update-address module.environment.aws_iam_instance_profile.runtime \
--allow-update-address module.environment.aws_iam_role.runtime \
--allow-update-address module.environment.aws_iam_role.github_deploy \
--allow-update-address module.environment.aws_iam_role_policy.github_deploy \
--allow-update-address module.environment.aws_secretsmanager_secret.app_config \
--allow-update-address module.environment.aws_elastic_beanstalk_environment.this
```
6. Apply only when every update address is named on the command line and the
plan contains no create, delete, or replacement action. The dev direct ALB
alias remains pinned during this phase and must not update.
The same reviewed change prepares the legacy dev CDK stack for ownership
transfer. Before the Terraform apply, deploy `shoc-backend-deploy-dev` with
`ManageGithubDeployRole=true` so both the role and generated inline-policy
resource carry `Retain`. After Terraform succeeds and live verification passes,
deploy the same reviewed SHA with `ManageGithubDeployRole=false`. This removes
both resources from CloudFormation ownership without deleting them. Never use
`ManageGithubDeployRole=true` again after that transfer.
The reviewed `adoption_complete=true` change updates ownership tags on IAM
roles, instance profiles, and app-config secrets, and narrows the dev role to
the staging-style S3 bucket and application prefix. Elastic Beanstalk
environment tags remain at their imported values. EB accepts an added
`ManagedBy` tag request but can fail the asynchronous service-managed
CloudFormation propagation after Terraform reports success. Terraform still
manages the declared EB settings. Deploy-role descriptions and immutable
`HcpTerraformWorkspace` tags remain unchanged. Read-only AWS APIs retain
`Resource = "*"` only where AWS does not support resource-level permissions.
## POC retained identifiers
The tf-poc HCP workspace stores the exact retained environment ID, app-config
secret ARN, child-zone ID, and certificate ARN declared in
`tf-poc/variables.tf`. The declarative import blocks consumed those identifiers
during the completed transfer. Do not guess or replace them, and do not put
credentials or secret values in HCP variables.
ACM DNS validation remains part of the Terraform-owned certificate resource;
its generated validation record is not a separate ownership target. The public
delegation of `tf-poc.seahaven.com` from `seahaven.com` remains outside this
Terraform state.
## Pinned live identities
- Dev: workspace `shoc-backend-dev`; EB environment `shoc-backend-dev`
(`e-hehnrqjjrt`); .NET 8 AL2023 `3.11.3`; `api.dev.seahaven.com`.
- Staging: workspace `shoc-backend-staging`; EB environment
`shoc-backend-staging` (`e-6c9m4vb62z`); .NET 8 AL2023 `3.11.3`;
`api.staging.seahaven.com`.
The environment roots are intentionally not general-purpose modules. Exact
identifiers make accidental cross-environment reuse fail review and planning.
## Safety invariants
- Auto-apply remains off.
- Org baseline owns final HCP plan/apply permissions and manager tags.
- Every imported Terraform resource has `prevent_destroy`.
- The tf-poc CloudFormation creator path was removed after its no-op import,
controlled update, and retained-resource ownership transfer completed.