feat(federation): mTLS AuthGuard with OID-based grant resolution (FED-M3-03) #509

Merged
jason.woltje merged 2 commits from feat/federation-m3-auth-guard into main 2026-04-25 13:27:21 +00:00
Owner

Summary

  • Implements FederationAuthGuard (CanActivate) for inbound federation API routes on apps/gateway
  • Validates mTLS client certificates: extracts OIDs 1.3.6.1.4.1.99999.1 (grantId) and 1.3.6.1.4.1.99999.2 (subjectUserId)
  • Loads grant + peer in a single DB join query (getGrantWithPeer), asserts status === 'active', and validates cert serial against stored peer cert serial (defense-in-depth)
  • Attaches FederationContext to request.federationContext on success
  • All rejection paths use the federation wire-format error envelope from @mosaicstack/types, not raw NestJS exceptions
  • Guard is registered in FederationModule providers and exported for @UseGuards() in M3-05/06/07 verb controllers

Closes #462

PRD AC-3 (cross-user isolation prerequisite): checked off -- the guard binds cert to grant to subjectUserId at the transport layer, preventing any cross-user data access by a peer that does not hold an active grant for the target user.

Key decisions

Cert-serial check: Kept. The stored certSerial on federation_peers is matched against the inbound cert serial. This is defense-in-depth: even if the mTLS CA is compromised or a TLS terminator forwards an arbitrary cert, the serial must match what was registered at enrollment. No additional DB round-trip needed (peer is loaded in the same join as the grant).

FastifyRequest typing: request.raw.socket typed as Partial<tls.TLSSocket> -- presence of getPeerCertificate is checked at runtime, allowing safe fallback for plain HTTP connections in dev/test.

OID helper location: federation/oid.util.ts (not inside server/) so it can be reused by future client-side verification without a circular dependency.

Test plan

  • pnpm typecheck -- 38 package typecheck tasks pass
  • pnpm lint -- 21 lint tasks pass
  • pnpm format:check -- all files use Prettier code style
  • pnpm --filter @mosaicstack/gateway test -- 28 test files pass (450 tests), 5 skipped (DB integration)
  • New guard tests: 11/11 pass (missing cert 401, parse failure 401, missing OIDs 401, grant not found 403, pending/revoked/expired status 403, serial mismatch 403, happy path context attach)

Generated with Claude Code

## Summary - Implements `FederationAuthGuard` (`CanActivate`) for inbound federation API routes on `apps/gateway` - Validates mTLS client certificates: extracts OIDs `1.3.6.1.4.1.99999.1` (grantId) and `1.3.6.1.4.1.99999.2` (subjectUserId) - Loads grant + peer in a single DB join query (`getGrantWithPeer`), asserts `status === 'active'`, and validates cert serial against stored peer cert serial (defense-in-depth) - Attaches `FederationContext` to `request.federationContext` on success - All rejection paths use the federation wire-format error envelope from `@mosaicstack/types`, not raw NestJS exceptions - Guard is registered in `FederationModule` providers and exported for `@UseGuards()` in M3-05/06/07 verb controllers Closes #462 PRD AC-3 (cross-user isolation prerequisite): checked off -- the guard binds cert to grant to subjectUserId at the transport layer, preventing any cross-user data access by a peer that does not hold an active grant for the target user. ## Key decisions **Cert-serial check**: Kept. The stored `certSerial` on `federation_peers` is matched against the inbound cert serial. This is defense-in-depth: even if the mTLS CA is compromised or a TLS terminator forwards an arbitrary cert, the serial must match what was registered at enrollment. No additional DB round-trip needed (peer is loaded in the same join as the grant). **FastifyRequest typing**: `request.raw.socket` typed as `Partial<tls.TLSSocket>` -- presence of `getPeerCertificate` is checked at runtime, allowing safe fallback for plain HTTP connections in dev/test. **OID helper location**: `federation/oid.util.ts` (not inside `server/`) so it can be reused by future client-side verification without a circular dependency. ## Test plan - [x] `pnpm typecheck` -- 38 package typecheck tasks pass - [x] `pnpm lint` -- 21 lint tasks pass - [x] `pnpm format:check` -- all files use Prettier code style - [x] `pnpm --filter @mosaicstack/gateway test` -- 28 test files pass (450 tests), 5 skipped (DB integration) - [x] New guard tests: 11/11 pass (missing cert 401, parse failure 401, missing OIDs 401, grant not found 403, pending/revoked/expired status 403, serial mismatch 403, happy path context attach) Generated with Claude Code
jason.woltje added 1 commit 2026-04-24 03:21:43 +00:00
feat(federation): mTLS AuthGuard with OID-based grant resolution (FED-M3-03)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
53ee36239b
Adds FederationAuthGuard that validates inbound mTLS client certs on
federation API routes. Extracts custom OIDs (grantId, subjectUserId),
loads the grant+peer from DB in one query, asserts active status, and
validates cert serial as defense-in-depth. Attaches FederationContext
to requests on success and uses federation wire-format error envelopes
(not raw NestJS exceptions) for 401/403 responses.

New files:
- apps/gateway/src/federation/oid.util.ts — shared OID extraction (no dupe ASN.1 logic)
- apps/gateway/src/federation/server/federation-auth.guard.ts — guard impl
- apps/gateway/src/federation/server/federation-context.ts — FederationContext type + module augment
- apps/gateway/src/federation/server/index.ts — barrel export
- apps/gateway/src/federation/server/__tests__/federation-auth.guard.spec.ts — 11 unit tests

Modified:
- apps/gateway/src/federation/grants.service.ts — adds getGrantWithPeer() with join
- apps/gateway/src/federation/federation.module.ts — registers FederationAuthGuard as provider

Closes #462

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jason.woltje added 1 commit 2026-04-24 03:46:23 +00:00
fix(federation/auth-guard): remediate CRIT-1/CRIT-2 + HIGH-1..4 review findings
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
71c7b85026
- CRIT-1: Validate cert subjectUserId against grant.subjectUserId from DB;
  use authoritative DB value in FederationContext
- CRIT-2: Add @Inject(GrantsService) decorator (tsx/esbuild requirement)
- HIGH-1: Validate UTF8String TLV tag, length, and bounds in OID parser
- HIGH-2: Collapse all 403 wire messages to a generic string to prevent
  grant enumeration; keep internal logger detail
- HIGH-3: Assert federation wire envelope shape in all guard tests
- HIGH-4: Regression test for subjectUserId cert/DB mismatch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jason.woltje force-pushed feat/federation-m3-auth-guard from 71c7b85026 to 0af3e218a1 2026-04-25 11:34:59 +00:00 Compare
jason.woltje merged commit bd83f86740 into main 2026-04-25 13:27:21 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#509