feat: add WebSocket-specific TLS configuration (Gap 15.1)

This commit is contained in:
Joseph Doherty
2026-02-25 13:14:47 -05:00
parent 4b9384dfcf
commit 74473d81cf
2 changed files with 197 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
namespace NATS.Server.WebSocket;
/// <summary>
/// Separate TLS configuration for the WebSocket listener,
/// allowing different certificates than the main NATS listener.
/// Go reference: server/websocket.go — wsTLSConfig.
/// </summary>
public sealed class WebSocketTlsConfig
{
public string? CertFile { get; set; }
public string? KeyFile { get; set; }
public string? CaFile { get; set; }
public bool RequireClientCert { get; set; }
public bool InsecureSkipVerify { get; set; }
/// <summary>Returns true when a certificate file has been specified.</summary>
public bool IsConfigured => !string.IsNullOrWhiteSpace(CertFile);
/// <summary>
/// Validates the TLS configuration.
/// An empty configuration (no cert, no key) is valid and means no TLS.
/// When either cert or key is specified, both must be provided.
/// </summary>
public WebSocketTlsValidationResult Validate()
{
var errors = new List<string>();
bool hasCert = !string.IsNullOrWhiteSpace(CertFile);
bool hasKey = !string.IsNullOrWhiteSpace(KeyFile);
if (hasKey && !hasCert)
errors.Add("CertFile must be specified when KeyFile is set.");
if (hasCert && !hasKey)
errors.Add("KeyFile must be specified when CertFile is set.");
return new WebSocketTlsValidationResult(errors.Count == 0, errors);
}
/// <summary>
/// Returns true when this configuration differs from <paramref name="other"/>.
/// Used for hot-reload diff detection.
/// </summary>
public bool HasChangedFrom(WebSocketTlsConfig? other)
{
if (other is null)
return true;
return CertFile != other.CertFile
|| KeyFile != other.KeyFile
|| CaFile != other.CaFile
|| RequireClientCert != other.RequireClientCert
|| InsecureSkipVerify != other.InsecureSkipVerify;
}
/// <summary>
/// Creates a <see cref="WebSocketTlsConfig"/> from the WebSocket-specific TLS fields
/// in <paramref name="options"/>, or returns <c>null</c> if no WebSocket TLS is configured.
/// </summary>
public static WebSocketTlsConfig? FromOptions(NatsOptions options)
{
var ws = options.WebSocket;
if (string.IsNullOrWhiteSpace(ws.TlsCert) && string.IsNullOrWhiteSpace(ws.TlsKey))
return null;
return new WebSocketTlsConfig
{
CertFile = ws.TlsCert,
KeyFile = ws.TlsKey,
};
}
}
/// <summary>Result of <see cref="WebSocketTlsConfig.Validate"/>.</summary>
public sealed record WebSocketTlsValidationResult(
bool IsValid,
IReadOnlyList<string> Errors);