feat(config+ws): add TLS cert reload, WS compression negotiation, WS JWT auth (E9+E10+E11)
E9: TLS Certificate Reload - Add TlsCertificateProvider with Interlocked-swappable cert field - New connections get current cert, existing connections keep theirs - ConfigReloader.ReloadTlsCertificate rebuilds SslServerAuthenticationOptions - NatsServer.ApplyConfigChanges triggers TLS reload on TLS config changes - 11 tests covering cert swap, versioning, thread safety, config diff E10: WebSocket Compression Negotiation (RFC 7692) - Add WsDeflateNegotiator to parse Sec-WebSocket-Extensions parameters - Parse server_no_context_takeover, client_no_context_takeover, server_max_window_bits, client_max_window_bits - WsDeflateParams record struct with ToResponseHeaderValue() - NATS always enforces no_context_takeover (matching Go server) - WsUpgrade returns negotiated WsDeflateParams in upgrade result - 22 tests covering parameter parsing, clamping, response headers E11: WebSocket JWT Authentication - Extract JWT from Authorization header (Bearer token), cookie, or ?jwt= query param - Priority: Authorization header > cookie > query parameter - WsUpgrade.TryUpgradeAsync now parses query string from request URI - Add FailUnauthorizedAsync for 401 responses - 24 tests covering all JWT extraction sources and priority ordering
This commit is contained in:
@@ -50,8 +50,9 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
private readonly Account _globalAccount;
|
||||
private readonly Account _systemAccount;
|
||||
private InternalEventSystem? _eventSystem;
|
||||
private readonly SslServerAuthenticationOptions? _sslOptions;
|
||||
private SslServerAuthenticationOptions? _sslOptions;
|
||||
private readonly TlsRateLimiter? _tlsRateLimiter;
|
||||
private readonly TlsCertificateProvider? _tlsCertProvider;
|
||||
private readonly SubjectTransform[] _subjectTransforms;
|
||||
private readonly RouteManager? _routeManager;
|
||||
|
||||
@@ -148,6 +149,8 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
|
||||
public void WaitForShutdown() => _shutdownComplete.Task.GetAwaiter().GetResult();
|
||||
|
||||
internal TlsCertificateProvider? TlsCertProviderForTest => _tlsCertProvider;
|
||||
|
||||
internal Task AcquireReloadLockForTestAsync() => _reloadMu.WaitAsync();
|
||||
|
||||
internal void ReleaseReloadLockForTest() => _reloadMu.Release();
|
||||
@@ -427,7 +430,9 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
|
||||
if (options.HasTls)
|
||||
{
|
||||
_tlsCertProvider = new TlsCertificateProvider(options.TlsCert!, options.TlsKey);
|
||||
_sslOptions = TlsHelper.BuildServerAuthOptions(options);
|
||||
_tlsCertProvider.SwapSslOptions(_sslOptions);
|
||||
|
||||
// OCSP stapling: build a certificate context so the runtime can
|
||||
// fetch and cache a fresh OCSP response and staple it during the
|
||||
@@ -1377,6 +1382,16 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
Connections = ClientCount,
|
||||
TotalConnections = Interlocked.Read(ref _stats.TotalConnections),
|
||||
Subscriptions = SubList.Count,
|
||||
Sent = new Events.DataStats
|
||||
{
|
||||
Msgs = Interlocked.Read(ref _stats.OutMsgs),
|
||||
Bytes = Interlocked.Read(ref _stats.OutBytes),
|
||||
},
|
||||
Received = new Events.DataStats
|
||||
{
|
||||
Msgs = Interlocked.Read(ref _stats.InMsgs),
|
||||
Bytes = Interlocked.Read(ref _stats.InBytes),
|
||||
},
|
||||
InMsgs = Interlocked.Read(ref _stats.InMsgs),
|
||||
OutMsgs = Interlocked.Read(ref _stats.OutMsgs),
|
||||
InBytes = Interlocked.Read(ref _stats.InBytes),
|
||||
@@ -1672,11 +1687,13 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
{
|
||||
bool hasLoggingChanges = false;
|
||||
bool hasAuthChanges = false;
|
||||
bool hasTlsChanges = false;
|
||||
|
||||
foreach (var change in changes)
|
||||
{
|
||||
if (change.IsLoggingChange) hasLoggingChanges = true;
|
||||
if (change.IsAuthChange) hasAuthChanges = true;
|
||||
if (change.IsTlsChange) hasTlsChanges = true;
|
||||
}
|
||||
|
||||
// Copy reloadable values from newOpts to _options
|
||||
@@ -1689,6 +1706,18 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
_logger.LogInformation("Logging configuration reloaded");
|
||||
}
|
||||
|
||||
if (hasTlsChanges)
|
||||
{
|
||||
// Reload TLS certificates: new connections get the new cert,
|
||||
// existing connections keep their original cert.
|
||||
// Reference: golang/nats-server/server/reload.go — tlsOption.Apply.
|
||||
if (ConfigReloader.ReloadTlsCertificate(_options, _tlsCertProvider))
|
||||
{
|
||||
_sslOptions = _tlsCertProvider!.GetCurrentSslOptions();
|
||||
_logger.LogInformation("TLS configuration reloaded");
|
||||
}
|
||||
}
|
||||
|
||||
if (hasAuthChanges)
|
||||
{
|
||||
// Rebuild auth service with new options, then propagate changes to connected clients
|
||||
@@ -1837,6 +1866,7 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
reg.Dispose();
|
||||
_quitCts.Dispose();
|
||||
_tlsRateLimiter?.Dispose();
|
||||
_tlsCertProvider?.Dispose();
|
||||
_listener?.Dispose();
|
||||
_wsListener?.Dispose();
|
||||
_routeManager?.DisposeAsync().AsTask().GetAwaiter().GetResult();
|
||||
|
||||
Reference in New Issue
Block a user