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:
89
src/NATS.Server/Tls/TlsCertificateProvider.cs
Normal file
89
src/NATS.Server/Tls/TlsCertificateProvider.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
// TLS certificate provider that supports atomic cert swapping for hot reload.
|
||||
// New connections get the current certificate; existing connections keep their original.
|
||||
// Reference: golang/nats-server/server/reload.go — tlsOption.Apply.
|
||||
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace NATS.Server.Tls;
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe provider for TLS certificates that supports atomic swapping
|
||||
/// during config reload. New connections retrieve the latest certificate via
|
||||
/// <see cref="GetCurrentCertificate"/>; existing connections are unaffected.
|
||||
/// </summary>
|
||||
public sealed class TlsCertificateProvider : IDisposable
|
||||
{
|
||||
private volatile X509Certificate2? _currentCert;
|
||||
private volatile SslServerAuthenticationOptions? _currentSslOptions;
|
||||
private int _version;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new provider and loads the initial certificate from the given paths.
|
||||
/// </summary>
|
||||
public TlsCertificateProvider(string certPath, string? keyPath)
|
||||
{
|
||||
_currentCert = TlsHelper.LoadCertificate(certPath, keyPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a provider from a pre-loaded certificate (for testing).
|
||||
/// </summary>
|
||||
public TlsCertificateProvider(X509Certificate2 cert)
|
||||
{
|
||||
_currentCert = cert;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current certificate. This is called for each new TLS handshake
|
||||
/// so that new connections always get the latest certificate.
|
||||
/// </summary>
|
||||
public X509Certificate2? GetCurrentCertificate() => _currentCert;
|
||||
|
||||
/// <summary>
|
||||
/// Atomically swaps the current certificate with a newly loaded one.
|
||||
/// Returns the old certificate (caller may dispose it after existing connections drain).
|
||||
/// </summary>
|
||||
public X509Certificate2? SwapCertificate(string certPath, string? keyPath)
|
||||
{
|
||||
var newCert = TlsHelper.LoadCertificate(certPath, keyPath);
|
||||
return SwapCertificate(newCert);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Atomically swaps the current certificate with the provided one.
|
||||
/// Returns the old certificate.
|
||||
/// </summary>
|
||||
public X509Certificate2? SwapCertificate(X509Certificate2 newCert)
|
||||
{
|
||||
var old = Interlocked.Exchange(ref _currentCert, newCert);
|
||||
Interlocked.Increment(ref _version);
|
||||
return old;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current SSL options, rebuilding them if the certificate has changed.
|
||||
/// </summary>
|
||||
public SslServerAuthenticationOptions? GetCurrentSslOptions() => _currentSslOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Atomically swaps the SSL server authentication options.
|
||||
/// Called after TLS config changes are detected during reload.
|
||||
/// </summary>
|
||||
public void SwapSslOptions(SslServerAuthenticationOptions newOptions)
|
||||
{
|
||||
Interlocked.Exchange(ref _currentSslOptions, newOptions);
|
||||
Interlocked.Increment(ref _version);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monotonically increasing version number, incremented on each swap.
|
||||
/// Useful for tests to verify a reload occurred.
|
||||
/// </summary>
|
||||
public int Version => Volatile.Read(ref _version);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_currentCert?.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user