|
| 1 | +# Imaging CDK stack |
| 2 | + |
| 3 | +Provisions the AWS resources `docs/IMAGING.md`'s "Required AWS resources" |
| 4 | +table specifies for `server/`'s `S3ImagingObjectStore`: a private, |
| 5 | +SSE-KMS-encrypted S3 bucket; a customer-managed KMS key; a CloudFront |
| 6 | +distribution fronting the bucket via Origin Access Control with a trusted |
| 7 | +signing key group; and a least-privilege IAM role for whatever compute runs |
| 8 | +`server/`. Read that table before changing anything here — this stack is a |
| 9 | +direct translation of it, not an independent design. |
| 10 | + |
| 11 | +**What this stack does *not* do**: it does not deploy `server/` itself (no |
| 12 | +ECS service/task definition, no compute). It hands back |
| 13 | +`ImagingServerTaskRoleArn` as an output — attach that role to whichever |
| 14 | +compute you actually run `server/` on (ECS task role, EC2 instance profile, |
| 15 | +etc.). It also does not touch anything outside this one CloudFormation |
| 16 | +stack — no VPC, no networking, no other ModelForge infrastructure. |
| 17 | + |
| 18 | +**Not run against a real AWS account from this repository** — `npx cdk |
| 19 | +synth` was used locally to confirm the stack synthesizes correctly (a fake |
| 20 | +account/region and a throwaway test keypair, both discarded); `cdk diff` |
| 21 | +and `cdk deploy` need your real AWS credentials and account, and are yours |
| 22 | +to run. |
| 23 | + |
| 24 | +## 1. Generate the signing key pair (offline, outside CDK) |
| 25 | + |
| 26 | +The private half of the CloudFront signing key must never be generated by, |
| 27 | +or pass through, CDK/CloudFormation — it goes straight from `openssl` to |
| 28 | +the server's own secret storage (`IMAGING_CLOUDFRONT_PRIVATE_KEY`), and |
| 29 | +CDK only ever sees the public half. |
| 30 | + |
| 31 | +```bash |
| 32 | +openssl genrsa -out imaging-signing-key.pem 2048 |
| 33 | +openssl rsa -pubout -in imaging-signing-key.pem -out imaging-signing-key.pub.pem |
| 34 | +``` |
| 35 | + |
| 36 | +- `imaging-signing-key.pub.pem` — passed to this stack (see below). Safe to |
| 37 | + keep around; it's public. |
| 38 | +- `imaging-signing-key.pem` — the private half. Base64-encode it |
| 39 | + (`base64 -w0 imaging-signing-key.pem`) and set that as the server's |
| 40 | + `IMAGING_CLOUDFRONT_PRIVATE_KEY`. **Never commit either file** — the |
| 41 | + repo's `.gitignore` already excludes `infra/imaging-cdk/*.pem`, so |
| 42 | + generating them directly in this directory (as the commands above do) is |
| 43 | + covered automatically; confirm with `git check-ignore -v` if you generate |
| 44 | + them elsewhere instead. |
| 45 | + |
| 46 | +## 2. Install and bootstrap |
| 47 | + |
| 48 | +```bash |
| 49 | +npm ci |
| 50 | +npx cdk bootstrap aws://<account-id>/<region> # once per account+region |
| 51 | +``` |
| 52 | + |
| 53 | +## 3. Review and deploy |
| 54 | + |
| 55 | +```bash |
| 56 | +npx cdk diff \ |
| 57 | + -c signingPublicKeyPath=/absolute/path/to/imaging-signing-key.pub.pem \ |
| 58 | + -c bucketNamePrefix=modelforge-imaging-prod |
| 59 | + |
| 60 | +npx cdk deploy \ |
| 61 | + -c signingPublicKeyPath=/absolute/path/to/imaging-signing-key.pub.pem \ |
| 62 | + -c bucketNamePrefix=modelforge-imaging-prod |
| 63 | +``` |
| 64 | + |
| 65 | +`bucketNamePrefix` defaults to `modelforge-imaging`; the stack appends |
| 66 | +`-<account>-<region>` to keep the final bucket name globally unique. |
| 67 | +`signingPublicKeyPath` defaults to `./imaging-signing-key.pub.pem` (relative |
| 68 | +to wherever you run the CDK CLI from). |
| 69 | + |
| 70 | +## 4. Map the outputs onto server config |
| 71 | + |
| 72 | +`cdk deploy` prints these as stack outputs; `docs/IMAGING.md`'s |
| 73 | +"Configuration" section names the exact env vars each one fills: |
| 74 | + |
| 75 | +| Stack output | Env var | |
| 76 | +|---|---| |
| 77 | +| `ImagingS3Bucket` | `IMAGING_S3_BUCKET` | |
| 78 | +| `ImagingS3KmsKeyId` | `IMAGING_S3_KMS_KEY_ID` | |
| 79 | +| `ImagingS3Region` | `IMAGING_S3_REGION` | |
| 80 | +| `ImagingCloudFrontDomain` | `IMAGING_CLOUDFRONT_DOMAIN` | |
| 81 | +| `ImagingCloudFrontKeyPairId` | `IMAGING_CLOUDFRONT_KEY_PAIR_ID` | |
| 82 | +| — (step 1's private key, base64) | `IMAGING_CLOUDFRONT_PRIVATE_KEY` | |
| 83 | +| `ImagingServerTaskRoleArn` | attach to server/'s compute; not an env var | |
| 84 | + |
| 85 | +`IMAGING_S3_KEY_PREFIX` isn't a stack output — it's your own choice of |
| 86 | +object-key namespace within the bucket, not an AWS resource. |
| 87 | + |
| 88 | +See `.env.production.example` at the repo root for where these land in a |
| 89 | +real deployment's environment file. |
| 90 | + |
| 91 | +## Teardown |
| 92 | + |
| 93 | +`cdk destroy` removes the CloudFront distribution and IAM role, but the S3 |
| 94 | +bucket, log bucket, and KMS key all use `RemovalPolicy.RETAIN` deliberately |
| 95 | +(this stack holds PHI-bearing imaging data and its encryption key) — delete |
| 96 | +those by hand, deliberately, only after confirming what's actually in the |
| 97 | +bucket. A KMS key scheduled for deletion has a mandatory 7–30 day waiting |
| 98 | +window before AWS actually deletes it. |
0 commit comments