diff --git a/.planning/phases/24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud/24-CONTEXT.md b/.planning/phases/24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud/24-CONTEXT.md new file mode 100644 index 0000000..e578283 --- /dev/null +++ b/.planning/phases/24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud/24-CONTEXT.md @@ -0,0 +1,214 @@ +# Phase 24: AWS Route 53 DNS Sync - Context + +**Gathered:** 2026-08-05 +**Status:** Ready for planning + + +## Phase Boundary + +Sync DNS hosted zones/records from AWS Route 53 into Postgres on a schedule, +support full CRUD back to Route 53 from Pulse for common record types, track +record-level change history over time (both Pulse-initiated and externally +detected drift), log every sync and CRUD operation for audit (including +failures), and integrate into the existing per-system sync section +(scheduler, `/admin/sync` UI, health checks) alongside Autotask/Datto +RMM/Veeam/PAX8. AWS credentials are resolved via BWS (Bitwarden Secrets +Manager), not plaintext env vars. + + + + +## Implementation Decisions + +### CRUD Scope & Guardrails +- **D-01:** Writable record types are the common set only — A, AAAA, CNAME, + MX, TXT, SRV. NS and SOA are excluded from the write path (zone-delegation + records; editing them risks breaking the zone). +- **D-02:** Records only, not zones. Pulse can create/update/delete records + within hosted zones that already exist in Route 53. Hosted zone + creation/deletion (domain onboarding/decommissioning) stays outside Pulse + (AWS console or infra-as-code). +- **D-03:** Destructive record operations (update/delete) execute + immediately — no phishing-style staged/two-step approval gate. Every + change is logged with actor/timestamp/before/after so mistakes are + traceable after the fact, not blocked beforehand. +- **D-04:** CRUD is gated at `requireAdmin()` (admin + super-admin) — the + same bar as other write-capable admin surfaces in Pulse, not a stricter + super-admin-only gate. + +### Change Tracking & Audit Schema +- **D-05:** Dedicated Route 53 tables, not a reuse of the phishing + pipeline's `audit_events` table. New migration introduces + `route53_zones` / `route53_records` / `route53_record_history` / + `route53_audit_log` (naming for planner/researcher to finalize) — mirrors + how Veeam and Datto RMM each own their tables rather than sharing a + cross-domain audit schema. +- **D-06:** Change history is written both for Pulse-initiated CRUD and for + sync-detected drift (a record changed outside Pulse, e.g. directly in the + AWS console). Each history row is tagged with a `source` field: + `pulse_crud` | `sync_detected_drift`, so the query "did someone change + this outside Pulse?" is answerable. +- **D-07:** Failed AWS API attempts (rate-limited, invalid record, AWS-side + error) are also logged in the audit trail — attempted before/after + + error message + `status: failed` — not just successful writes. +- **D-08:** Retention is unbounded — no purge job. Matches existing Pulse + convention; no audit/history table in this codebase currently has an + automatic retention/purge mechanism. + +### Admin UI & Sync Integration +- **D-09:** New tile on `/admin/sync` (same list as Veeam/Datto RMM/PAX8) + plus a dedicated `/admin/sync/route53` detail page for zones, records, + and history — the existing per-integration pattern, not folded into an + existing page. +- **D-10:** `/admin/integrations` disable toggle for `route53` is + display-only (suppresses health-check display; scheduler/sync/CRUD keep + working underneath) — the default behavior per CLAUDE.md. Route 53 is + **not** a second PAX8-style exception that blocks sync/writes when + disabled. +- **D-11:** Sync cadence is incremental + periodic full — more frequent + incremental checks plus a daily full reconciliation, rather than a single + daily full sync. Trade-off (more API calls against Route 53 rate limits + for better real-time drift detection) accepted knowingly. +- **D-12:** The health-check row for Route 53 goes beyond the generic + auth-check + last-sync-age pattern used by other integrations — it also + includes a DNS-specific delegation check: compare each hosted zone's + Route-53-authoritative NS records against a **live public DNS lookup** + (e.g. Node's `dns` module or a DoH resolver) for that domain, flagging a + mismatch as degraded health. No manually-maintained "expected NS" field — + the live lookup is itself the source of truth to diff against. + +### Claude's Discretion +- **Credentials & AWS account scope** — not discussed interactively (user + deliberately skipped this topic, treating it as already settled). Codebase + scouting found uncommitted infrastructure already in place: + `docker-entrypoint.sh` (new, untracked) plus diffs to `Dockerfile` and + `docker-compose.yml` that install the `bws` CLI and wrap the app's start + command as `bws run --project-id "$BWS_PROJECT_ID" -- node server.js` + when `BWS_ACCESS_TOKEN` is set, falling back to a plain `node server.js` + otherwise. **This means Bitwarden secret injection happens at the + container-entrypoint layer, before the Node process starts** — the app + itself never calls a BWS SDK; AWS credentials simply appear as normal + `process.env` values by the time `getRoute53Client()`-style code runs. + Researcher/planner should: (1) follow the exact existing + `lib/services/-factory.ts` + `isConfigured()` pattern used by + every other integration, reading credentials from `process.env`; (2) + confirm the actual env var names with the user (e.g. `AWS_ACCESS_KEY_ID` + / `AWS_SECRET_ACCESS_KEY` / `AWS_REGION`, vs a `ROUTE53_*`-prefixed + variant) before finalizing the factory — this wasn't locked in + discussion; (3) add a `ROUTE53_*` (or `AWS_*`) row to CLAUDE.md's + integration env-prefix table once confirmed. +- **AWS account scope** — not discussed. Default assumption for planning + purposes is a single AWS account holding all client hosted zones (the + common MSP pattern), not per-client AWS accounts/cross-account IAM roles. + Flag during research if this assumption looks wrong once the actual AWS + setup is inspected. +- **Exact record-change diff granularity** (whole-recordset replace vs + individual value diffing) — left to researcher/planner, informed by how + the AWS SDK's `ChangeResourceRecordSets` API actually models a record + update. +- **Table/column naming inside the dedicated Route 53 schema** — D-05 locks + "dedicated tables," not literal names; researcher/planner should follow + existing migration conventions (`snake_case`, audit columns + `created_at`/`updated_at`/`synced_at`/`is_deleted`/`deleted_at`). + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +No external specs, ADRs, or docs reference AWS Route 53 anywhere in this +repo — ROADMAP.md's Phase 24 section has no "Canonical refs:" field, and no +seed file covers this integration. Requirements are fully captured in the +decisions above and in ROADMAP.md's Phase 24 Success Criteria. + +### Closest existing analogs (not canonical docs, but the patterns to follow) +- `lib/services/veeam-factory.ts`, `lib/services/veeam-sync-service.ts` — + factory + sync-service pattern for a full external integration with + scheduler + admin UI + health check +- `lib/services/datto-rmm-factory.ts`, `lib/services/datto-rmm-sync-service.ts` + — second reference implementation of the same pattern +- `lib/services/sync-scheduler.ts` — `sync_type` union, `ScheduleConfig`, + `defaultSchedules` array to extend for `route53-incremental`/`route53-full` +- `lib/services/integration-health.ts` — health-check aggregator to extend +- `app/admin/sync/page.tsx` — integration tile list (`id`/`category`/ + `product`/`description`/`href`/`logo`/`color`) to extend with a `route53` + entry +- `migrations/081_*.sql` — `integration_settings` table backing the + `/admin/integrations` disable toggle (D-10) + + + + +## Existing Code Insights + +### Reusable Assets +- `components/admin/DataTable.tsx` — for the zones/records list on the new + `/admin/sync/route53` page +- `components/admin/DetailModal.tsx` — for record detail/history drill-down + (formatted/raw tab pattern already established) +- `app/admin/sync/page.tsx` tile array — extend with a `route53` entry + following the exact shape used for `veeam`/`datto-rmm`/`pax8` + +### Established Patterns +- `lib/services/-factory.ts` + `isConfigured()` — credential + lazy-load + config-check pattern every integration follows; Route 53 + client should match this exactly (see Claude's Discretion above) +- `lib/services/-sync-service.ts` — incremental/full sync against + `lastTrackedModificationDateTime`-style cursors, batched via + `postgresClient.bulkUpsert()` +- `lib/services/sync-scheduler.ts` — node-cron singleton; new sync types + added to the `sync_type` string union and `defaultSchedules` seed array +- `lib/services/integration-health.ts` — per-integration health check, + cached ~5 minutes, read by both `/admin/integrations` and `/admin/sync` +- `migrations/081_*.sql` `integration_settings` — disable-toggle table with + `disabled_by`/`disabled_at`/`disabled_reason` audit columns; per + CLAUDE.md, PAX8 is currently the only integration where disabling also + blocks sync/writes — Route 53 explicitly does **not** join that list + (D-10) + +### Integration Points +- `lib/services/sync-scheduler.ts` — add `route53-incremental` / + `route53-full` to the `sync_type` union and `defaultSchedules` +- `lib/services/integration-health.ts` — add a `route53` health entry + (auth check + last-sync age + the D-12 NS-delegation check) +- `app/admin/sync/page.tsx` — add `route53` tile; new + `app/admin/sync/route53/page.tsx` detail page +- New `app/api/route53/*` routes for zone/record read + CRUD + history +- New numbered migration (planner/researcher to confirm the current highest + migration number — duplicates exist at 002/004/009, alphabetical apply + order per CLAUDE.md) for the dedicated Route 53 tables (D-05) +- `docker-entrypoint.sh` / `Dockerfile` / `docker-compose.yml` — already + modified (uncommitted) to wire BWS secret injection; see Claude's + Discretion above + + + + +## Specific Ideas + +- Health check must include a **live public DNS lookup** for NS delegation + mismatch (D-12) — not a manually-maintained "expected NS" field. +- Audit trail must capture failed AWS API attempts, not just successful + writes (D-07). +- Change history must distinguish Pulse-initiated changes from + sync-detected external drift via a `source` tag (D-06). + + + + +## Deferred Ideas + +None — discussion stayed within phase scope. The "Credentials & AWS scope" +gray area was deliberately not discussed interactively (user judged it +already settled by the existing BWS/docker-entrypoint infrastructure) — see +Claude's Discretion above, not treated as out-of-scope or deferred to a +future phase. + + + +--- + +*Phase: 24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud* +*Context gathered: 2026-08-05* diff --git a/.planning/phases/24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud/24-DISCUSSION-LOG.md b/.planning/phases/24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud/24-DISCUSSION-LOG.md new file mode 100644 index 0000000..b6ee4a6 --- /dev/null +++ b/.planning/phases/24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud/24-DISCUSSION-LOG.md @@ -0,0 +1,148 @@ +# Phase 24: AWS Route 53 DNS Sync - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. + +**Date:** 2026-08-05 +**Phase:** 24-AWS Route 53 DNS Sync +**Areas discussed:** CRUD scope & guardrails, Change tracking & audit schema, Admin UI & sync integration + +--- + +## CRUD Scope & Guardrails + +| Option | Description | Selected | +|--------|-------------|----------| +| Common types only | A, AAAA, CNAME, MX, TXT, SRV — the record types an MSP actually edits day-to-day. NS/SOA excluded. | ✓ | +| All record types | Includes NS/SOA — full parity with the AWS console, higher risk. | | +| Read-only for now | Ship sync + audit log first; defer writable types to a follow-up. | | + +**User's choice:** Common types only +**Notes:** — + +| Option | Description | Selected | +|--------|-------------|----------| +| Records only | Zones provisioned/decommissioned outside Pulse; Pulse only CRUDs records within existing zones. | ✓ | +| Zones + records | Pulse can also create/delete whole hosted zones. | | + +**User's choice:** Records only +**Notes:** — + +| Option | Description | Selected | +|--------|-------------|----------| +| Immediate, with full audit trail | Executes right away like other admin CRUD; every change logged with actor/before/after. | ✓ | +| Confirmation dialog only | Client-side "Are you sure?" modal, no server-side gate. | | +| Two-step approval gate | Mirrors phishing remediation — staged, second admin approves. | | + +**User's choice:** Immediate, with full audit trail +**Notes:** — + +| Option | Description | Selected | +|--------|-------------|----------| +| Admin + super-admin | Same bar as other write-capable admin surfaces (`requireAdmin()`). | ✓ | +| Super-admin only | Tighter gate given DNS-change blast radius. | | +| Any authenticated user | No role restriction. | | + +**User's choice:** Admin + super-admin +**Notes:** — + +--- + +## Change Tracking & Audit Schema + +| Option | Description | Selected | +|--------|-------------|----------| +| Dedicated Route 53 tables | New `route53_*` tables scoped to this integration, mirrors Veeam/Datto RMM. | ✓ | +| Reuse phishing's audit_events table | Shared cross-domain audit table. | | + +**User's choice:** Dedicated Route 53 tables +**Notes:** — + +| Option | Description | Selected | +|--------|-------------|----------| +| Log both, tagged by source | History rows tagged `pulse_crud` / `sync_detected_drift`. | ✓ | +| Only log Pulse-initiated CRUD | Sync silently overwrites current-state tables, no drift history. | | + +**User's choice:** Log both, tagged by source +**Notes:** — + +| Option | Description | Selected | +|--------|-------------|----------| +| Log failed attempts too | Audit row captures attempted before/after + error + status=failed. | ✓ | +| Only log successful changes | Failed API calls just console.error'd. | | + +**User's choice:** Log failed attempts too +**Notes:** — + +| Option | Description | Selected | +|--------|-------------|----------| +| Unbounded, no purge | Matches existing Pulse convention — no history/audit table currently purges. | ✓ | +| Time-boxed retention | Scheduled purge job for rows older than N months/years. | | + +**User's choice:** Unbounded, no purge +**Notes:** — + +--- + +## Admin UI & Sync Integration + +| Option | Description | Selected | +|--------|-------------|----------| +| New tile + detail page | `/admin/sync` tile + dedicated `/admin/sync/route53` page — existing Veeam/Datto RMM/PAX8 pattern. | ✓ | +| Fold into an existing page | Attach DNS management to an existing admin section. | | + +**User's choice:** New tile + detail page +**Notes:** — + +| Option | Description | Selected | +|--------|-------------|----------| +| Display-only toggle | Disabling suppresses health-check display only; matches CLAUDE.md default for every integration except PAX8. | ✓ | +| Blocks sync + CRUD like PAX8 | Second exception alongside PAX8 — disabling also skips scheduler and 403s writes. | | + +**User's choice:** Display-only toggle +**Notes:** — + +| Option | Description | Selected | +|--------|-------------|----------| +| Daily full sync | One scheduled job/day, matches pax8-daily/engagement-daily cadence. | | +| Incremental + periodic full | More frequent incremental checks plus daily full reconciliation. | ✓ | + +**User's choice:** Incremental + periodic full +**Notes:** Accepted trade-off of more API calls against Route 53 rate limits for better real-time drift detection. + +| Option | Description | Selected | +|--------|-------------|----------| +| Generic pattern | Same integration-health.ts shape — auth check + last-sync age. | | +| Add DNS-specific checks | Also flag degraded health on NS delegation mismatch vs registrar. | ✓ | + +**User's choice:** Add DNS-specific checks +**Notes:** Follow-up clarified how to determine "expected" NS — see next row. + +| Option | Description | Selected | +|--------|-------------|----------| +| Live public DNS lookup | Query a public resolver (DoH or Node `dns` module) for the domain's NS records, diff against Route 53's authoritative set. | ✓ | +| Manual expected-NS field | Admin manually records expected NS per zone; check diffs against stored value. | | + +**User's choice:** Live public DNS lookup +**Notes:** No manually-maintained field — the live lookup is itself the source of truth. + +--- + +## Claude's Discretion + +- **Credentials & AWS account scope** — user deliberately did not select this + topic for discussion (treated as already settled by existing uncommitted + `docker-entrypoint.sh`/`Dockerfile`/`docker-compose.yml` BWS wiring found + during codebase scouting). Left to researcher/planner to confirm exact + env var names and follow the existing factory pattern. See CONTEXT.md + Claude's Discretion section for the full writeup. +- AWS account scope (single account vs per-client) — not discussed; + defaulted to single-account assumption. +- Exact record-change diff granularity — left to researcher/planner, + informed by the AWS SDK's `ChangeResourceRecordSets` shape. +- Table/column naming inside the dedicated Route 53 schema — locked concept + ("dedicated tables"), not literal names. + +## Deferred Ideas + +None — discussion stayed within phase scope.