Cloud Infrastructure Architect

Design a scalable, secure, cost-optimized cloud architecture with IaC, diagrams, and a phased rollout plan.

// prompt
You are a senior cloud infrastructure architect with deep, hands-on expertise across {{cloud_provider}} and certified experience in the Well-Architected Framework. Design a production-grade architecture for the workload below, balancing reliability, security, performance, and cost. ## Context I'm Providing - **Workload:** {{workload_description}} - **Primary cloud:** {{cloud_provider}} - **Region(s):** {{target_regions}} - **Expected scale:** {{traffic_and_data_volume}} - **Availability target:** {{sla_or_uptime_goal}} - **Budget ceiling:** {{monthly_budget}} - **Compliance / security needs:** {{compliance_requirements}} - **IaC tool:** {{iac_tool}} ## How to Work Through It 1. **Clarify first.** List any assumptions you must make, and ask up to 3 blocking questions only if essential. 2. **Propose the architecture.** Define compute, storage, networking, data, and identity layers. Justify each major service choice against the requirements above. 3. **Engineer for resilience.** Specify multi-AZ/region strategy, autoscaling rules, backups, and a concrete disaster-recovery plan (state RTO/RPO). 4. **Secure by design.** Cover IAM least-privilege, network segmentation, encryption in transit and at rest, secrets management, and how the design meets {{compliance_requirements}}. 5. **Optimize cost.** Recommend sizing, reserved/spot/savings options, and flag the top 3 cost risks with mitigations. 6. **Plan the rollout.** Give a phased implementation roadmap with milestones and rollback strategy. ## Deliver Your Response As - **Architecture overview** — a text-based diagram (components and data flow) plus a short narrative. - **Service breakdown table** — | Layer | Service | Purpose | Why chosen |. - **{{iac_tool}} starter** — a representative, well-commented configuration snippet in a fenced code block. - **Security checklist** — concrete controls mapped to requirements. - **Estimated monthly cost** — itemized, with assumptions stated. - **Implementation roadmap** — phased, with dependencies. Use clear headings and keep recommendations specific and actionable. Note any trade-offs explicitly rather than hiding them.
Fill in the variables

Example run

Generated by Claude Opus 5.5 on September 23, 2026

Inputs used

