feat: enforce jwt allowed connection types with go-compatible semantics

This commit is contained in:
Joseph Doherty
2026-02-23 05:43:46 -05:00
parent e562077e4c
commit 4a242f614f
4 changed files with 56 additions and 1 deletions

View File

@@ -1,4 +1,5 @@
using System.Security.Cryptography.X509Certificates;
using NATS.Server.Auth.Jwt;
using NATS.Server.Protocol;
namespace NATS.Server.Auth;
@@ -19,5 +20,5 @@ public sealed class ClientAuthContext
/// Used by JWT authenticator to enforce allowed_connection_types claims.
/// Defaults to "STANDARD" for regular NATS client connections.
/// </summary>
public string ConnectionType { get; init; } = "STANDARD";
public string ConnectionType { get; init; } = JwtConnectionTypes.Standard;
}

View File

@@ -0,0 +1,34 @@
namespace NATS.Server.Auth.Jwt;
internal static class JwtConnectionTypes
{
public const string Standard = "STANDARD";
public const string Websocket = "WEBSOCKET";
public const string Leafnode = "LEAFNODE";
public const string LeafnodeWs = "LEAFNODE_WS";
public const string Mqtt = "MQTT";
public const string MqttWs = "MQTT_WS";
public const string InProcess = "INPROCESS";
private static readonly HashSet<string> Known =
[
Standard, Websocket, Leafnode, LeafnodeWs, Mqtt, MqttWs, InProcess,
];
public static (HashSet<string> Valid, bool HasUnknown) Convert(IEnumerable<string>? values)
{
var valid = new HashSet<string>(StringComparer.Ordinal);
var hasUnknown = false;
if (values is null) return (valid, false);
foreach (var raw in values)
{
var up = (raw ?? string.Empty).Trim().ToUpperInvariant();
if (up.Length == 0) continue;
if (Known.Contains(up)) valid.Add(up);
else hasUnknown = true;
}
return (valid, hasUnknown);
}
}

View File

@@ -95,6 +95,24 @@ public sealed class JwtAuthenticator : IAuthenticator
}
}
// 7b. Check allowed connection types
var (allowedTypes, hasUnknown) = JwtConnectionTypes.Convert(userClaims.Nats?.AllowedConnectionTypes);
if (allowedTypes.Count == 0)
{
if (hasUnknown)
return null; // unknown-only list should reject
}
else
{
var connType = string.IsNullOrWhiteSpace(context.ConnectionType)
? JwtConnectionTypes.Standard
: context.ConnectionType.ToUpperInvariant();
if (!allowedTypes.Contains(connType))
return null;
}
// 8. Build permissions from JWT claims
Permissions? permissions = null;
var nats = userClaims.Nats;

View File

@@ -8,6 +8,7 @@ using System.Text.Json;
using System.Threading.Channels;
using Microsoft.Extensions.Logging;
using NATS.Server.Auth;
using NATS.Server.Auth.Jwt;
using NATS.Server.Protocol;
using NATS.Server.Subscriptions;
using NATS.Server.Tls;
@@ -388,6 +389,7 @@ public sealed class NatsClient : IDisposable
Opts = ClientOpts,
Nonce = _nonce ?? [],
ClientCertificate = TlsState?.PeerCert,
ConnectionType = JwtConnectionTypes.Standard,
};
authResult = _authService.Authenticate(context);