# Nova Bootstrap Runbook Phase 4 bootstraps the AWS state backend + the spike runner IAM user for the nova-platform. Two scripts create the infrastructure exactly once; after that, the rotated spike-runner key is used for all platform + CI operations. > **Spike scope (D-025):** onboarding uses a cross-account IAM *role* > (not OIDC). The consumer's CI runner assumes the deploy role via > `sts assume-role` using the platform runner user's static credentials. > Real OIDC federation is the production path, OOS for nova v1.0. ## State backend (D-022) `create_state_backend.py` creates (idempotent): - **S3 bucket** `nova-tfstate--` (versioned) — holds all Terraform state files (`platform/terraform.tfstate`, `spike/ci-vpc/terraform.tfstate`, `spike/microservice//terraform.tfstate`). - **DynamoDB table** `nova-tfstate-locks` — the dedicated Terraform state lock table (NOT `nova-outbox` — the outbox is OOS for nova v1.0). The S3 backend `lock_table` attribute points to this table. The account + region are resolved from the caller's live credentials (`sts:GetCallerIdentity`) — NO hardcoded account ID. A marker file `terraform/bootstrap/.bootstrap_state.json` records the created bucket + table names (gitignored). ## IAM runner (D-026) `create_iam_user.py` creates: - **IAM user** `nova-spike-runner`. - **Inline/managed policy** `nova-spike-runner-policy` from `terraform/bootstrap/spike_runner_policy.json`. The JSON uses `${account_id}` and `${region}` placeholders (NOT hardcoded — D-026); `create_iam_user.py` substitutes the live account ID + region before attaching the policy. - **Initial access key** (printed to stdout; capture or rotate via `rotate_spike_key.sh`). ### Policy scope (D-026) The `spike_runner_policy.json` grants the runner the Terraform-deployable permissions it needs to apply the platform + L2 module stacks: | Service | Granted | Notes | |----------------|---------|-------| | S3 | ✅ | State bucket `nova-tfstate--` | | DynamoDB | ✅ | Lock table `nova-tfstate-locks` (D-022) | | ECS | ✅ | Clusters + services + task definitions | | ECR | ✅ | Repositories + images | | ELB | ✅ | ALBs + target groups + listeners | | IAM | ✅ | Roles + policies (Terraform-managed) | | EC2 | ✅ | VPCs + subnets + SGs + route tables | | CloudFront | ✅ | Distributions | | WAF | ✅ | Web ACLs | | KMS | ✅ | Customer-managed keys + aliases | | Lambda | ❌ DROP | Platform Lambda OOS (D-023) | | Secrets Mgr | ❌ DROP | OOS | | SNS | ❌ DROP | OOS | | CostExplorer | ❌ DROP | OOS | | OIDC provider | ❌ DROP | Onboarding uses assume-role (D-025) | ## NOVA_BOOTSTRAP_AWS_* fallback The bootstrap scripts accept the root-credential pair via the `NOVA_BOOTSTRAP_AWS_*` env vars (never committed, never echoed): ```bash export NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID="" export NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY="" export AWS_DEFAULT_REGION="us-east-1" ``` These are the bootstrap-only credentials (used exactly once to create the state backend + spike runner). The fallback precedence is: 1. `NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID` / `NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY` (bootstrap root key — highest priority). 2. Standard `AWS_*` env vars / `~/.aws/credentials` profile (for re-running scripts later with the rotated runner key). ## Steps 1. **Set the bootstrap root key in env** (never commit, never echo): ```bash export NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID="" export NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY="" export AWS_DEFAULT_REGION="us-east-1" ``` 2. **Create the state backend** (S3 bucket + DynamoDB lock table): ```bash python3 terraform/bootstrap/create_state_backend.py ``` Idempotent; writes `terraform/bootstrap/.bootstrap_state.json` marker. 3. **Create the IAM user + scoped policy + initial key**: ```bash python3 terraform/bootstrap/create_iam_user.py ``` Prints `NOVA_AWS_ACCESS_KEY_ID=<...>` + `NOVA_AWS_SECRET_ACCESS_KEY=<...>` to stdout (capture if you want the initial key; rotate it before use). 4. **Rotate the spike key** (creates a new key, deactivates+deletes old, writes the new key to gitignored `.env.secrets`): ```bash bash scripts/rotate_spike_key.sh ``` 5. **Verify** (manual): confirm the caller identity is `nova-spike-runner` (not root); the S3 bucket + DynamoDB table + IAM user + scoped policy all exist; `.env.secrets` + `.bootstrap_state.json` are gitignored. 6. **MANUAL:** rotate/deactivate the **root** key in the AWS IAM console (the user does this, not the script). The bootstrap root key has now served its one-shot purpose; the spike uses the rotated `nova-spike-runner` key for all subsequent operations. ## Onboarding (D-025) Consumer onboarding is handled by `terraform/onboarding/main.tf`, which creates a per-consumer IAM **role** (not a user) with a trust policy allowing the platform runner user to assume it via `sts:AssumeRole` (cross-account assume-role pattern). NO OIDC. See the onboarding root for variable documentation (`consumer_repo`, `owner_id`, `account_id`, `region`, `runner_user_arn`).