Cloud provider
AWS
Workload description
We're building "VerdantConnect," a telehealth platform for Verdant Health Technologies that lets patients book appointments and have live video consultations with licensed clinicians, and lets providers manage schedules, write encounter notes, and issue e-prescriptions. Core components: - Patient-facing web app (React) and mobile apps (React Native, iOS/Android) for browsing providers, booking/rescheduling appointments, joining video visits, and viewing visit summaries. - Provider web dashboard (React) for calendar management, video consultations, clinical notes (structured encounter forms), and e-prescription submission via a third-party e-prescribing API (Surescripts-compatible). - Real-time video consultation service using WebRTC with a managed SFU (media relay) for 1:1 video/audio visits, including screen share for reviewing lab results. - Appointment scheduling and notification service (email/SMS reminders via a third-party provider) with calendar sync (Google/Outlook via OAuth). - REST/GraphQL API backend (Node.js/TypeScript microservices) handling auth, scheduling, clinical notes, billing, and notifications. - Payment processing for co-pays and self-pay visits via a PCI-compliant third-party gateway (tokenized, no raw card data stored by us). - Integration layer that syncs appointment and encounter data with partner clinics' EHR systems via HL7/FHIR APIs. - Background jobs for reminder scheduling, post-visit summary generation, and nightly reconciliation with the payment gateway. - Admin/ops console for support staff to manage user accounts, refunds, and audit logs. The system stores protected health information (PHI): patient demographics, appointment history, encounter notes, and prescription records. Video sessions are not recorded or stored. We're a Series B startup with ~40 engineers, currently at 18,000 monthly active patients and 650 active clinicians, growing roughly 12% month over month.
Target regions
Primary: us-east-1 (N. Virginia); DR/failover: us-west-2 (Oregon)
Traffic and data volume
~18,000 MAU patients, 650 active clinicians; ~9,000 video visits/month, avg 20 min each; API traffic averages 120 req/s with peaks to 900 req/s during 8-10am and 5-7pm local business hours; Postgres primary dataset ~450 GB and growing ~25 GB/month; S3 object storage (encounter attachments, lab PDFs) ~2 TB, growing ~150 GB/month; daily HL7/FHIR sync batches of ~15,000 records with partner clinics.
Sla or uptime goal
99.95% uptime for core booking and video services (max ~22 minutes downtime/month), measured monthly, with a target RTO of 30 minutes and RPO of 5 minutes for PHI data stores
Monthly budget
$18,000 USD/month for infrastructure (excludes third-party SaaS fees for SMS/email, e-prescribing, and payment gateway)
Compliance requirements
Must be HIPAA-compliant (we are a covered entity's business associate) and undergo annual SOC 2 Type II audit; targeting first SOC 2 report by Q2 2027. Specific requirements: - All PHI encrypted at rest (AES-256) and in transit (TLS 1.2+) end to end, including internal service-to-service traffic. - Signed Business Associate Agreements (BAAs) required with AWS and every third-party subprocessor (e-prescribing, SMS/email, video/SFU) that touches PHI or PII. - Detailed audit logging of all PHI access (who accessed which patient record, when, from where), retained for a minimum of 6 years per HIPAA requirements, with tamper-evident storage. - Role-based access control enforcing least privilege between patient, clinician, and admin/support roles; break-glass access procedure for emergency support cases, fully logged. - Data residency: all PHI must remain within US regions (no cross-border replication). - Automatic session timeout (15 min inactivity) and MFA required for all clinician and admin accounts. - Formal incident response plan with breach notification procedures meeting the HIPAA Breach Notification Rule (60-day notification window). - Annual third-party penetration test and quarterly vulnerability scans required for SOC 2 evidence. - Secrets (API keys, DB credentials, e-prescribing certs) must be stored in a managed secrets manager, never in code or environment files committed to source control.
Iac tool
Terraform

VerdantConnect: Production Architecture on AWS (HIPAA / SOC 2)

Summary. The design runs in us-east-1 as the active region with a warm standby in us-west-2. It uses:

  • ECS on Fargate (Graviton) for the microservices.
  • Aurora PostgreSQL Global Database for PHI.
  • Amazon Chime SDK as the managed SFU. It is covered by the AWS BAA, so it adds no new subprocessor.
  • Cognito for customer identity.
  • An immutable, Object-Locked audit archive in a dedicated Log Archive account.

Steady-state cost is about $11.3k/month with commitments, or about $12.5k on-demand during the first 2–3 months. That leaves roughly 37% headroom under the $18k ceiling today. At 12% month-over-month growth, the ceiling is reached around months 9–10. Section 7 covers this, and it is the most important business finding in this document.


1. Assumptions and Blocking Questions

Blocking questions

I have proceeded with the defaults shown in brackets. Please confirm or correct them.

  1. The SLO and the regional RTO conflict. A 99.95% monthly target allows about 21.6–22.3 minutes of downtime. Your regional RTO is 30 minutes. A single regional failover therefore breaks the SLO for that month.
    • Do declared regional disasters count against the SLO?
    • [Default: the SLO covers AZ-level and component failures. Regional disasters are governed by the RTO/RPO and excluded from the SLO. Meeting 99.95% through a regional loss would require active-active, adding roughly $4–6k/month and substantial engineering work on data consistency.]
  2. Current state. Is the platform greenfield, or are the 18k MAU running live somewhere today? If live, where, and on which database?
    • [Default: a live system exists on a single-region Postgres-compatible stack, and we migrate it using logical replication with a reverse-replication rollback path.]
  3. Workforce identity. Do you have a corporate IdP (Okta, Entra ID, or Google Workspace) for support/admin staff? Do any partner clinics want clinicians to sign in through their own IdP?
    • [Default: staff federate from a corporate IdP. Clinicians use a Verdant-managed Cognito pool with passkeys or TOTP. Partner-clinic SAML/OIDC federation is added per clinic.]

Assumptions

# Assumption
A1 The AWS BAA is accepted at the Organization level via AWS Artifact. PHI is processed only by HIPAA-eligible services, and this is enforced by SCPs and a service allowlist.
A2 Amazon Chime SDK is acceptable as the managed SFU. The Amazon Chime end-user app was retired in Feb 2026, but the Chime SDK is a separate service and is unaffected. Confirm the roadmap with your AWS account team. A video-svc adapter keeps a switch to Daily, Vonage, or Zoom Video SDK (all offer BAAs) feasible.
A3 Partner EHR integration uses FHIR R4 REST over HTTPS (OAuth2 / SMART Backend Services, optionally mTLS). Any HL7v2-over-MLLP partner needs a Site-to-Site VPN, about $36/month per connection, which is not included in the estimate.
A4 Patients, clinicians, and staff are in the US. Traffic peaks follow US business hours across time zones, so the "8–10am" peak rolls from about 08:00 ET to 13:00 ET.
A5 Payments use gateway-hosted fields or an iframe, which keeps PCI scope at SAQ A. No PHI (diagnosis, visit reason) is sent in payment metadata.
A6 Post-visit summaries are template-based. If an LLM is introduced, use Amazon Bedrock in US regions under the AWS BAA. This is not costed.
A7 Prices are approximate us-east-1/us-west-2 public list prices as of mid-2026, ±15%. Validate them in the AWS Pricing Calculator before committing.
A8 The $18k budget includes non-prod and AWS Business Support. It excludes SaaS such as compliance automation (Vanta/Drata), APM, pen-test fees, and SMS/email/e-Rx/payments.
A9 A platform team of 4–5 of the 40 engineers owns Terraform, CI/CD, and operations. That team size is why I chose ECS over EKS.
A10 Non-production environments contain synthetic data only (no PHI). This is both a control and a cost lever.

2. Architecture Overview

2.1 Component and data-flow diagram

 Patients: Web (React) / iOS / Android     Clinicians: Web (React)      Support/Admin staff
        │                                          │                             │
        │ static assets only (no PHI)              │                             │ AWS Verified Access
        ▼                                          │                             │ (corp IdP + MFA + device posture)
 ┌─────────────────────┐                           │                             │
 │ CloudFront + S3     │  SPA bundles, geo-restricted to US                      │
 └─────────────────────┘                           │                             │
        │  api.verdantconnect.com  (Route 53 failover records, TTL 60s)          │
        ▼                                          ▼                             ▼
┌────────────────────────────── us-east-1  (ACTIVE, 3 AZs) ──────────────────────────────────┐
│  AWS WAF ──► Public ALB (TLS 1.3/1.2 policy, ACM)          Internal ALB (admin) ◄── VA     │
│                 │ HTTPS to targets                                  │                      │
│  ┌──────────────▼───────── ECS on Fargate (Graviton), private app subnets ─────────────┐   │
│  │ api-gateway / BFF (GraphQL + REST, session validation, authZ policy engine)          │   │
│  │     │  ECS Service Connect with TLS (AWS Private CA, short-lived certs)               │   │
│  │     ├── identity-svc ───────── Cognito: patient pool | clinician pool                │   │
│  │     ├── scheduling-svc ─────── Postgres exclusion constraints (no double-booking)    │   │
│  │     ├── clinical-svc (notes, Rx) ───► e-Rx vendor (Surescripts; mTLS via NAT EIPs)   │   │
│  │     ├── video-svc ──────────── Chime SDK Meetings API (control plane only)           │   │
│  │     ├── billing-svc ────────── Payment gateway (tokens only, SAQ A)                  │   │
│  │     ├── notify-svc ─────────── SMS/email vendor (BAA; minimum-necessary content)     │   │
│  │     ├── calendar-svc ───────── Google/Microsoft Graph (OAuth; events contain no PHI) │   │
│  │     ├── integration-svc ◄──── Step Functions (batched) ───► Partner EHR FHIR APIs    │   │
│  │     ├── admin-svc (reachable only via internal ALB + Verified Access)                │   │
│  │     └── workers (SQS consumers; Fargate + Fargate Spot): reminders, summaries, recon │   │
│  └──────────────────────────────────────────────────────────────────────────────────────┘   │
│        │TLS verify-full         │TLS              │TLS             │ VPC endpoints (HTTPS)   │
│  ┌─────▼──────────────┐  ┌──────▼──────────┐  ┌───▼───────────┐  ┌▼──────────────────────┐ │
│  │ Aurora PostgreSQL  │  │ ElastiCache     │  │ DynamoDB      │  │ S3 PHI bucket         │ │
│  │ writer + reader    │  │ Valkey (cache,  │  │ global table  │  │ (attachments, labs)   │ │
│  │ Multi-AZ, pgAudit  │  │ rate limits)    │  │ (sessions)    │  │ SSE-KMS, versioned    │ │
│  └─────┬──────────────┘  └─────────────────┘  └──────┬────────┘  └──────┬────────────────┘ │
│        │                                             │                  │                  │
│  App audit events ──► Firehose (Parquet) ──► S3 Object Lock (Log Archive acct, 7 yr) ──┐   │
│  CloudTrail (org) / VPC Flow Logs / pgAudit ─────────────────────────────────────────┘   │
└────────┼─────────────────────────────────────────────┼──────────────────┼────────────────┘
         │ Aurora Global (storage-level, <1s typical)   │ global table      │ S3 CRR + RTC
┌────────▼──────────────── us-west-2  (WARM STANDBY) ───▼──────────────────▼────────────────┐
│ Aurora secondary cluster (1 × r7g.2xlarge, promotable)   DynamoDB replica   S3 replica     │
│ ECS services at minimum capacity, ALB + WAF, Valkey (small), Cognito shadow pools          │
│ AWS Backup vault (Vault Lock) with cross-region snapshot copies; audit archive replica     │
└───────────────────────────────────────────────────────────────────────────────────────────┘

Media path (never enters our VPC):
 Patient app ⇄ DTLS-SRTP ⇄ Chime SDK media (MediaRegion pinned to us-east-1 or us-west-2) ⇄ Clinician

2.2 Narrative

  • Edge. Static SPA bundles are served from CloudFront. API traffic deliberately bypasses CloudFront and resolves through Route 53 failover records to a regional ALB protected by WAF.
    • This keeps PHI-bearing traffic on US regional endpoints, so there are no edge-location questions for auditors.
    • The trade-off is losing edge-level DDoS absorption. That is covered by Shield Standard, WAF rate-based rules, and autoscaling.
  • API layer.
    • A BFF/gateway service terminates user sessions. Web clients use an HttpOnly, Secure, SameSite=strict cookie; mobile clients use an opaque token.
    • The gateway enforces the 15-minute idle timeout server-side and runs authorization through an in-process Cedar/OPA policy engine.
    • It then calls domain services over Service Connect TLS.
  • Sessions. Cognito handles authentication events only (sign-in, MFA, password reset). Our own session record lives in a DynamoDB global table, so active sessions survive a regional failover.
  • Video.
    • video-svc creates Chime SDK meetings and attendees with pseudonymous IDs. No names, reasons for visit, or other PHI go into meeting metadata.
    • Clients connect directly to Chime media.
    • Nothing is recorded, and media pipelines/capture are disabled by IAM policy.
  • Scheduling and reminders.
    • Appointments are stored in Postgres with a tstzrange exclusion constraint (btree_gist), so double-booking is impossible even under concurrency.
    • Reminders use a DB outbox table driven by a 1-minute EventBridge Scheduler tick. They do not use per-appointment schedules, which are regional resources and would complicate DR.
  • Integration.
    • Daily FHIR batches (~15k records) run as one Step Functions execution per partner, with a Map state over chunks of about 500 records. Per-record executions would cost about 10× more.
    • Outbound traffic uses static NAT EIPs so partners and the e-Rx vendor can allowlist us.
  • Audit. Every PHI read or write emits a structured audit event. Events go through Firehose to Parquet in an Object-Locked (COMPLIANCE mode) bucket in a separate Log Archive account, queryable with Athena.

3. Service Breakdown

Layer Service Purpose Why chosen
Governance AWS Organizations + Control Tower Accounts: Mgmt, Security Tooling, Log Archive, Backup, Shared Services, Prod, Staging, Dev, Sandbox Blast-radius isolation, org-wide BAA acceptance, SCP guardrails. Separate audit and backup accounts make evidence tamper-resistant.
Edge (static) CloudFront + S3 (OAC) Serves React SPA bundles Cheap and fast. No PHI, so no residency concern.
Edge (API) Route 53 failover + AWS WAF + ALB Regional API ingress, TLS 1.2+/1.3, OWASP and rate rules Keeps PHI traffic regional. WAF managed rules and rate limits provide SOC 2 evidence.
Admin access AWS Verified Access Zero-trust access to the admin console (IdP + device posture) Removes VPN and bastions. Every request is logged per identity.
Compute ECS on Fargate (Graviton/ARM64) All Node.js/TS microservices and workers No node patching (less SOC 2 surface), per-task isolation, about 20% cheaper on ARM. The team is too small to justify EKS operations.
Service-to-service ECS Service Connect + AWS Private CA (short-lived mode) TLS for east-west traffic, service discovery Meets "TLS everywhere" without a self-managed mesh. App Mesh reaches end of support on Sep 30, 2026, so it is excluded.
Identity (customers) Amazon Cognito: separate patient and clinician pools Sign-up, sign-in, MFA, passkeys HIPAA-eligible under the AWS BAA and inexpensive. Separate pools isolate policies: clinicians have mandatory MFA; patients can use passkeys or optional MFA.
Identity (workforce) IAM Identity Center + corporate IdP AWS console/CLI and admin console SSO Central joiner/mover/leaver process, which is core SOC 2 access-review evidence.
Authorization In-process Cedar (or OPA) + Postgres RLS RBAC plus relationship-based access (a clinician sees only their patients) Amazon Verified Permissions at our request volume would cost roughly $1k+/month. An embedded engine costs nothing. RLS adds defense in depth.
Sessions DynamoDB global table (on-demand) Server-side sessions, 15-min idle tracking, revocation Multi-region replication means sessions survive DR. Pennies at this scale.
Primary data Aurora PostgreSQL Global Database (r7g) Scheduling, notes, prescriptions, billing records Multi-AZ failover in about 30–60s, cross-region lag typically under 1s (fits the 5-min RPO), managed global failover, and a global writer endpoint.
Cache ElastiCache for Valkey (TLS, AUTH, CMK) Read cache, rate limiting, idempotency keys Non-authoritative, so no RPO is needed. Valkey is about 20% cheaper than Redis OSS.
Objects S3 (SSE-KMS, versioning, CRR + RTC) + GuardDuty Malware Protection for S3 Encounter attachments, lab PDFs Presigned uploads, uploads scanned before release, replicated to us-west-2.
Video Amazon Chime SDK Meetings WebRTC SFU for 1:1 video/audio and screen share Covered by the AWS BAA (no new subprocessor), US media region pinning, DTLS-SRTP, about $0.0017 per attendee-minute.
Async SQS (+DLQ), EventBridge Scheduler, Step Functions Reminders, summaries, reconciliation, FHIR batches Serverless, HIPAA-eligible, idempotent consumers.
Egress control NAT GW (static EIPs) + Route 53 Resolver DNS Firewall Vendor allowlisting, domain-level egress allowlist DNS Firewall provides about 80% of the exfiltration protection of Network Firewall for about 5% of the cost. See trade-offs.
Private AWS access VPC interface and gateway endpoints KMS, Secrets Manager, ECR, STS, SQS, Logs, Firehose, S3, DynamoDB Keeps AWS API traffic off the internet and enables data-perimeter policies (aws:SourceVpce).
Keys AWS KMS multi-Region CMKs (per data domain) Encryption at rest (AES-256-GCM) and envelope encryption Separation of duties per domain. Multi-Region keys make ciphertext usable in DR.
Secrets AWS Secrets Manager (CMK, replicated, rotation) DB credentials, API keys, e-Rx certificates A managed secrets store as required. Rotation Lambdas. Replicas for DR.
Audit Kinesis Data Firehose → S3 Object Lock (Log Archive) + Athena; CloudTrail org trail; pgAudit PHI access trail, AWS API trail, DB trail WORM storage (COMPLIANCE mode) plus signed digests give tamper evidence for 7 years.
Detection GuardDuty, Security Hub, Config (HIPAA conformance pack), Inspector, Macie, IAM Access Analyzer, Audit Manager Threat detection, posture, vulnerability scanning, PHI discovery, evidence Continuous controls and evidence for SOC 2 CC6/CC7 and the HIPAA §164.308 risk management requirements.
Resilience AWS Backup (Vault Lock, cross-account + cross-region), restore testing Immutable backups against ransomware and logical corruption Global DB replicates corruption too; locked backups do not.
Observability CloudWatch (Logs data-protection masking), ADOT/X-Ray Metrics, traces, PHI-masked logs HIPAA-eligible and native. Masking policies catch accidental PHI in logs.
CI/CD GitHub Actions (OIDC → IAM) + ECR + Terraform Build, scan, deploy; IaC No long-lived AWS keys. Checkov/Trivy/CodeQL gates. ECS blue/green with alarm-based rollback.

4. Resilience and Disaster Recovery

4.1 Availability design (within the region)

  • 3 AZs for ALB, ECS tasks (minimum one per AZ for critical services), NAT gateways, and endpoints.
  • Aurora: writer plus a same-size reader in a different AZ (promotion tier 0), with auto-scaled readers 0–2 for peaks. Failover is typically 30–60 seconds, and clients reconnect using the cluster endpoint with retry and backoff.
  • Dependency budget.
    • Several dependencies publish SLAs below 99.95%; Cognito's is 99.9%.
    • To reduce exposure, the BFF session design means existing users are unaffected by a Cognito brownout. Only new sign-ins are affected, and JWKS keys are cached.
    • Video clients have automatic rejoin. A PSTN dial-in fallback (Chime SDK PSTN audio) can be enabled later as a degraded-mode path for visits.
  • Deploy safety: ECS deployment circuit breaker plus CloudWatch-alarm rollback on 5xx rate and p95 latency. Most real-world downtime comes from deploys, not AZ failures.

4.2 Autoscaling rules

Component Min / Max (prod) Scaling signal Notes
api-gateway (BFF) 3 / 30 tasks (1 vCPU, 2 GB) Target tracking: ALBRequestCountPerTarget = 150; CPU 60% Scheduled pre-warm: min 6 at 07:30–13:30 ET and 16:30–22:30 ET on weekdays. Scale-out cooldown 60s, scale-in 300s.
Critical domain services (identity, scheduling, video, clinical) 3 / 20 CPU 60%, memory 70% Spread one per AZ.
Non-critical (billing, calendar, admin) 2 / 10 CPU 60%
Workers 1 / 20 SQS ApproximateNumberOfMessagesVisible per task (target 100) and oldest-message age < 60s Capacity providers: base 1 on-demand, then 70% Fargate Spot. Consumers must be idempotent.
Aurora readers 1 fixed + 0–2 auto Reader CPU 60% or connections Base reader doubles as the failover target.
Valkey Fixed (vertical) Memory > 70% alarm Rebuildable cache.
DR region (steady state) 1 per service (2 for gateway) — Raised to prod minimums by the failover runbook (SSM Automation).

4.3 Backups

  • Aurora: 35-day PITR (continuous). AWS Backup takes daily snapshots (kept 35 days) and monthly snapshots (kept 1 year; longer if your BAA or partner medical-records retention requires it). Snapshots are copied cross-account into the Backup account and cross-region to us-west-2, both with Vault Lock in compliance mode.
  • S3: versioning, CRR to us-west-2, and lifecycle rules that expire noncurrent versions after 90 days (PHI bucket only; audit bucket rules are in the Terraform).
  • DynamoDB: PITR enabled.
  • Restore testing: AWS Backup restore-testing plans run monthly with automated validation queries. The results are SOC 2 A1.2/A1.3 evidence.

4.4 RTO/RPO by component

Component AZ failure (RTO / RPO) Region failure (RTO / RPO) How
Aurora (PHI) ~1 min / 0 ≤ 20 min / typically < 1 s, alarm at 60 s Global DB managed failover (--allow-data-loss for unplanned); alarm on AuroraGlobalDBRPOLag
S3 attachments 0 / 0 ≤ 20 min / typically seconds; SLA 15 min (RTC) CRR + RTC. See the gap noted below.
Sessions (DynamoDB) 0 / 0 0 / ~1 s Global table; active users stay logged in
Audit events 0 / 0 — / ≤ 60 s buffer Firehose buffer set to 60s; the audit archive has its own CRR
Reminders 0 / 0 Same as Aurora DB outbox, idempotent send flag
Video sessions Seconds (client rejoin) ~1–2 min after gateway failover Clients rejoin a new meeting created via the us-west-2 Chime control plane
Cache Rebuild Rebuild Non-authoritative

Trade-off: S3 RPO gap. S3 Replication Time Control guarantees 99.99% of objects replicate within 15 minutes. That is outside the 5-minute RPO in the worst case, although it is typically seconds. Mitigations:

  • Alarm on ReplicationLatency above 300s.
  • A post-failover reconciliation job flags DB rows whose object is missing in the replica, and those uploads are re-requested.
  • Dual-writing uploads to both regions would close the gap but doubles write complexity. I don't recommend it at this stage.

Trade-off: rds.global_db_rpo. This Aurora PostgreSQL parameter can enforce an RPO by pausing commits when secondary lag exceeds a threshold. I do not recommend enabling it. It couples primary write availability to the health of the secondary region, so a us-west-2 problem could stop writes in us-east-1. Use alarms instead.

4.5 Regional failover runbook (target: 30 minutes)

Failover is human-declared, then automated. Automatic regional failover risks flapping and split-brain, and it would cost more than the problem it solves at this stage.

T+ Step Owner / mechanism
0–5 min Detection: synthetic canaries (booking and join-visit flows) from us-west-2, Route 53 health checks, alarms → PagerDuty On-call
5 min Incident Commander declares a regional failover. Criteria: sustained regional impairment over 10 min, or an AWS Health event confirming it. IC
5–12 min failover-global-cluster --allow-data-loss promotes the us-west-2 Aurora cluster. The global writer endpoint follows it. SSM Automation document
5–15 min (parallel) Scale us-west-2 ECS to prod minimums. Enable the west reminder tick. Switch video-svc to the us-west-2 Chime control region. Promote the Cognito shadow pools. Same SSM document
15–18 min Flip Route 53 (health-check inversion / manual failover control). Record TTL is 60s. Runbook step
18–28 min Smoke tests (synthetic bookings, video join, note save, e-Rx sandbox ping). Run the S3 reconciliation job. On-call + automated suite
≤ 30 min Status page updated. Covered-entity partners notified if contractually required. IC / Comms

Identity during regional DR (trade-off). Cognito user pools are regional and cannot export password hashes or MFA secrets.

  • Existing sessions survive because they live in the DynamoDB global table.
  • New sign-ins in us-west-2 go to shadow pools. These are kept in sync for profile attributes via EventBridge, but they carry no credentials.
    • Patients sign in with email or SMS OTP.
    • Clinicians re-verify with email OTP and then must re-enroll a passkey or TOTP before any PHI access.
    • Staff are unaffected because the corporate IdP is itself multi-region.
  • If this degraded path is unacceptable, the alternative is a multi-region CIAM with a BAA, such as Okta Customer Identity (Auth0) at an enterprise tier. That is SaaS spend outside this budget.

DR testing: a quarterly game day. Run the full regional failover in staging each quarter and in production twice a year during a low-traffic window, such as Sunday 02:00 ET. Record measured RTO/RPO as SOC 2 evidence.

Fargate capacity during regional events: many customers fail over at the same time. The warm minimum capacity in us-west-2 and pre-created ALB, WAF, and target groups reduce reliance on burst launches.


5. Security by Design

5.1 Account structure and IAM least privilege

  • Human access goes through IAM Identity Center from the corporate IdP, with phishing-resistant MFA (FIDO2).
    • There are no IAM users except two sealed break-glass users in the management account. Their hardware MFA keys are kept in separate safes, and any use of them triggers an alarm.
  • Permission sets:
    • ReadOnly-Prod explicitly denies the PHI data plane: s3:GetObject on PHI buckets, secretsmanager:GetSecretValue, rds-db:connect, dynamodb:GetItem on sessions, and kms:Decrypt on the PHI key.
    • Deploy is used only by CI through OIDC.
    • PlatformAdmin-Prod is just-in-time, using AWS's open-source TEAM solution (Temporary Elevated Access Management). It requires an approver, a justification, and a session of at most 4 hours.
  • Workload roles: one ECS task role per service, scoped to specific resource ARNs, with a permissions boundary on all roles that CI creates.
  • Data perimeter:
    • PHI bucket policies allow only named task roles via aws:SourceVpce.
    • VPC endpoint policies restrict to aws:ResourceOrgID, which blocks exfiltration to buckets in other accounts.
    • KMS key policies grant use only to specific roles.
  • SCPs (org-wide):
    • Deny regions other than us-east-1 and us-west-2 (data residency).
    • Deny disabling CloudTrail, GuardDuty, Config, or Security Hub.
    • Deny creating unencrypted RDS or EBS.
    • Deny IAM user and access-key creation in workload OUs.
    • Deny organizations:LeaveOrganization.
    • Deny non-HIPAA-eligible services in Prod via an allowlist.
  • Database:
    • One Postgres role per service with schema-scoped grants.
    • Row-Level Security on PHI tables using SET LOCAL app.actor_id / app.role.
    • Human DB access is break-glass only: IAM DB authentication through SSM Session Manager port forwarding, fully logged.
    • ecs exec is disabled in prod.

5.2 Network segmentation

  • A VPC per environment per region with three subnet tiers:
    • public: ALB and NAT only.
    • private-app: ECS tasks.
    • private-data: Aurora, Valkey, and interface endpoints. This tier has no route to the NAT gateway.
  • Security groups reference other security groups: ALB SG → gateway SG → service SGs → data SG on specific ports only. NACLs on data subnets deny 0.0.0.0/0.
  • Egress: NAT with static EIPs, plus Route 53 Resolver DNS Firewall in allowlist mode (AWS domains and named vendors: e-Rx, SMS/email, payment, Google/Microsoft calendar APIs, partner FHIR hosts).
    • Trade-off: DNS Firewall does not stop connections made directly to an IP address. AWS Network Firewall does, but costs about $870/month for 3 AZs. Revisit it at SOC 2 year 2 or if pen testing shows the gap.
  • VPC Flow Logs go to S3 as Parquet in the Log Archive account.

5.3 Encryption

At rest. Customer-managed KMS keys use AES-256-GCM, with separate keys per domain (phi, secrets, logs, audit), annual rotation, and multi-Region replicas only to us-west-2. Encrypted with these keys:

  • Aurora and Aurora snapshots
  • S3 (SSE-KMS with bucket keys, which cut KMS request costs by about 99%)
  • DynamoDB, Valkey, SQS, Firehose
  • CloudWatch Logs, Secrets Manager
  • AWS Backup vaults

AWS Config rules detect any drift.

Field-level encryption. The AWS Encryption SDK with data-key caching protects especially sensitive fields:

  • Calendar OAuth refresh tokens
  • Government IDs, if collected
  • Any 42 CFR Part 2 (substance-use treatment) or behavioral-health notes, if applicable

In transit:

Hop Control
Client → ALB ELBSecurityPolicy-TLS13-1-2-2021-06 (TLS 1.3 preferred, 1.2 minimum), HSTS preload, ACM certificates
ALB → tasks HTTPS target groups (task serves TLS with a Private CA–issued certificate)
Service → service Service Connect TLS (Private CA, short-lived certs, automatic rotation)
App → Aurora rds.force_ssl=1, ssl_min_protocol_version=TLSv1.2, client sslmode=verify-full with the RDS CA bundle
App → Valkey / DynamoDB / S3 / KMS TLS; bucket policies deny aws:SecureTransport=false and s3:TlsVersion < 1.2
Video media DTLS-SRTP (Chime SDK)
App → vendors TLS 1.2+, mTLS client certificate for e-Rx and FHIR where supported
Mobile iOS ATS, Android Network Security Config (cleartext disabled)

Trade-off: Service Connect TLS is one-way TLS. It encrypts traffic and authenticates the server, but it does not provide mutual TLS. Caller identity is established by a short-lived, signed internal JWT that carries the service identity and the end-user context. If auditors or pen testers push for workload-identity mTLS, VPC Lattice with IAM auth policies is the managed path, adding roughly $200–300/month.

5.4 Secrets management

  • All secrets live in Secrets Manager: CMK-encrypted, replicated to us-west-2, and injected into ECS through the task definition secrets.valueFrom or fetched with the SDK and cached. Nothing is placed in .env files or Terraform state.
    • Terraform uses ephemeral resources and write-only attributes (see the Terraform starter).
  • Rotation schedule:
    • Service DB credentials every 30 days, using the alternating-users strategy.
    • Vendor API keys every 90 days where the vendor supports it.
    • The e-Rx client certificate and key are stored as a secret, with an alarm 30 days before certificate expiry.
  • Leak prevention: gitleaks pre-commit hooks, GitHub secret scanning with push protection, and Checkov/Trivy in CI. A detected secret fails the build and triggers a rotation runbook.

5.5 Identity, MFA, and session timeout

Control Patients Clinicians Admin/Support
Authentication Cognito: passkey or password + optional TOTP Cognito: mandatory MFA, passkey (WebAuthn) preferred or TOTP; SMS not allowed Corporate IdP (FIDO2) via Verified Access / Identity Center
Session idle timeout 15 min (server-enforced) 15 min (server-enforced) 15 min (Verified Access / IdP + app)
Absolute session lifetime 12 h 12 h 8 h
Adaptive protection Cognito threat protection (Plus tier) on the clinician pool Same IdP risk policies
  • How the 15-minute timeout is enforced.
    • The BFF session record stores last_activity, written at most once per 60 seconds to control DynamoDB cost.
    • Any request after 15 minutes of inactivity is rejected, and the session and its refresh token are revoked.
    • The client shows a 2-minute warning dialog.
  • Edge cases: during an active video visit or note editing, heartbeat pings and keystroke-driven autosave count as activity. Drafts autosave every 30 seconds so a timeout never loses clinical notes.
  • Mobile: after 15 minutes of inactivity or backgrounding, the app locks and requires biometric or PIN re-authentication. Tokens are stored in Keychain/Keystore. FLAG_SECURE and the iOS privacy overlay protect PHI screens.

5.6 RBAC, relationship-based access, and break-glass

  • Roles: patient, clinician, clinic_admin, support_tier1, support_tier2, billing_ops, security_admin.
  • Policy rules (Cedar/OPA, policy-as-code in git, reviewed via PR):
    • A patient may access only their own records.
    • A clinician may access a patient only with an active care relationship: an upcoming or completed appointment within N days, or clinic-level assignment. RLS enforces the same rule in the database.
    • Support staff see account and scheduling metadata by default and not clinical notes.
  • Break-glass for support emergencies:
    1. The support agent requests elevated access for a specific patient with a reason code, free-text justification, and a ticket ID.
    2. A second approver (tier 2 or the on-call security admin) approves. For a declared P1 there is auto-approval with a mandatory review afterward.
    3. A time-boxed grant (60 minutes, one patient) is written to a access_grants table that the policy engine checks.
    4. Every access during the grant carries break_glass=true in the audit event, and a real-time alert goes to #security with IDs only, no PHI.
    5. The Privacy Officer reviews all break-glass sessions weekly. Quarterly reviews are SOC 2 evidence.
  • Infrastructure break-glass uses TEAM JIT roles, with sealed root and emergency IAM credentials as a last resort. Use of either triggers a PagerDuty SEV-2 page to security.

5.7 PHI access audit logging (tamper-evident, retained 6+ years)

Event schema, emitted by a shared audit library used by every service:

{ "event_id":"uuid", "ts":"RFC3339", "actor_id":"...", "actor_role":"clinician",
  "actor_org":"clinic-123", "action":"READ|CREATE|UPDATE|DELETE|EXPORT|PRINT",
  "resource_type":"EncounterNote", "resource_id":"...", "patient_id":"...",
  "purpose":"TREATMENT|PAYMENT|OPERATIONS|BREAK_GLASS", "break_glass":false,
  "source_ip":"...", "user_agent":"...", "geo":"US-VA", "session_id":"...",
  "request_id":"...", "outcome":"ALLOW|DENY", "service":"clinical-svc" }

Delivery path.

  • Events go to Firehose via PutRecordBatch with retries. If Firehose is unavailable, they fall back to an SQS queue.
  • If both fail, the PHI read fails closed. This trade-off favors compliance over availability, and it should be exceedingly rare.
  • For writes, the audit event is written through a transactional outbox in the same DB transaction.

Storage and tamper evidence.

  • Firehose converts events to Parquet, partitioned by date.
  • Records land in the Log Archive account bucket with Object Lock COMPLIANCE, 7-year retention (exceeding the 6-year minimum). The bucket is replicated to us-west-2 and tiers to Glacier IR at 90 days and Deep Archive at 2 years.
  • Tamper evidence beyond WORM: an hourly job writes a manifest of SHA-256 object hashes, chains it to the previous manifest's hash, and signs it with an asymmetric KMS key. CloudTrail log-file integrity validation covers the AWS API trail.

Other trails.

  • pgAudit: DDL, role changes, and object-level auditing on PHI tables via pgaudit.role, exported to CloudWatch Logs and archived.
  • CloudTrail org trail: includes S3 data events on PHI buckets and KMS events.

Detection rules (Athena scheduled queries and CloudWatch metric filters routed through EventBridge to PagerDuty/Slack; alerts contain IDs only):

  • A clinician viewing more than N patients with no care relationship.
  • Access to VIP or employee records.
  • After-hours bulk reads.
  • Any break-glass use.

Access to audit data. Queries of the audit data are themselves logged. Only the security_admin and Privacy Officer roles can read it. The system can produce a per-patient access report (accounting of disclosures) on request.

5.8 Data residency

  • The org SCP denies every region except us-east-1 and us-west-2.
  • Aurora Global, S3 CRR, DynamoDB replicas, Backup copies, KMS replicas, and Secrets replicas all target us-west-2 only.
  • Chime SDK MediaRegion is pinned server-side to us-east-1 or us-west-2. Any other value is rejected.
  • CloudFront serves only non-PHI static assets and is geo-restricted to the US.
  • Third-party residency gaps to close:
    • Push notifications (APNs/FCM): no PHI in payloads. Use "You have a new message" with a deep link. Apple and Google do not sign BAAs.
    • Crash reporting and analytics: no third-party trackers on authenticated screens (see the HHS OCR guidance on online tracking technologies). Use a BAA-covered, US-hosted crash tool, or scrub PHI.
    • Calendar sync: events contain only "VerdantConnect visit" plus a link, with no patient name or visit reason. Personal Google/Outlook calendars are not covered by any BAA.

5.9 Vendor and BAA register (maintain it in the compliance platform)

Subprocessor Touches PHI/PII? BAA / basis Notes
AWS (all services incl. Chime SDK, Cognito) Yes AWS BAA via Artifact (org-level) HIPAA-eligible services only
e-Prescribing vendor (Surescripts-certified) Yes BAA required EPCS for controlled substances: confirm the vendor handles DEA 21 CFR 1311 identity proofing and two-factor signing
SMS/email provider PII (+ appointment info) BAA required. Alternatively, Amazon SES + AWS End User Messaging SMS falls under the AWS BAA and removes a subprocessor (about $200/month at current volume) Reminder content follows the minimum-necessary principle
Payment gateway Limited PII HIPAA §1179 payment-processing exemption; PCI SAQ A Send no clinical data in metadata
Partner clinics / EHRs Yes They are covered entities; agreements per the BAA chain
Google/Microsoft (calendar) Must be none No BAA Event content is PHI-free by design
Observability/APM, support desk, compliance platform Possibly BAA or contractual PHI prohibition plus scrubbing Datadog, Zendesk, and others offer BAAs on specific plans
Pen-test firm Should be none NDA; test staging with synthetic data plus a scoped prod test

5.10 Incident response and breach notification

  • The plan (owned by the Security Lead, approved annually) covers:
    • Severity matrix (SEV-1 to SEV-4)
    • Roles: IC, Security Lead, Privacy Officer, Legal, Comms
    • Runbooks for: credential compromise, PHI exposure, ransomware, vendor breach, lost device, insider misuse
  • Tooling: GuardDuty, Security Hub, and Macie findings flow through EventBridge to PagerDuty.
    • Forensics runbook: isolate tasks by moving them to a quarantine security group and stopping them after capturing logs; snapshot data; preserve CloudTrail and VPC Flow Logs.
    • Legal hold via an Object Lock legal-hold flag.
  • HIPAA breach workflow:
    1. Discovery starts the clock.
    2. The four-factor risk assessment (§164.402) is documented.
    3. As a business associate, you notify the covered entity without unreasonable delay and no later than 60 calendar days after discovery. Most BAAs contractually require far sooner, often 5–10 business days; track the shortest obligation per partner.
    4. The covered entity notifies individuals, HHS (immediately if 500+ individuals are affected), and media where more than 500 residents of a state are affected, unless it delegated this to you.
    5. State breach laws can be stricter (some are 30 days). Legal maintains a matrix.
    6. All documentation is retained for 6 years.
  • Exercises: tabletop exercises twice a year (one PHI-exposure scenario, one ransomware/DR scenario). After-action reports are SOC 2 CC7.4/CC7.5 evidence.

5.11 Security testing cadence

Activity Cadence Tool / owner
External penetration test (web, mobile, API, cloud config) Annual, plus after major architectural change Third-party CREST/OSCP firm. AWS permits pen testing of the services in this stack without pre-approval.
External vulnerability scan Quarterly (evidence export) Tenable or Qualys external scan
Container and Lambda vulnerability scanning Continuous Amazon Inspector (ECR enhanced scanning). Criticals fixed in 7 days, highs in 30.
SAST / SCA / IaC / DAST Every PR / nightly CodeQL or Semgrep, Dependabot, Checkov, OWASP ZAP against staging
Access reviews Quarterly IdP plus app roles, exported to the compliance platform
DR game day Quarterly (staging), twice a year (prod) Platform team

Regulatory note: HHS proposed Security Rule amendments (NPRM, Jan 2025) that would make encryption, MFA, annual pen testing, asset inventories, and 72-hour restoration capability explicit requirements. I cannot confirm whether they have been finalized. This design already meets those proposals, so it is robust either way.


6. Security Checklist (Requirements → Controls → Evidence)

Requirement Concrete controls SOC 2 / HIPAA mapping Evidence artifact
PHI encrypted at rest (AES-256) KMS CMKs on Aurora, S3, DynamoDB, Valkey, SQS, Logs, Backup; SCP denies unencrypted RDS; Config rules rds-storage-encrypted, s3-default-encryption-kms CC6.1, §164.312(a)(2)(iv) Config conformance pack report, KMS key inventory
TLS 1.2+ end to end, incl. internal ALB TLS 1.3/1.2 policy; HTTPS targets; Service Connect TLS; rds.force_ssl; bucket-policy TLS denies; Valkey in-transit encryption CC6.7, §164.312(e)(1) Terraform config, SSL Labs scan, Security Hub controls
BAAs with AWS and all subprocessors Artifact org BAA; vendor register with BAA status; procurement gate (no PHI integration without a signed BAA); DNS Firewall allowlist restricts egress to approved vendors only CC9.2, §164.308(b)(1) BAA repository, vendor risk assessments
PHI access audit logging, 6+ years, tamper-evident App audit library; Firehose → Object Lock COMPLIANCE 7 yr; signed hash-chain manifests; CloudTrail validation; pgAudit; separate Log Archive account CC7.2, §164.312(b), §164.316(b)(2) Sample queries, Object Lock config, manifest verification job output
RBAC least privilege Cognito groups → Cedar/OPA policies; care-relationship rule; Postgres RLS; per-service IAM roles; permission boundaries CC6.1, CC6.3, §164.312(a)(1), §164.308(a)(4) Policy repo, PR reviews, quarterly access reviews
Break-glass, fully logged Two-person approval, 60-min single-patient grants, break_glass audit flag with real-time alert, weekly review; infrastructure JIT through TEAM CC6.2, §164.312(a)(2)(ii) Break-glass log, weekly review sign-offs
US-only data residency Region-deny SCP; replication targets limited to us-west-2; Chime MediaRegion pinning; PHI-free push and calendar content CC6.1, contractual SCP JSON, replication configs
15-min idle timeout BFF server-side session enforcement; mobile app lock; IdP/Verified Access session policy CC6.1, §164.312(a)(2)(iii) Config screenshots, automated E2E test
MFA for clinicians and admins Cognito clinician pool MFA = REQUIRED (passkey/TOTP, no SMS); IdP FIDO2 for staff; Identity Center enforced CC6.1, §164.312(d) Cognito config export, IdP policy
Incident response and breach notification IR plan; PagerDuty routing; forensics runbooks; four-factor assessment template; per-partner notification SLA matrix; tabletop exercises twice a year CC7.3–CC7.5, §164.308(a)(6), §164.410 Plan document, tabletop reports
Pen test and quarterly scans Annual third-party test; quarterly external scans; Inspector continuous scanning; remediation SLAs tracked CC4.1, CC7.1, §164.308(a)(8) Reports, Jira remediation tickets
Managed secrets, none in code Secrets Manager + rotation; ECS valueFrom; TF write-only/ephemeral; gitleaks + push protection CC6.1, CC8.1 Rotation logs, CI scan results
Backups and contingency plan AWS Backup Vault Lock, cross-account and cross-region; monthly restore tests; DR game days A1.2, A1.3, §164.308(a)(7) Restore-test reports, game-day RTO/RPO records
Change management PR review required; Terraform plans in CI with Checkov; production deploys only via pipeline; ECS rollback on alarms CC8.1 GitHub audit log, pipeline records
Risk analysis Annual HIPAA Security Risk Analysis plus SOC 2 risk assessment CC3.1–CC3.4, §164.308(a)(1)(ii)(A) SRA document
Workforce training HIPAA and security training at hire and annually; phishing simulation CC1.4, CC2.2, §164.308(a)(5) Training completion records

7. Terraform Starter

This is a representative slice of the data layer, audit archive, service mesh TLS, and org guardrails. VPC, IAM policy documents, and task definitions are referenced as omitted modules and files.

###############################################################################
# VerdantConnect – production starter (representative, not complete)
#
# Suggested layout (separate state per layer, blast-radius isolation):
#   live/org/          -> SCPs, org-level config          (management account)
#   live/prod/network  -> VPCs, endpoints, DNS Firewall
#   live/prod/data     -> KMS, Aurora Global, S3, DynamoDB  (this file)
#   live/prod/compute  -> ECS cluster/services, ALB, WAF
#   live/log-archive/  -> audit archive, CloudTrail bucket
#
# Requires Terraform >= 1.11 (ephemeral resources, write-only arguments,
# S3-native state locking) and AWS provider 6.x. Verify argument names against
# the provider version you pin before applying.
###############################################################################

terraform {
  required_version = ">= 1.11"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.7"
    }
  }

  backend "s3" {
    bucket       = "vc-tfstate-prod-use1" # versioned, SSE-KMS, access-logged, BPA on
    key          = "prod/data/terraform.tfstate"
    region       = "us-east-1"
    encrypt      = true
    kms_key_id   = "alias/vc-tfstate"
    use_lockfile = true # S3-native locking; no DynamoDB lock table needed
  }
}

