Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for auth in URL #670

Merged
merged 14 commits into from
Nov 5, 2024
56 changes: 55 additions & 1 deletion src/NATS.Client.Core/NatsConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public NatsConnection()
public NatsConnection(NatsOpts opts)
{
_logger = opts.LoggerFactory.CreateLogger<NatsConnection>();
Opts = opts;
Opts = ReadUserInfoFromConnectionString(opts);
ConnectionState = NatsConnectionState.Closed;
_waitForOpenConnection = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
_disposedCancellationTokenSource = new CancellationTokenSource();
Expand Down Expand Up @@ -289,6 +289,60 @@ internal ValueTask UnsubscribeAsync(int sid)
return default;
}

private static NatsOpts ReadUserInfoFromConnectionString(NatsOpts opts)
{
var first = true;

var natsUris = opts.GetSeedUris();
var maskedUris = new List<string>(natsUris.Length);

foreach (var natsUri in natsUris)
{
var uriBuilder = new UriBuilder(natsUri.Uri);

if (uriBuilder.UserName is { Length: > 0 } username)
{
if (first)
{
first = false;

if (uriBuilder.Password is { Length: > 0 } password)
{
opts = opts with
{
AuthOpts = opts.AuthOpts with
{
Username = uriBuilder.UserName,
Password = uriBuilder.Password,
},
};

uriBuilder.Password = "***"; // to redact the password from logs
}
else
{
opts = opts with
{
AuthOpts = opts.AuthOpts with
{
Token = uriBuilder.UserName,
},
};

uriBuilder.UserName = "***"; // to redact the token from logs
}
}
}

maskedUris.Add(uriBuilder.ToString().TrimEnd('/'));
}

var combinedUri = string.Join(",", maskedUris);
opts = opts with { Url = combinedUri };

return opts;
}

private async ValueTask InitialConnectAsync()
{
Debug.Assert(ConnectionState == NatsConnectionState.Connecting, "Connection state");
Expand Down
32 changes: 26 additions & 6 deletions tests/NATS.Client.Core.Tests/ClusterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,43 @@ namespace NATS.Client.Core.Tests;

public class ClusterTests(ITestOutputHelper output)
{
[Fact]
public async Task Seed_urls_on_retry()
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task Seed_urls_on_retry(bool userAuthInUrl)
{
await using var cluster1 = new NatsCluster(
new NullOutputHelper(),
TransportType.Tcp,
(i, b) => b.WithServerName($"c1n{i}"));
(i, b) =>
{
b.WithServerName($"c1n{i}");
if (userAuthInUrl)
{
b.AddServerConfig("resources/configs/auth/password.conf");
b.WithClientUrlAuthentication("a", "b");
}
},
userAuthInUrl);

await using var cluster2 = new NatsCluster(
new NullOutputHelper(),
TransportType.Tcp,
(i, b) => b.WithServerName($"c2n{i}"));
(i, b) =>
{
b.WithServerName($"c2n{i}");
if (userAuthInUrl)
{
b.AddServerConfig("resources/configs/auth/password.conf");
b.WithClientUrlAuthentication("a", "b");
}
},
userAuthInUrl);

// Use the first node from each cluster as the seed
// so that we can confirm seeds are used on retry
var url1 = cluster1.Server1.ClientUrl;
var url2 = cluster2.Server1.ClientUrl;
var url1 = userAuthInUrl ? cluster1.Server1.ClientUrlWithAuth : cluster1.Server1.ClientUrl;
var url2 = userAuthInUrl ? cluster2.Server1.ClientUrlWithAuth : cluster2.Server1.ClientUrl;

await using var nats = new NatsConnection(new NatsOpts
{
Expand Down
44 changes: 36 additions & 8 deletions tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
NatsOpts.Default with { AuthOpts = NatsAuthOpts.Default with { Token = "s3cr3t", }, }),
};

yield return new object[]
{
new Auth(
"TOKEN_IN_CONNECTIONSTRING",
"resources/configs/auth/token.conf",
NatsOpts.Default,
urlAuth: "s3cr3t"),
};

yield return new object[]
{
new Auth(
Expand All @@ -23,6 +32,15 @@
}),
};

