Files
natsdotnet/docs/plans/2026-02-23-remaining-gaps-design.md
Joseph Doherty f533bf0945 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.
2026-02-23 04:12:45 -05:00

7.1 KiB

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.csIAccountResolver 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<string, long>).
  • 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<string, long> 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.SubjectMappingsDictionary<string, string> 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.csOcspMode 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.csOcspConfig and OcspPeerVerify properties.
  • TlsConnectionWrapper.cs — Peer verification in cert validation callback.
  • NatsServer.csSslStreamCertificateContext 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.LogOverridesDictionary<string, string> 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.