Files
natsnet/docs/plans/2026-02-26-stub-features-design.md

186 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Stub Features Implementation Design
**Date:** 2026-02-26
**Scope:** Complete the 93 remaining `stub` features in Phase 6
**Approach:** Two parallel sessions (Config + Auth)
## Overview
After Phase 6's 23 porting sessions, 93 features remain at `stub` status. They fall into two
independent concerns that can be implemented in parallel:
| Group | Go File | Stubs | Go LOC | Concern |
|-------|---------|-------|--------|---------|
| Config | `server/opts.go` | 67 | ~4,876 | Configuration file parsing / binding |
| Auth | `server/auth.go` | 19 | ~1,296 | Authentication dispatch |
| Auth | `server/auth_callout.go` | 3 | ~456 | External auth callout |
| Auth | `server/jwt.go` | 3 | ~137 | Operator JWT validation |
| Signals | `server/signal.go` | 1 | ~46 | OS signal handling |
---
## Session A: Configuration Binding (67 stubs, opts.go)
### Decision
Map all NATS server configuration to **`appsettings.json`** via
`Microsoft.Extensions.Configuration`. The Go `conf` package tokenizer and the 765-line
`processConfigFileLine` dispatch loop are **not ported** — JSON deserialization replaces them.
### New Files
**`Config/ServerOptionsConfiguration.cs`**
```csharp
public static class ServerOptionsConfiguration
{
public static ServerOptions ProcessConfigFile(string path);
public static ServerOptions ProcessConfigString(string json);
public static void BindConfiguration(IConfiguration config, ServerOptions target);
}
```
- `ProcessConfigFile` uses `new ConfigurationBuilder().AddJsonFile(path).Build()`
- `ProcessConfigString` uses `AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json)))`
- `BindConfiguration` calls `config.Bind(target)` with custom converters registered
**`Config/NatsJsonConverters.cs`**
Custom `JsonConverter<T>` for non-trivial types:
| Converter | Input | Output | Mirrors |
|-----------|-------|--------|---------|
| `DurationJsonConverter` | `"2s"`, `"100ms"`, `"1h30m"` | `TimeSpan` | `parseDuration` |
| `TlsVersionJsonConverter` | `"1.2"`, `"TLS12"` | `SslProtocols` | `parseTLSVersion` |
| `NatsUrlJsonConverter` | `"nats://host:port"` | validated `string` | `parseURL` |
| `StorageSizeJsonConverter` | `"1GB"`, `"512mb"` | `long` (bytes) | `getStorageSize` |
### ServerOptions.cs Changes
Add `[JsonPropertyName("...")]` attributes for fields whose JSON key names differ from C# names.
JSON key names follow NATS server conventions (lowercase, underscore-separated):
```json
{
"port": 4222,
"host": "0.0.0.0",
"tls": { "cert_file": "...", "key_file": "...", "ca_file": "..." },
"cluster": { "port": 6222, "name": "my-cluster" },
"gateway": { "port": 7222, "name": "my-gateway" },
"jetstream": { "store_dir": "/data/jetstream", "max_memory": "1GB" },
"leafnodes": { "port": 7422 },
"mqtt": { "port": 1883 },
"websocket": { "port": 8080 },
"accounts": [ { "name": "A", "users": [ { "user": "u1", "password": "p1" } ] } ]
}
```
### DB Outcome
All 67 opts.go stubs → `complete`:
- Feature IDs 25052574, 2580, 2584 (+ `configureSystemAccount` 2509, `setupUsersAndNKeysDuplicateCheckMap` 2515)
- `parse*` functions have no C# equivalent — their logic is subsumed by converters and JSON binding
---
## Session B: Auth Implementation (26 stubs)
### New Files
**`NatsServer.Auth.cs`** — `partial class NatsServer` with:
| Method | Go Equivalent | Notes |
|--------|--------------|-------|
| `ConfigureAuthorization()` | `configureAuthorization` | Builds `_nkeys`/`_users` dicts from `_opts` |
| `BuildNkeysAndUsersFromOptions()` | `buildNkeysAndUsersFromOptions` | Creates typed lookup maps |
| `CheckAuthforWarnings()` | `checkAuthforWarnings` | Validates auth config consistency |
| `AssignGlobalAccountToOrphanUsers()` | `assignGlobalAccountToOrphanUsers` | — |
| `CheckAuthentication(ClientConnection)` | `checkAuthentication` | Entry point |
| `IsClientAuthorized(ClientConnection)` | `isClientAuthorized` | Check user credentials |
| `ProcessClientOrLeafAuthentication(ClientConnection, ServerOptions)` | `processClientOrLeafAuthentication` | Main 554-line auth dispatch |
| `IsRouterAuthorized(ClientConnection)` | `isRouterAuthorized` | Route-specific auth |
| `IsGatewayAuthorized(ClientConnection)` | `isGatewayAuthorized` | Gateway-specific auth |
| `RegisterLeafWithAccount(ClientConnection, string)` | `registerLeafWithAccount` | — |
| `IsLeafNodeAuthorized(ClientConnection)` | `isLeafNodeAuthorized` | Leaf-specific auth |
| `ProcessProxiesTrustedKeys()` | `processProxiesTrustedKeys` | Proxy key setup |
| `ProxyCheck(ClientConnection, ServerOptions)` | `proxyCheck` | Validate proxy headers |
**Auth dispatch flow in `ProcessClientOrLeafAuthentication`:**
```
if callout configured → ProcessClientOrLeafCallout()
else if JWT bearer → JwtProcessor.ValidateAndRegisterUser()
else if NKey → verify NKey signature (NATS.NKeys NuGet)
else if user+password → BCrypt.Net.BCrypt.Verify() (BCrypt.Net-Next NuGet)
else if TLS cert map → CheckClientTlsCertSubject()
else if no-auth mode → allow (if opts.NoAuth)
→ set client account, permissions, labels
```
**`Auth/AuthCallout.cs`** — `partial class NatsServer` with:
- `ProcessClientOrLeafCallout(ClientConnection, ServerOptions)` — publishes to `$SYS.REQ.USER.AUTH`, waits for signed JWT response, validates it
- `FillClientInfo(AuthorizationRequestClaims, ClientConnection)` — populate auth request payload
- `FillConnectOpts(AuthorizationRequestClaims, ClientConnection)` — populate connect opts in payload
**`Auth/JwtProcessor.cs` additions:**
- `ReadOperatorJwt(string path)` — read operator JWT from file, decode `OperatorClaims`
- `ReadOperatorJwtInternal(string jwtString)` — decode from string
- `ValidateTrustedOperators(ServerOptions opts)` — walk operator → account → user signing key chain
**`Auth/AuthHandler.cs` additions:**
- `ProcessUserPermissionsTemplate(UserPermissionLimits, UserClaims, Account)` — expand `{{account}}`, `{{tag.*}}` template variables in JWT user permissions
- `GetTlsAuthDcs(X509DistinguishedName)` — extract DC= components from TLS cert RDN
- `CheckClientTlsCertSubject(ClientConnection, Func<string, bool>)` — TLS cert subject matching
- `ValidateProxies(ServerOptions)` — validate proxy configuration
- `GetAuthErrClosedState(ClientConnection)` — map auth failure to client closed state enum
### New NuGet Packages
| Package | Version | Purpose |
|---------|---------|---------|
| `BCrypt.Net-Next` | ≥4.0 | bcrypt password hashing and comparison |
| `NATS.NKeys` | ≥2.0 | NKey keypair creation, signature verify |
### `NatsServer.Signals.cs`
New partial class file:
```csharp
// Registers OS signal handlers via PosixSignalRegistration (cross-platform).
// SIGHUP → server.Reload()
// SIGTERM → server.Shutdown()
// SIGINT → server.Shutdown()
// Windows fallback: Console.CancelKeyPress → Shutdown()
```
### DB Outcome
All 26 auth/jwt/callout/signal stubs → `complete`:
- Feature IDs 354383, 19731976, 2584, 3156
---
## File Summary
| File | Action |
|------|--------|
| `Config/ServerOptionsConfiguration.cs` | CREATE |
| `Config/NatsJsonConverters.cs` | CREATE |
| `NatsServer.Auth.cs` | CREATE (partial) |
| `NatsServer.Signals.cs` | CREATE (partial) |
| `Auth/AuthCallout.cs` | CREATE (partial) |
| `Auth/JwtProcessor.cs` | MODIFY (add 3 methods) |
| `Auth/AuthHandler.cs` | MODIFY (add 5 methods) |
| `ServerOptions.cs` | MODIFY (add JsonPropertyName attrs) |
| `ZB.MOM.NatsNet.Server.csproj` | MODIFY (add 2 NuGet packages) |
---
## Testing
Unit tests in `ZB.MOM.NatsNet.Server.Tests/`:
- `Config/ServerOptionsConfigurationTests.cs` — round-trip JSON bind tests for each major option group
- `Auth/AuthHandlerTests.cs` additions — bcrypt comparison, NKey verify, TLS cert subject matching
- `Auth/JwtProcessorTests.cs` additions — operator JWT read/validate
No new test IDs needed — these are implementations of already-tracked Phase 6 features.
After implementation, relevant test IDs in Phase 7 will be marked complete.