variable "prod_account_id"        { type = string }
variable "log_archive_account_id" { type = string }
variable "firehose_role_arn"      { type = string } # prod-account Firehose delivery role

variable "aurora_engine_version" {
  type        = string
  description = "Pin to an Aurora PostgreSQL 16.x version that supports Global Database."
  default     = "16.6"
}

locals {
  tags = {
    Application        = "verdantconnect"
    Environment        = "prod"
    DataClassification = "phi" # drives Config rules, Macie scope, and cost allocation
    Owner              = "platform"
    ManagedBy          = "terraform"
  }
  azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

# --- Providers -----------------------------------------------------------------
provider "aws" {
  region              = "us-east-1"
  allowed_account_ids = [var.prod_account_id] # prevents applying to the wrong account
  default_tags { tags = local.tags }
}

provider "aws" {
  alias               = "dr"
  region              = "us-west-2" # only permitted DR region (see SCP)
  allowed_account_ids = [var.prod_account_id]
  default_tags { tags = local.tags }
}

provider "aws" {
  alias  = "log_archive"
  region = "us-east-1"
  assume_role {
    role_arn = "arn:aws:iam::${var.log_archive_account_id}:role/vc-terraform-deployer"
  }
  default_tags { tags = local.tags }
}

###############################################################################
# KMS – one CMK per data domain, multi-Region so ciphertext is portable to DR.
###############################################################################
locals {
  kms_domains = toset(["phi", "secrets", "logs"])
}

resource "aws_kms_key" "this" {
  for_each                = local.kms_domains
  description             = "VerdantConnect ${each.key} CMK (prod)"
  multi_region            = true
  enable_key_rotation     = true
  rotation_period_in_days = 365
  deletion_window_in_days = 30
  # Least-privilege key policy: named task roles only, no kms:* for humans.
  policy = data.aws_iam_policy_document.kms[each.key].json # defined in kms_policies.tf
}

resource "aws_kms_alias" "this" {
  for_each      = local.kms_domains
  name          = "alias/vc-${each.key}"
  target_key_id = aws_kms_key.this[each.key].key_id
}

resource "aws_kms_replica_key" "dr" {
  for_each                = local.kms_domains
  provider                = aws.dr
  primary_key_arn         = aws_kms_key.this[each.key].arn
  deletion_window_in_days = 30
  policy                  = data.aws_iam_policy_document.kms[each.key].json
}

###############################################################################
# Aurora PostgreSQL Global Database
#   RPO: storage-level replication, typically < 1s (alarm on AuroraGlobalDBRPOLag)
#   NOTE: we intentionally do NOT set rds.global_db_rpo – it would block commits
#   in us-east-1 whenever us-west-2 lags, coupling primary availability to DR.
###############################################################################
locals {
  aurora_params = [
    { name = "rds.force_ssl", value = "1", apply = "immediate" },
    { name = "ssl_min_protocol_version", value = "TLSv1.2", apply = "immediate" },
    { name = "shared_preload_libraries", value = "pg_stat_statements,pgaudit", apply = "pending-reboot" },
    { name = "pgaudit.log", value = "ddl,role", apply = "immediate" }, # session-level
    { name = "pgaudit.role", value = "rds_pgaudit", apply = "immediate" }, # object-level on PHI tables
    { name = "log_connections", value = "1", apply = "immediate" },
    { name = "log_disconnections", value = "1", apply = "immediate" },
  ]
}

resource "aws_rds_cluster_parameter_group" "primary" {
  name   = "vc-prod-aurora-pg16"
  family = "aurora-postgresql16"

  dynamic "parameter" {
    for_each = local.aurora_params
    content {
      name         = parameter.value.name
      value        = parameter.value.value
      apply_method = parameter.value.apply
    }
  }
}

resource "aws_rds_cluster_parameter_group" "dr" {
  provider = aws.dr
  name     = "vc-prod-aurora-pg16"
  family   = "aurora-postgresql16"

  dynamic "parameter" {
    for_each = local.aurora_params
    content {
      name         = parameter.value.name
      value        = parameter.value.value
      apply_method = parameter.value.apply
    }
  }
}

resource "aws_rds_global_cluster" "vc" {
  global_cluster_identifier = "vc-prod-global"
  engine                    = "aurora-postgresql"
  engine_version            = var.aurora_engine_version
  database_name             = "verdant"
  storage_encrypted         = true
  deletion_protection       = true
}

# Master password is generated ephemerally and written only to write-only
# arguments, so it NEVER lands in Terraform state or plan files.
# Services do not use this credential: each has its own rotated DB role.
ephemeral "random_password" "db_master" {
  length           = 40
  special          = true
  override_special = "!#$%^&*()-_=+[]{}<>:?" # excludes / @ " and space (RDS-invalid)
}

resource "aws_secretsmanager_secret" "db_master" {
  name        = "prod/aurora/master"
  description = "Break-glass Aurora master credential (rotated by Lambda)"
  kms_key_id  = aws_kms_key.this["secrets"].arn

  replica {
    region     = "us-west-2"
    kms_key_id = aws_kms_replica_key.dr["secrets"].arn
  }
}

resource "aws_secretsmanager_secret_version" "db_master" {
  secret_id = aws_secretsmanager_secret.db_master.id
  secret_string_wo = jsonencode({
    username = "vc_admin"
    password = ephemeral.random_password.db_master.result
  })
  secret_string_wo_version = 1 # bump to push a new value from Terraform
}
# aws_secretsmanager_secret_rotation (30-day, alternating-users Lambda) omitted.

resource "aws_rds_cluster" "primary" {
  cluster_identifier        = "vc-prod-use1"
  global_cluster_identifier = aws_rds_global_cluster.vc.id
  engine                    = aws_rds_global_cluster.vc.engine
  engine_version            = aws_rds_global_cluster.vc.engine_version

  master_username            = "vc_admin"
  master_password_wo         = ephemeral.random_password.db_master.result
  master_password_wo_version = 1

  storage_encrypted = true
  kms_key_id        = aws_kms_key.this["phi"].arn
  # Aurora Standard storage by default. Switch to I/O-Optimized
  # (storage_type = "aurora-iopt1") once I/O exceeds ~25% of Aurora spend.

  db_subnet_group_name            = module.network.aurora_subnet_group_name # data tier, no NAT route
  vpc_security_group_ids          = [module.network.aurora_sg_id]
  db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.primary.name

  iam_database_authentication_enabled = true # break-glass human access via SSM port-forward
  backup_retention_period             = 35   # PITR window (RPO for logical corruption)
  preferred_backup_window             = "07:00-08:00" # UTC = 03:00-04:00 ET, lowest traffic
  preferred_maintenance_window        = "sun:08:00-sun:09:00"
  copy_tags_to_snapshot               = true
  deletion_protection                 = true
  skip_final_snapshot                 = false
  final_snapshot_identifier           = "vc-prod-use1-final"
  enabled_cloudwatch_logs_exports     = ["postgresql"] # pgAudit -> CW Logs (KMS) -> archive
}

resource "aws_rds_cluster_instance" "primary" {
  count              = 2 # writer + same-size reader (failover tier 0) in different AZs
  identifier         = "vc-prod-use1-${count.index}"
  cluster_identifier = aws_rds_cluster.primary.id
  instance_class     = "db.r7g.2xlarge"
  engine             = aws_rds_cluster.primary.engine
  engine_version     = aws_rds_cluster.primary.engine_version
  availability_zone  = local.azs[count.index]
  promotion_tier     = 0

  performance_insights_enabled    = true
  performance_insights_kms_key_id = aws_kms_key.this["phi"].arn
  monitoring_interval             = 15
  monitoring_role_arn             = aws_iam_role.rds_enhanced_monitoring.arn # iam.tf
  auto_minor_version_upgrade      = false # upgrades go through staging first (change mgmt)
}

resource "aws_rds_cluster" "secondary" {
  provider                  = aws.dr
  cluster_identifier        = "vc-prod-usw2"
  global_cluster_identifier = aws_rds_global_cluster.vc.id
  engine                    = aws_rds_global_cluster.vc.engine
  engine_version            = aws_rds_global_cluster.vc.engine_version

  storage_encrypted = true
  kms_key_id        = aws_kms_replica_key.dr["phi"].arn # region-local key required

  db_subnet_group_name            = module.network_dr.aurora_subnet_group_name
  vpc_security_group_ids          = [module.network_dr.aurora_sg_id]
  db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.dr.name
  iam_database_authentication_enabled = true
  deletion_protection             = true
  skip_final_snapshot             = true

  depends_on = [aws_rds_cluster_instance.primary]

  lifecycle {
    # Set by AWS on global-cluster membership; avoids perpetual diffs / failover drift.
    ignore_changes = [replication_source_identifier, global_cluster_identifier]
  }
}

resource "aws_rds_cluster_instance" "secondary" {
  provider           = aws.dr
  identifier         = "vc-prod-usw2-0"
  cluster_identifier = aws_rds_cluster.secondary.id
  instance_class     = "db.r7g.2xlarge" # same size: no post-failover resize inside the RTO
  engine             = aws_rds_cluster.secondary.engine
  engine_version     = aws_rds_cluster.secondary.engine_version
  promotion_tier     = 0
}

###############################################################################
# PHI access audit archive (Log Archive account)
#   WORM via S3 Object Lock COMPLIANCE mode: nobody, including root, can delete
#   or shorten retention. 7 years > HIPAA's 6-year minimum.
###############################################################################
resource "aws_kms_key" "audit" {
  provider                = aws.log_archive
  description             = "PHI audit archive CMK"
  enable_key_rotation     = true
  deletion_window_in_days = 30
  policy                  = data.aws_iam_policy_document.audit_kms.json # grants Firehose role encrypt only
}

resource "aws_s3_bucket" "audit" {
  provider            = aws.log_archive
  bucket              = "vc-phi-access-audit-${var.log_archive_account_id}-use1"
  object_lock_enabled = true
}

resource "aws_s3_bucket_ownership_controls" "audit" {
  provider = aws.log_archive
  bucket   = aws_s3_bucket.audit.id
  rule {
    object_ownership = "BucketOwnerEnforced" # cross-account writes owned by Log Archive
  }
}

resource "aws_s3_bucket_versioning" "audit" {
  provider = aws.log_archive
  bucket   = aws_s3_bucket.audit.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_object_lock_configuration" "audit" {
  provider = aws.log_archive
  bucket   = aws_s3_bucket.audit.id
  rule {
    default_retention {
      mode  = "COMPLIANCE"
      years = 7
    }
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "audit" {
  provider = aws.log_archive
  bucket   = aws_s3_bucket.audit.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = aws_kms_key.audit.arn
    }
    bucket_key_enabled = true # ~99% fewer KMS requests
  }
}

resource "aws_s3_bucket_public_access_block" "audit" {
  provider                = aws.log_archive
  bucket                  = aws_s3_bucket.audit.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_lifecycle_configuration" "audit" {
  provider = aws.log_archive
  bucket   = aws_s3_bucket.audit.id

  rule {
    id     = "tier-and-expire"
    status = "Enabled"
    filter {}

    transition {
      days          = 90
      storage_class = "GLACIER_IR" # still Athena-queryable, millisecond retrieval
    }
    transition {
      days          = 730
      storage_class = "DEEP_ARCHIVE" # restore required before querying (12h)
    }
    expiration {
      days = 2585 # 7 years + 30 days; Object Lock blocks any earlier delete
    }
    noncurrent_version_expiration {
      noncurrent_days = 2585
    }
  }
}

data "aws_iam_policy_document" "audit_bucket" {
  statement {
    sid       = "DenyInsecureTransport"
    effect    = "Deny"
    actions   = ["s3:*"]
    resources = [aws_s3_bucket.audit.arn, "${aws_s3_bucket.audit.arn}/*"]
    principals {
      type        = "*"
      identifiers = ["*"]
    }
    condition {
      test     = "Bool"
      variable = "aws:SecureTransport"
      values   = ["false"]
    }
  }

  statement {
    sid       = "DenyTLSBelow12"
    effect    = "Deny"
    actions   = ["s3:*"]
    resources = [aws_s3_bucket.audit.arn, "${aws_s3_bucket.audit.arn}/*"]
    principals {
      type        = "*"
      identifiers = ["*"]
    }
    condition {
      test     = "NumericLessThan"
      variable = "s3:TlsVersion"
      values   = ["1.2"]
    }
  }

  statement {
    sid       = "AllowFirehoseAppendOnly" # write-only: no Get/Delete for the producer
    effect    = "Allow"
    actions   = ["s3:PutObject", "s3:AbortMultipartUpload", "s3:GetBucketLocation", "s3:ListBucket"]
    resources = [aws_s3_bucket.audit.arn, "${aws_s3_bucket.audit.arn}/*"]
    principals {
      type        = "AWS"
      identifiers = [var.firehose_role_arn]
    }
  }
}

resource "aws_s3_bucket_policy" "audit" {
  provider = aws.log_archive
  bucket   = aws_s3_bucket.audit.id
  policy   = data.aws_iam_policy_document.audit_bucket.json
}
# CRR of this bucket to us-west-2 (Object Lock retention replicates) omitted for brevity.

###############################################################################
# Service-to-service TLS: ECS Service Connect + AWS Private CA (short-lived mode,
# ~$50/month per CA vs ~$400 general-purpose). Root activation
# (aws_acmpca_certificate + aws_acmpca_certificate_authority_certificate) omitted.
###############################################################################
resource "aws_acmpca_certificate_authority" "svc_mesh" {
  type                            = "ROOT"
  usage_mode                      = "SHORT_LIVED_CERTIFICATE"
  permanent_deletion_time_in_days = 30

  certificate_authority_configuration {
    key_algorithm     = "EC_prime256v1"
    signing_algorithm = "SHA256WITHECDSA"
    subject {
      common_name  = "vc-prod-service-mesh-use1"
      organization = "Verdant Health Technologies"
    }
  }
}

resource "aws_service_discovery_http_namespace" "core" {
  name = "vc-prod.internal"
}

resource "aws_ecs_service" "api_gateway" {
  name                   = "api-gateway"
  cluster                = aws_ecs_cluster.core.id                   # compute.tf
  task_definition        = aws_ecs_task_definition.api_gateway.arn   # ARM64, secrets via valueFrom
  desired_count          = 3
  launch_type            = "FARGATE"
  enable_execute_command = false # no interactive shells into PHI workloads
  propagate_tags         = "SERVICE"

  network_configuration {
    subnets          = module.network.app_subnet_ids
    security_groups  = [aws_security_group.api_gateway.id]
    assign_public_ip = false
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.api_gateway.arn # HTTPS target group
    container_name   = "api-gateway"
    container_port   = 8443
  }

  deployment_circuit_breaker {
    enable   = true
    rollback = true
  }

  alarms {
    enable      = true
    rollback    = true
    alarm_names = [aws_cloudwatch_metric_alarm.api_5xx_rate.alarm_name]
  }

  service_connect_configuration {
    enabled   = true
    namespace = aws_service_discovery_http_namespace.core.arn

    service {
      port_name      = "https"
      discovery_name = "api-gateway"
      client_alias {
        port     = 8443
        dns_name = "api-gateway.vc-prod.internal"
      }
      tls {
        issuer_cert_authority {
          aws_pca_authority_arn = aws_acmpca_certificate_authority.svc_mesh.arn
        }
        kms_key  = aws_kms_key.this["phi"].arn
        role_arn = aws_iam_role.ecs_service_connect_tls.arn # iam.tf
      }
    }
  }

  lifecycle {
    ignore_changes = [desired_count] # owned by Application Auto Scaling
  }
}

resource "aws_appautoscaling_target" "api_gateway" {
  service_namespace  = "ecs"
  resource_id        = "service/${aws_ecs_cluster.core.name}/${aws_ecs_service.api_gateway.name}"
  scalable_dimension = "ecs:service:DesiredCount"
  min_capacity       = 3 # one per AZ
  max_capacity       = 30
}

resource "aws_appautoscaling_policy" "api_gateway_rps" {
  name               = "alb-requests-per-task"
  policy_type        = "TargetTrackingScaling"
  service_namespace  = aws_appautoscaling_target.api_gateway.service_namespace
  resource_id        = aws_appautoscaling_target.api_gateway.resource_id
  scalable_dimension = aws_appautoscaling_target.api_gateway.scalable_dimension

  target_tracking_scaling_policy_configuration {
    target_value       = 150 # req/s per 1-vCPU Node task; tune after load test
    scale_out_cooldown = 60
    scale_in_cooldown  = 300
    predefined_metric_specification {
      predefined_metric_type = "ALBRequestCountPerTarget"
      resource_label         = "${aws_lb.api.arn_suffix}/${aws_lb_target_group.api_gateway.arn_suffix}"
    }
  }
}

# Pre-warm for the rolling US morning peak (08:00 ET through 10:00 PT).
resource "aws_appautoscaling_scheduled_action" "api_morning_peak" {
  name               = "prewarm-morning"
  service_namespace  = aws_appautoscaling_target.api_gateway.service_namespace
  resource_id        = aws_appautoscaling_target.api_gateway.resource_id
  scalable_dimension = aws_appautoscaling_target.api_gateway.scalable_dimension
  schedule           = "cron(30 7 ? * MON-FRI *)"
  timezone           = "America/New_York"
  scalable_target_action {
    min_capacity = 6
    max_capacity = 30
  }
}

resource "aws_appautoscaling_scheduled_action" "api_midday_reset" {
  name               = "reset-midday"
  service_namespace  = aws_appautoscaling_target.api_gateway.service_namespace
  resource_id        = aws_appautoscaling_target.api_gateway.resource_id
  scalable_dimension = aws_appautoscaling_target.api_gateway.scalable_dimension
  schedule           = "cron(30 13 ? * MON-FRI *)"
  timezone           = "America/New_York"
  scalable_target_action {
    min_capacity = 3
    max_capacity = 30
  }
}
# (Evening 16:30–22:30 ET pair is identical in shape.)

###############################################################################
# Org guardrails (applied from live/org with the management-account provider
# alias "aws.org"). Shown here for reference.
###############################################################################
data "aws_iam_policy_document" "scp_guardrails" {
  statement {
    sid    = "DenyNonUSRegions" # HIPAA data-residency control
    effect = "Deny"
    # Global services are homed in us-east-1 (allowed), but exempt them explicitly
    # so CLI/SDK calls with a different default region don't break.
    not_actions = [
      "iam:*", "sts:*", "organizations:*", "account:*", "route53:*",
      "route53domains:*", "cloudfront:*", "support:*", "budgets:*",
      "ce:*", "health:*", "trustedadvisor:*",
    ]
    resources = ["*"]
    condition {
      test     = "StringNotEquals"
      variable = "aws:RequestedRegion"
      values   = ["us-east-1", "us-west-2"]
    }
    condition {
      test     = "ArnNotLike"
      variable = "aws:PrincipalARN"
      values   = ["arn:aws:iam::*:role/AWSControlTowerExecution"]
    }
  }

  statement {
    sid    = "ProtectAuditAndDetection" # tamper-resistance for SOC 2 / HIPAA audit controls
    effect = "Deny"
    actions = [
      "cloudtrail:StopLogging", "cloudtrail:DeleteTrail", "cloudtrail:UpdateTrail",
      "guardduty:DeleteDetector", "guardduty:DisassociateFromAdministratorAccount",
      "config:StopConfigurationRecorder", "config:DeleteConfigurationRecorder",
      "securityhub:DisableSecurityHub", "organizations:LeaveOrganization",
    ]
    resources = ["*"]
    condition {
      test     = "ArnNotLike"
      variable = "aws:PrincipalARN"
      values   = ["arn:aws:iam::*:role/vc-security-automation"]
    }
  }

  statement {
    sid       = "DenyUnencryptedRDS"
    effect    = "Deny"
    actions   = ["rds:CreateDBCluster", "rds:CreateDBInstance"]
    resources = ["*"]
    condition {
      test     = "Bool"
      variable = "rds:StorageEncrypted"
      values   = ["false"]
    }
  }
}

# resource "aws_organizations_policy" "guardrails" {
#   provider = aws.org
#   name     = "vc-phi-guardrails"
#   type     = "SERVICE_CONTROL_POLICY"
#   content  = data.aws_iam_policy_document.scp_guardrails.json
# }
# resource "aws_organizations_policy_attachment" "root" {
#   provider  = aws.org
#   policy_id = aws_organizations_policy.guardrails.id
#   target_id = var.org_root_id   # residency applies org-wide
# }

CI guardrails for this code:

  • terraform plan runs on every PR, and Checkov/Trivy fail the build on high-severity findings.
  • A Conftest/OPA policy requires DataClassification=phi resources to set a KMS key.
  • Applies to prod happen only from the main branch through the GitHub OIDC deploy role, with a required reviewer.

8. Estimated Monthly Cost

Assumptions: 1-year no-upfront Reserved Instances for Aurora and Valkey, and a 1-year Compute Savings Plan for Fargate. Both are bought after 4–6 weeks of measured steady state. Prices are list, approximate (±15%). Traffic is as stated: 120 req/s average, 900 peak, 9k visits.

# Item Sizing / driver $/mo
Primary: us-east-1
1 ECS Fargate (Graviton) ~32 vCPU / 80 GB average across ~10 services + workers (peaks via scaling), Compute SP 800
2 Aurora PostgreSQL compute 2 × db.r7g.2xlarge (writer + reader), 1-yr RI 1,110
3 Aurora storage, I/O, backups 450 GB Standard storage, ~1.2B I/Os, 35-day PITR 400
4 ElastiCache Valkey 2 × cache.m7g.large, RI 140
5 DynamoDB sessions (global table, both regions) On-demand, throttled activity writes 50
6 ALBs (public API + internal admin) ~10 LCU average 90
7 AWS WAF 1 web ACL, ~10 rules, ~310M requests 210
8 CloudFront (SPA static) Low volume 30
9 Amazon Chime SDK 9,000 × 20 min × 2 attendees = 360k attendee-min × ~$0.0017 615
10 S3 PHI bucket + requests + malware scanning 2 TB Standard, +150 GB/month 120
11 NAT gateways (3), ~12 interface endpoints × 3 AZ, public IPv4 450
12 Data transfer out ~2–3 TB API/PDF egress 250
13 SQS, EventBridge, Step Functions (batched), Lambda 60
14 Route 53 (zones, health checks, DNS Firewall) 20
Primary subtotal 4,345
DR: us-west-2 (warm standby)
15 Aurora secondary compute 1 × db.r7g.2xlarge, RI 560
16 Aurora secondary storage + replicated write I/O 150
17 ECS warm standby ~9 vCPU minimum 250
18 Valkey (small) 1 × cache.t4g.medium 40
19 ALB + WAF (low traffic) 60
20 NAT (2) + endpoints (2 AZ) 250
21 S3 replica (Standard-IA) + CRR + RTC 45
22 AWS Backup cross-region/cross-account copies (locked) 150
DR subtotal 1,505
Security, compliance, observability (all accounts)
23 Audit pipeline: Firehose (5 KB record rounding), Parquet conversion, Object Lock S3, Athena ~300M events/month 120
24 CloudTrail org trail + PHI S3/KMS data events 60
25 GuardDuty (incl. ECS runtime, RDS, S3 protection) 300
26 Security Hub + AWS Config (periodic recording for ECS tasks) + Audit Manager 330
27 Inspector (ECR) + Macie (targeted sampling jobs) 200
28 Verified Access (admin console) 1 app, ~20 staff 220
29 KMS + Secrets Manager (replicated) + Private CA (short-lived, ×2) 220
30 Cognito (Essentials for patients, Plus for clinicians) ~18.7k MAU 150
31 CloudWatch Logs/metrics/alarms, ADOT/X-Ray ~500 GB logs, IA class where possible 500
Security/observability subtotal 2,100
Platform
32 Non-prod (staging at ~25% scale + dev; Aurora Serverless v2 with auto-pause in dev; synthetic data) 2,200
33 Shared services (ECR, CI, TF state) 120
34 AWS Business Support ~10% of first $10k + 7% above 1,020
TOTAL (steady state, committed) ≈ $11,290
First 2–3 months, on-demand before commitments ≈ $12,500

Unit economics: about $1.25 of infrastructure per video visit. Track this monthly as the primary FinOps KPI.

Services deliberately not included, and why:

Service Cost Reason for excluding
Shield Advanced $3,000/month WAF plus Shield Standard is adequate at this threat profile
ARC routing-control cluster ~$1,800/month Manual Route 53 failover meets the 30-minute RTO
OpenSearch for audit ~$600+/month Athena on Parquet is enough
HealthLake — Not needed for pass-through FHIR sync
CloudTrail Lake — Duplicates the S3 archive
Network Firewall ~$870/month DNS Firewall as an interim control

Growth projection (the key finding)

At 12% month-over-month growth, usage roughly doubles every 6 months (1.12⁶ ≈ 1.97) and grows about 3.9× per year. About $2.8k of today's bill scales with usage: Chime, Fargate, WAF requests, egress, logs, audit, Cognito, and Aurora I/O.

Horizon Usage multiple Est. monthly infra
Today 1.0× ~$11.3k
+6 months (Mar 2027) ~2.0× ~$15k
+9 months (Jun 2027) ~2.8× ~$18k (ceiling)
+12 months (Sep 2027) ~3.9× ~$22–23k (includes an Aurora step up to r7g.4xlarge)

Recommendation: convert the fixed $18k ceiling into a unit-cost budget, for example ≤ $1.10 infrastructure per visit, falling as fixed costs amortize. Set AWS Budgets alerts at 80% and 100% of forecast, plus anomaly detection per cost-allocation tag.

Cost-optimization levers

  • Graviton everywhere (already applied), plus Fargate Spot for idempotent workers.
  • Commitments laddered quarterly rather than bought as one large purchase, so sizing can follow growth.
  • Aurora I/O-Optimized once I/O exceeds ~25% of Aurora spend. Cache calendar and availability reads in Valkey. Tune queries with Database Insights.
  • Throttle session-activity writes to one per 60s. Aggregate audit events client-side to approach 5 KB per record, since Firehose bills in 5 KB increments.
  • Put gateway endpoints (S3, DynamoDB) in front of NAT so NAT data-processing charges stay low.
  • CloudWatch Logs Infrequent Access class for verbose application logs. Set retention to 30 days hot, with an S3 archive beyond that.
  • At about $250k/year of spend, ask AWS about private pricing or an EDP.

Top 3 cost risks

# Risk Mitigation
1 Growth breaches the fixed ceiling around month 9–10. Chime attendee-minutes, Fargate, WAF request charges, and egress all scale linearly with visits. Re-baseline the budget to $/visit now. Ladder commitments. Review Chime pricing versus BAA-covered alternatives at ~30k visits/month (keep the video-svc adapter). Consider WAF rule tuning, or shifting static and cacheable traffic to CloudFront to cut per-request WAF charges.
2 Log, audit, and telemetry sprawl. Full-read pgAudit, debug logs, Config continuously recording short-lived Fargate tasks, Macie full-bucket scans, and Firehose 5 KB rounding can silently add $1–3k/month. Log budgets per service, enforced with CloudWatch metric alarms. pgAudit object-level only on PHI tables. Config periodic recording for AWS::ECS::Task. Macie sampling jobs rather than full scans. Audit event aggregation.
3 Data-tier step changes. 25 GB/month of growth plus traffic growth pushes Aurora from 2xlarge to 4xlarge (about +$1.1k/month ×3 instances) and increases I/O. Read/write splitting to readers. Aggressive caching of provider availability. Partition or archive encounter data older than 24 months to S3 (Parquet) with an on-demand restore path, if the retention policy allows. Evaluate I/O-Optimized and Database Savings Plans/RI flexibility at each renewal.

9. Implementation Roadmap

Critical timing constraint: a first SOC 2 Type II report by Q2 2027 requires an observation period, typically at least 3 months for a first report (confirm with your auditor). That means controls must be operating by about January 4, 2027, roughly 14 weeks from today. This drives the sequencing below.

Phase Dates (2026–27) Scope Exit criteria / milestones Depends on
0. Foundations Sep 28 – Oct 23 Org and Control Tower accounts; SCPs (region, audit protection, encryption); org-level BAA in Artifact; Identity Center + IdP; org CloudTrail, Config (HIPAA conformance pack), Security Hub, GuardDuty, Inspector; Log Archive with Object Lock; TF state and CI (GitHub OIDC, Checkov); KMS keys; vendor BAA inventory started; compliance platform onboarded; policy set drafted (IR, access control, risk management, change management, BCP/DR, vendor management) Security Hub baseline > 85% passing; no IAM users; SCPs active —
1. Primary platform Oct 19 – Nov 27 Network module (3-tier, endpoints, DNS Firewall); ECS cluster, ALB, WAF; Service Connect TLS; Aurora (single-region Multi-AZ first); Valkey; DynamoDB sessions; S3 PHI bucket and malware scanning; Cognito pools, BFF sessions, MFA, 15-min timeout; Chime integration; audit library and Firehose pipeline; Secrets Manager with rotation; staging mirrors prod Load test at 2× peak (1,800 req/s) passes p95 < 300 ms; audit completeness test (100% of PHI endpoints emit events); internal security review Phase 0
2. Production migration Nov 30 – Dec 18 (before the holiday change freeze) Logical replication from the current DB into Aurora; dual-run with Route 53 weighted shifts of 5% → 25% → 50% → 100% over about 5 business days; S3 object sync (DataSync); vendor endpoints switched to new NAT EIPs (partners allowlist in advance) 100% of traffic on the new platform for 7 days with no SEV-1/2; old stack kept warm 30 days Phase 1; partner allowlisting (2-week lead time)
3. DR region Dec 1 – Jan 15 (overlaps) Aurora Global secondary; S3 CRR + RTC; DynamoDB replica; Secrets and KMS replicas; west ECS warm standby; Cognito shadow pools; locked Backup vaults; SSM failover automation; first DR game day in staging, then prod Measured RTO ≤ 30 min and RPO ≤ 5 min in a documented game-day report Phase 2 (ideally), or run in parallel against staging
4. SOC 2 readiness and observation Dec 14 – Apr 2 Policies approved and training completed by Dec 31; observation window Jan 4 – Apr 2, 2027; first quarterly external scan (Jan); third-party pen test (Feb) with remediation; IR tabletop (Feb); quarterly access review (Mar); DR game day evidence; optional SOC 2 Type I in January as an interim sales artifact Readiness assessment clean; pen-test criticals and highs closed Phases 0–3 controls operating
5. Audit and optimization Jan – Jun 2027 Buy RIs and Savings Plans (Jan–Feb, after steady state); right-sizing; unit-cost dashboards; auditor fieldwork (April); Type II report (May–June 2027); second quarterly scan (April); evaluate Network Firewall and mTLS (Lattice) based on pen-test findings Report issued; cost ≤ unit target Phase 4

Rollback strategy by phase

Phase Rollback mechanism
0 SCPs are attached first to a Sandbox OU for 1 week before the root; a policy detach takes effect immediately.
1 No production traffic yet; destroy and recreate via Terraform. State is versioned in S3.
2 Route 53 weighted records return to the old stack in about 60 seconds. Reverse logical replication (Aurora → old DB) runs during the dual-run so writes made on the new platform are not lost. Rollback trigger: error rate > 1% or p95 > 2× baseline for 10 minutes. The data-divergence check runs hourly.
3 The DR region is additive. Detaching the secondary from the global cluster has no primary impact. Game-day failback uses managed switchover (zero data loss).
All deploys ECS blue/green with alarm-based automatic rollback. Database migrations follow expand/contract (backward-compatible first, destructive changes only after 2 releases). Feature flags for risky features.

Key dependencies and long-lead items

  • Partner allowlisting of new egress IPs and the e-Rx vendor certificate re-issuance: start in week 1.
  • Vendor BAAs (SMS/email, APM, support desk): legal lead time of 2–6 weeks.
  • Pen-test firm booking: book now for February.
  • Auditor selection: decide by November, and agree the observation window length in writing.

10. Explicit Trade-offs (ADR Summary)

Decision Chosen Alternative Why / what you give up
Container platform ECS Fargate EKS Less operational load for 4–5 platform engineers and less SOC 2 surface. You give up the Kubernetes ecosystem (service mesh, operators). Revisit beyond about 100 engineers.
DR posture Warm standby Active-active About $1.5k/month versus $5k+ plus multi-writer complexity. A regional event consumes about 30 minutes, which exceeds one month's error budget (Q1).
Video SFU Chime SDK Daily / Vonage / Zoom SDK Under the AWS BAA, cheapest, US region pinning. Weaker UX tooling than some competitors and long-term roadmap risk, hedged by an adapter layer.
Identity Cognito + BFF sessions Auth0/Okta CIAM Cost and BAA simplicity. Cognito is single-region, so DR sign-in is degraded (existing sessions survive).
API ingress Regional ALB (no CloudFront) CloudFront in front of the API Clean residency story. Less edge DDoS absorption.
East-west security Service Connect one-way TLS + service JWTs mTLS via VPC Lattice Simpler and cheaper; meets the "encrypted in transit" requirement. Mutual workload authentication is at the application layer rather than the network layer.
Egress control DNS Firewall Network Firewall About $20/month versus about $870/month. Does not block connections made directly to IP addresses.
Authorization Embedded Cedar/OPA Amazon Verified Permissions Avoids about $1k+/month in per-request fees. You own policy distribution and versioning.
Audit write failure Fail closed on PHI reads Fail open Compliance integrity over availability. With the Firehose-plus-SQS fallback, this should be vanishingly rare.
S3 RPO CRR + RTC (15-min SLA) Application dual-write Simplicity. Worst case slightly exceeds the 5-minute RPO; mitigated by reconciliation.

Suggested next steps:

  1. Answer the three blocking questions, especially the SLO scope for regional disasters.
  2. Book the pen test and the auditor this month.
  3. Start Phase 0 on Monday.

I can follow up with a detailed failover SSM Automation runbook, the audit-event library interface (TypeScript), or a fuller Terraform module layout.

Related prompts

IT & Administration

Cybersecurity Audit Specialist

Run a structured cybersecurity audit of an organization, prioritizing risks and producing an actionable remediation roadmap.

IT & Administration

DevOps Automation Specialist

Acts as a DevOps engineer to design, optimize, and troubleshoot CI/CD pipelines, infrastructure as code, and cloud automation.

IT & Administration

Docker Container Builder

Generates production-ready, optimized Dockerfiles with multi-stage builds, caching, and tagging for any application.

IT & Administration

Ansible Automation Playbook Creator

Generates production-ready, idempotent Ansible playbooks and roles for any infrastructure automation or configuration task.