yield return new object[]
{
new Auth(
"USER-PASSWORD_IN_CONNECTIONSTRING",
"resources/configs/auth/password.conf",
NatsOpts.Default,
urlAuth: "a:b"),
};

yield return new object[]
{
new Auth(
Expand Down Expand Up @@ -84,15 +102,22 @@
var name = auth.Name;
var serverConfig = auth.ServerConfig;
var clientOpts = auth.ClientOpts;
var useAuthInUrl = !string.IsNullOrEmpty(auth.UrlAuth);

_output.WriteLine($"AUTH TEST {name}");

var serverOpts = new NatsServerOptsBuilder()
var serverOptsBuilder = new NatsServerOptsBuilder()
.UseTransport(_transportType)
.AddServerConfig(serverConfig)
.Build();
.AddServerConfig(serverConfig);

if (useAuthInUrl)
{
serverOptsBuilder.WithClientUrlAuthentication(auth.UrlAuth);
}

await using var server = NatsServer.Start(_output, serverOpts, clientOpts);
var serverOpts = serverOptsBuilder.Build();

await using var server = NatsServer.Start(_output, serverOpts, clientOpts, useAuthInUrl);

var subject = Guid.NewGuid().ToString("N");

Expand All @@ -104,8 +129,8 @@
Assert.Contains("Authorization Violation", natsException.GetBaseException().Message);
}

await using var subConnection = server.CreateClientConnection(clientOpts);
await using var pubConnection = server.CreateClientConnection(clientOpts);
await using var subConnection = server.CreateClientConnection(clientOpts, useAuthInUrl: useAuthInUrl);
await using var pubConnection = server.CreateClientConnection(clientOpts, useAuthInUrl: useAuthInUrl);

var signalComplete1 = new WaitSignal();
var signalComplete2 = new WaitSignal();
Expand Down Expand Up @@ -141,7 +166,7 @@
await disconnectSignal2;

_output.WriteLine("START NEW SERVER");
await using var newServer = NatsServer.Start(_output, serverOpts, clientOpts);
await using var newServer = NatsServer.Start(_output, serverOpts, clientOpts, useAuthInUrl);
await subConnection.ConnectAsync(); // wait open again
await pubConnection.ConnectAsync(); // wait open again

Expand All @@ -162,11 +187,12 @@

public class Auth
{
public Auth(string name, string serverConfig, NatsOpts clientOpts)
public Auth(string name, string serverConfig, NatsOpts clientOpts, string urlAuth = null)

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Windows (main)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Windows (main)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Windows (main)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Windows (main)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Windows (v2.9)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Windows (v2.9)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Windows (v2.9)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Windows (v2.9)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Windows (latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Windows (latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Windows (latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Windows (latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Linux (latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Linux (latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Linux (latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Linux (latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Linux (main)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Linux (main)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Linux (main)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Linux (main)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Linux (v2.9)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Linux (v2.9)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Linux (v2.9)

Cannot convert null literal to non-nullable reference type.

Check warning on line 190 in tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs

View workflow job for this annotation

GitHub Actions / Linux (v2.9)

Cannot convert null literal to non-nullable reference type.
{
Name = name;
ServerConfig = serverConfig;
ClientOpts = clientOpts;
UrlAuth = urlAuth;
}

public string Name { get; }
Expand All @@ -175,6 +201,8 @@

public NatsOpts ClientOpts { get; }

public string UrlAuth { get; }

public override string ToString() => Name;
}
}
43 changes: 32 additions & 11 deletions tests/NATS.Client.TestUtilities/NatsServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,22 @@
_ => throw new ArgumentOutOfRangeException(),
};

public string ClientUrlWithAuth
{
get
{
if (!string.IsNullOrEmpty(Opts.ClientUrlUserName))
{
var uriBuilder = new UriBuilder(ClientUrl);
uriBuilder.UserName = Opts.ClientUrlUserName;
uriBuilder.Password = Opts.ClientUrlPassword;
return uriBuilder.ToString().TrimEnd('/');
}

return ClientUrl;
}
}

public int ConnectionPort
{
get
Expand Down Expand Up @@ -134,7 +150,7 @@
public static NatsServer Start(ITestOutputHelper outputHelper, TransportType transportType) =>
Start(outputHelper, new NatsServerOptsBuilder().UseTransport(transportType).Build());

public static NatsServer Start(ITestOutputHelper outputHelper, NatsServerOpts opts, NatsOpts? clientOpts = default)
public static NatsServer Start(ITestOutputHelper outputHelper, NatsServerOpts opts, NatsOpts? clientOpts = default, bool useAuthInUrl = false)
{
NatsServer? server = null;
NatsConnection? nats = null;
Expand All @@ -144,7 +160,7 @@
{
server = new NatsServer(outputHelper, opts);
server.StartServerProcess();
nats = server.CreateClientConnection(clientOpts ?? NatsOpts.Default, reTryCount: 3);
nats = server.CreateClientConnection(clientOpts ?? GetDefaultClientOpts(server), reTryCount: 3, useAuthInUrl: useAuthInUrl);
#pragma warning disable CA2012
return server;
}
Expand All @@ -162,7 +178,12 @@
throw new Exception("Can't start nats-server and connect to it");
}

private static NatsOpts GetDefaultClientOpts(NatsServer server)
{
return NatsOpts.Default/* with { Url = server.ClientUrl }*/;
}

public void StartServerProcess()

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / test (main)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / test (main)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / test (latest)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / test (latest)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / test (v2.9)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / test (v2.9)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / check

'public' members should come before 'private' members

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / check

'public' members should come before 'private' members

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Windows (main)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Windows (main)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Windows (main)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Windows (main)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Windows (v2.9)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Windows (v2.9)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Windows (v2.9)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Windows (v2.9)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Windows (latest)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Windows (latest)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Windows (latest)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Windows (latest)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Linux (latest)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Linux (latest)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Linux (latest)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Linux (latest)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Linux (main)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Linux (main)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Linux (main)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Linux (main)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Linux (v2.9)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Linux (v2.9)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Linux (v2.9)

Check warning on line 186 in tests/NATS.Client.TestUtilities/NatsServer.cs

View workflow job for this annotation

GitHub Actions / Linux (v2.9)

{
_cancellationTokenSource = new CancellationTokenSource();

Expand Down Expand Up @@ -335,13 +356,13 @@
return (client, proxy);
}

public NatsConnection CreateClientConnection(NatsOpts? options = default, int reTryCount = 10, bool ignoreAuthorizationException = false, bool testLogger = true)
public NatsConnection CreateClientConnection(NatsOpts? options = default, int reTryCount = 10, bool ignoreAuthorizationException = false, bool testLogger = true, bool useAuthInUrl = false)
{
for (var i = 0; i < reTryCount; i++)
{
try
{
var nats = new NatsConnection(ClientOpts(options ?? NatsOpts.Default, testLogger: testLogger));
var nats = new NatsConnection(ClientOpts(options ?? GetDefaultClientOpts(this), testLogger: testLogger, useAuthInUrl: useAuthInUrl));

try
{
Expand Down Expand Up @@ -369,14 +390,14 @@
throw new Exception("Can't create a connection to nats-server");
}

public NatsConnectionPool CreatePooledClientConnection() => CreatePooledClientConnection(NatsOpts.Default);
public NatsConnectionPool CreatePooledClientConnection() => CreatePooledClientConnection(GetDefaultClientOpts(this));

public NatsConnectionPool CreatePooledClientConnection(NatsOpts opts)
{
return new NatsConnectionPool(4, ClientOpts(opts));
}

public NatsOpts ClientOpts(NatsOpts opts, bool testLogger = true)
public NatsOpts ClientOpts(NatsOpts opts, bool testLogger = true, bool useAuthInUrl = false)
{
var natsTlsOpts = Opts.EnableTls
? opts.TlsOpts with
Expand All @@ -392,7 +413,7 @@
{
LoggerFactory = testLogger ? _loggerFactory : opts.LoggerFactory,
TlsOpts = natsTlsOpts,
Url = ClientUrl,
Url = useAuthInUrl ? ClientUrlWithAuth : ClientUrl,
};
}

Expand Down Expand Up @@ -464,7 +485,7 @@
{
private readonly ITestOutputHelper _outputHelper;

public NatsCluster(ITestOutputHelper outputHelper, TransportType transportType, Action<int, NatsServerOptsBuilder>? configure = default)
public NatsCluster(ITestOutputHelper outputHelper, TransportType transportType, Action<int, NatsServerOptsBuilder>? configure = default, bool useAuthInUrl = false)
{
_outputHelper = outputHelper;

Expand Down Expand Up @@ -516,13 +537,13 @@
}

_outputHelper.WriteLine($"Starting server 1...");
Server1 = NatsServer.Start(outputHelper, opts1);
Server1 = NatsServer.Start(outputHelper, opts1, useAuthInUrl: useAuthInUrl);

_outputHelper.WriteLine($"Starting server 2...");
Server2 = NatsServer.Start(outputHelper, opts2);
Server2 = NatsServer.Start(outputHelper, opts2, useAuthInUrl: useAuthInUrl);

_outputHelper.WriteLine($"Starting server 3...");
Server3 = NatsServer.Start(outputHelper, opts3);
Server3 = NatsServer.Start(outputHelper, opts3, useAuthInUrl: useAuthInUrl);
}

public NatsServer Server1 { get; }
Expand Down
23 changes: 23 additions & 0 deletions tests/NATS.Client.TestUtilities/NatsServerOpts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public sealed class NatsServerOptsBuilder
private bool _serverDisposeReturnsPorts;
private bool _enableClustering;
private bool _trace;
private string? _clientUrlUserName;
private string? _clientUrlPassword;

public NatsServerOpts Build() => new()
{
Expand All @@ -40,6 +42,8 @@ public sealed class NatsServerOptsBuilder
TlsVerify = _tlsVerify,
EnableJetStream = _enableJetStream,
ServerName = _serverName,
ClientUrlUserName = _clientUrlUserName,
ClientUrlPassword = _clientUrlPassword,
TlsServerCertFile = _tlsServerCertFile,
TlsServerKeyFile = _tlsServerKeyFile,
TlsClientCertFile = _tlsClientCertFile,
Expand Down Expand Up @@ -110,6 +114,21 @@ public NatsServerOptsBuilder WithServerName(string serverName)
return this;
}

public NatsServerOptsBuilder WithClientUrlAuthentication(string userName, string password)
{
_clientUrlUserName = userName;
_clientUrlPassword = password;
return this;
}

public NatsServerOptsBuilder WithClientUrlAuthentication(string authInfo)
{
var infoParts = authInfo.Split(':');
_clientUrlUserName = infoParts.FirstOrDefault();
_clientUrlPassword = infoParts.ElementAtOrDefault(1);
return this;
}

public NatsServerOptsBuilder UseJetStream()
{
_enableJetStream = true;
Expand Down Expand Up @@ -176,6 +195,10 @@ public NatsServerOpts()

public bool ServerDisposeReturnsPorts { get; init; } = true;

public string? ClientUrlUserName { get; set; }

public string? ClientUrlPassword { get; set; }

public string? TlsClientCertFile { get; init; }

public string? TlsClientKeyFile { get; init; }
Expand Down
Loading