From f533bf09451bd531d393b3d1a60eed9739c0da81 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 23 Feb 2026 04:12:45 -0500 Subject: [PATCH] docs: add design document for remaining lower-priority gaps Covers JWT authentication, subject mapping/transforms, OCSP support, Windows Service integration, per-subsystem logging, per-client trace, per-account stats, and TLS cert expiry in /varz. --- .../plans/2026-02-23-remaining-gaps-design.md | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 docs/plans/2026-02-23-remaining-gaps-design.md diff --git a/docs/plans/2026-02-23-remaining-gaps-design.md b/docs/plans/2026-02-23-remaining-gaps-design.md new file mode 100644 index 0000000..525499b --- /dev/null +++ b/docs/plans/2026-02-23-remaining-gaps-design.md @@ -0,0 +1,160 @@ +# Remaining Lower-Priority Gaps — Design Document + +**Goal:** Resolve all remaining lower-priority gaps from differences.md with full Go parity, bringing the .NET NATS server to feature completeness for single-server (non-clustered) deployments. + +**Approach:** Full Go parity — implement all listed gaps including JWT authentication, subject mapping, OCSP, and quick wins. + +--- + +## 1. JWT Authentication + +The largest component. NATS uses standard JWT encoding (base64url header.payload.signature) with Ed25519 signing via the `nats-io/jwt/v2` Go library. + +### New Files + +- **`Auth/Jwt/NatsJwt.cs`** — JWT decode/encode utilities. Base64url parsing, header extraction, Ed25519 signature verification via NATS.NKeys. Detects JWT by `"eyJ"` prefix. +- **`Auth/Jwt/UserClaims.cs`** — User claim record: `Subject` (nkey), `Issuer` (account nkey), `IssuerAccount` (scoped signer), `Name`, `Tags`, `BearerToken`, `Permissions` (pub/sub allow/deny), `ResponsePermission`, `Src` (allowed CIDRs), `Times` (time-based access), `AllowedConnectionTypes`, `IssuedAt`, `Expires`. +- **`Auth/Jwt/AccountClaims.cs`** — Account claim record: `Subject`, `Issuer` (operator nkey), `SigningKeys`, `Limits`, `Exports`, `Imports`. +- **`Auth/Jwt/PermissionTemplates.cs`** — Template expansion for all 6 mustache-style templates: + - `{{name()}}` → user's Name + - `{{subject()}}` → user's Subject (nkey) + - `{{tag(name)}}` → user tags matching `name:` prefix + - `{{account-name()}}` → account display name + - `{{account-subject()}}` → account nkey + - `{{account-tag(name)}}` → account tags matching prefix + - Cartesian product applied for multi-value tags. +- **`Auth/Jwt/AccountResolver.cs`** — `IAccountResolver` interface (`FetchAsync`, `StoreAsync`, `IsReadOnly`) + `MemAccountResolver` in-memory implementation. +- **`Auth/JwtAuthenticator.cs`** — Implements `IAuthenticator`. Flow: decode user JWT → resolve account → verify Ed25519 signature against nonce → expand permission templates → build `NkeyUser` → validate source IP + time restrictions → check user revocation. + +### Modified Files + +- **`Auth/Account.cs`** — Add `Nkey`, `Issuer`, `SigningKeys` (Dictionary), `RevokedUsers` (ConcurrentDictionary). +- **`NatsOptions.cs`** — Add `TrustedKeys` (string[]), `AccountResolver` (IAccountResolver). +- **`NatsClient.cs`** — Pass JWT + signature from CONNECT opts to authenticator. + +### Design Decisions + +- No external JWT NuGet — NATS JWTs are simple enough to parse inline (base64url + System.Text.Json + Ed25519 via NATS.NKeys). +- `MemAccountResolver` only — URL/directory resolvers are deployment infrastructure. +- User revocation: `ConcurrentDictionary` on Account (nkey → issuedAt; JWTs issued before revocation time are rejected). +- Source IP validation via `System.Net.IPNetwork.Contains()` (.NET 8+). + +--- + +## 2. Subject Mapping / Transforms + +Port Go's `subject_transform.go`. Configurable source pattern → destination template with function tokens. + +### New File + +- **`Subscriptions/SubjectTransform.cs`** — A transform has a source pattern (with wildcards) and a destination template with function tokens. On match, captured wildcard values are substituted into the destination. + +### Transform Functions + +| Function | Description | +|----------|-------------| +| `{{wildcard(n)}}` / `$n` | Replace with nth captured wildcard token (1-based) | +| `{{partition(num,tokens...)}}` | FNV-1a hash of captured tokens mod `num` | +| `{{split(token,delim)}}` | Split captured token by delimiter into subject tokens | +| `{{splitFromLeft(token,pos)}}` | Split token into two at position from left | +| `{{splitFromRight(token,pos)}}` | Split token into two at position from right | +| `{{sliceFromLeft(token,size)}}` | Slice token into fixed-size chunks from left | +| `{{sliceFromRight(token,size)}}` | Slice token into fixed-size chunks from right | +| `{{left(token,len)}}` | Take first `len` chars | +| `{{right(token,len)}}` | Take last `len` chars | + +### Integration + +- `NatsOptions.SubjectMappings` — `Dictionary` of source→destination rules. +- Transforms compiled at config time into token operation lists (no runtime regex). +- Applied in `NatsServer.DeliverMessage` before subject matching. +- Account-level mappings for import/export rewriting. + +--- + +## 3. OCSP Support + +Two dimensions: peer verification (client cert revocation checking) and stapling (server cert status). + +### New File + +- **`Tls/OcspConfig.cs`** — `OcspMode` enum (`Auto`, `Always`, `Must`, `Never`) + `OcspConfig` record with `Mode` and `OverrideUrls`. + +### Peer Verification + +- Modify `TlsConnectionWrapper` to set `X509RevocationMode.Online` when `OcspPeerVerify` is true. +- Checks CRL/OCSP during TLS handshake for client certificates. + +### OCSP Stapling + +- Build `SslStreamCertificateContext.Create(cert, chain, offline: false)` at startup — .NET fetches OCSP response automatically. +- Pass to `SslServerAuthenticationOptions.ServerCertificateContext`. +- `Must` mode: verify OCSP response obtained; fail startup if not. + +### Modified Files + +- `NatsOptions.cs` — `OcspConfig` and `OcspPeerVerify` properties. +- `TlsConnectionWrapper.cs` — Peer verification in cert validation callback. +- `NatsServer.cs` — `SslStreamCertificateContext` with OCSP at startup. +- `VarzHandler.cs` — Populate `TlsOcspPeerVerify` field. + +--- + +## 4. Quick Wins + +### A. Windows Service Integration + +- Add `Microsoft.Extensions.Hosting.WindowsServices` NuGet. +- Detect `--service` flag in `Program.cs`, call `UseWindowsService()`. +- .NET generic host handles service lifecycle automatically. + +### B. Per-Subsystem Log Control + +- `NatsOptions.LogOverrides` — `Dictionary` mapping namespace→level. +- CLI: `--log_level_override "NATS.Server.Protocol=Trace"` (repeatable). +- Serilog: `MinimumLevel.Override(namespace, level)` per entry. + +### C. Per-Client Trace Mode + +- `TraceMode` flag in `ClientFlagHolder`. +- When set, parser receives logger regardless of global `options.Trace`. +- Connz response includes `trace` boolean per connection. + +### D. Per-Account Stats + +- `long _inMsgs, _outMsgs, _inBytes, _outBytes` on `Account` with `Interlocked`. +- `IncrementInbound(long bytes)` / `IncrementOutbound(long bytes)`. +- Called from `DeliverMessage` (outbound) and message processing (inbound). +- `/accountz` endpoint returns per-account stats. + +### E. TLS Certificate Expiry in /varz + +- In `VarzHandler`, read server TLS cert `NotAfter`. +- Populate existing `TlsCertNotAfter` field. + +### F. differences.md Update + +- Mark all resolved features as Y with notes. +- Update summary section. +- Correct any stale entries. + +--- + +## Task Dependencies + +``` +Independent (parallelizable): + - JWT Authentication (#23) + - Subject Mapping (#24) + - OCSP Support (#25) + - Windows Service (#26) + - Per-Subsystem Logging (#27) + - Per-Client Trace (#28) + - Per-Account Stats (#29) + - TLS Cert Expiry (#30) + +Dependent: + - Update differences.md (#31) — blocked by all above +``` + +Most tasks can run in parallel since they touch different files. JWT and Subject Mapping are the two largest. The quick wins (26-30) are all independent of each other and of the larger tasks.