|
| 1 | +# Infrastructure |
| 2 | + |
| 3 | +Terraform-managed AWS infrastructure for the CUSP Urban Observatory static site. |
| 4 | + |
| 5 | +## Architecture |
| 6 | + |
| 7 | +``` |
| 8 | + ┌─────────────┐ |
| 9 | + │ Route53 │ |
| 10 | + │ DNS Zones │ |
| 11 | + └──────┬──────┘ |
| 12 | + │ A/AAAA/CNAME |
| 13 | + ▼ |
| 14 | +┌──────────┐ ┌──────────────┐ ┌────────────┐ |
| 15 | +│ ACM │──TLS──│ CloudFront │──OAC──│ S3 Bucket │ |
| 16 | +│ Cert │ │ Distribution │ │ (private) │ |
| 17 | +└──────────┘ └──────────────┘ └────────────┘ |
| 18 | + │ │ |
| 19 | + URL rewrite Security headers |
| 20 | + (index.html) (HSTS, CSP, etc.) |
| 21 | +``` |
| 22 | + |
| 23 | +- **S3** — Private bucket (`cuspuo-site`), accessible only via CloudFront OAC |
| 24 | +- **CloudFront** — CDN with TLS 1.2+, HTTP→HTTPS redirect, security headers, custom 404 page |
| 25 | +- **CloudFront Function** — Rewrites directory paths (`/path/` → `/path/index.html`) |
| 26 | +- **ACM** — TLS certificate with DNS validation for all active domains |
| 27 | +- **Route53** — DNS zones for `cuspuo.org` and `cuspuo.com` with A, AAAA, CNAME, and CAA records |
| 28 | +- **IAM** — GitHub Actions OIDC federation (no static credentials), separate deploy and terraform roles with permissions boundaries |
| 29 | + |
| 30 | +## Active Domains |
| 31 | + |
| 32 | +| Domain | DNS Provider | Status | |
| 33 | +|--------|-------------|--------| |
| 34 | +| cuspuo.org | Route53 | Active | |
| 35 | +| www.cuspuo.org | Route53 | Active | |
| 36 | +| cuspuo.com | Route53 | Active | |
| 37 | +| www.cuspuo.com | Route53 | Active | |
| 38 | +| muonetwork.com | GoDaddy | Deferred | |
| 39 | +| muonetwork.org | GoDaddy | Deferred | |
| 40 | + |
| 41 | +To enable the GoDaddy domains, set `external_domains = ["muonetwork.com", "muonetwork.org"]` in `variables.tf` and follow the output instructions for manual DNS configuration. |
| 42 | + |
| 43 | +## CI/CD Workflows |
| 44 | + |
| 45 | +| Workflow | Trigger | Action | |
| 46 | +|----------|---------|--------| |
| 47 | +| **Deploy Site** | Push to `master` (non-infra files) | S3 sync + CloudFront invalidation | |
| 48 | +| **Terraform Plan** | PR with `infra/**` changes | Plan + Infracost cost estimate posted to PR | |
| 49 | +| **Terraform Apply** | Push to `master` with `infra/**` changes | Auto-apply (no manual approval) | |
| 50 | + |
| 51 | +## Prerequisites |
| 52 | + |
| 53 | +- **AWS CLI** with profile `muon` configured for account `217832331713` |
| 54 | +- **Terraform** >= 1.5.0 |
| 55 | + |
| 56 | +## Local Usage |
| 57 | + |
| 58 | +All local terraform commands require the `muon` AWS profile: |
| 59 | + |
| 60 | +```bash |
| 61 | +cd infra |
| 62 | + |
| 63 | +# Set profile for the session |
| 64 | +export AWS_PROFILE=muon |
| 65 | + |
| 66 | +# Standard workflow |
| 67 | +terraform init |
| 68 | +terraform plan |
| 69 | +terraform apply |
| 70 | +``` |
| 71 | + |
| 72 | +## State Management |
| 73 | + |
| 74 | +- **State bucket:** `cuspuo-terraform-state` (S3, versioned, encrypted) |
| 75 | +- **Lock table:** `cuspuo-terraform-lock` (DynamoDB) |
| 76 | +- **State key:** `cuspuo-site/terraform.tfstate` |
| 77 | + |
| 78 | +## Terraform Files |
| 79 | + |
| 80 | +| File | Purpose | |
| 81 | +|------|---------| |
| 82 | +| `providers.tf` | AWS provider config and S3 backend | |
| 83 | +| `variables.tf` | Input variables (domains, bucket names, GitHub config) | |
| 84 | +| `locals.tf` | Computed values (domain lists, aliases) | |
| 85 | +| `state.tf` | State bucket and DynamoDB lock table | |
| 86 | +| `s3.tf` | Site bucket with OAC policy | |
| 87 | +| `acm.tf` | TLS certificate and DNS validation | |
| 88 | +| `cloudfront.tf` | CDN distribution, URL rewrite function, security headers | |
| 89 | +| `iam.tf` | OIDC provider, deploy/terraform roles, permissions boundaries | |
| 90 | +| `route53.tf` | DNS records (A, AAAA, CNAME, CAA) | |
| 91 | +| `outputs.tf` | Distribution ID, role ARNs, GoDaddy instructions | |
| 92 | + |
| 93 | +## IAM Roles |
| 94 | + |
| 95 | +| Role | Purpose | Trust | |
| 96 | +|------|---------|-------| |
| 97 | +| `cuspuo-github-deploy` | S3 sync + CloudFront invalidation | `master` branch only | |
| 98 | +| `cuspuo-github-terraform` | Infrastructure management | `master` branch, PRs, and `production-infra` environment | |
| 99 | + |
| 100 | +Both roles use OIDC federation (no static AWS keys) and have permissions boundaries to prevent privilege escalation. |
| 101 | + |
| 102 | +## Bootstrap from Scratch |
| 103 | + |
| 104 | +If you ever need to recreate the infrastructure from zero: |
| 105 | + |
| 106 | +1. Comment out the `backend "s3"` block in `providers.tf` |
| 107 | +2. Run `terraform init` (uses local state) |
| 108 | +3. Run `terraform apply -target=aws_s3_bucket.state -target=aws_s3_bucket_versioning.state -target=aws_s3_bucket_server_side_encryption_configuration.state -target=aws_s3_bucket_public_access_block.state -target=aws_dynamodb_table.state_lock` |
| 109 | +4. Uncomment the backend block |
| 110 | +5. Run `terraform init -migrate-state` to move state to S3 |
| 111 | +6. Run `terraform apply` for the full infrastructure |
0 commit comments