Skip to content

Commit af2acea

Browse files
committed
Merge branch 'main' into rd_decimal
2 parents e509af7 + 321ec73 commit af2acea

File tree

5 files changed

+97
-6
lines changed

5 files changed

+97
-6
lines changed

src/dummy-http-server/DummyHttpServer.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
using System.Text;
3030
using FastEndpoints;
3131
using FastEndpoints.Security;
32+
using Microsoft.AspNetCore.Server.Kestrel.Https;
3233

3334
namespace dummy_http_server;
3435

@@ -42,7 +43,7 @@ public class DummyHttpServer : IDisposable
4243
private readonly TimeSpan? _withStartDelay;
4344

4445
public DummyHttpServer(bool withTokenAuth = false, bool withBasicAuth = false, bool withRetriableError = false,
45-
bool withErrorMessage = false, TimeSpan? withStartDelay = null)
46+
bool withErrorMessage = false, TimeSpan? withStartDelay = null, bool requireClientCert = false)
4647
{
4748
var bld = WebApplication.CreateBuilder();
4849

@@ -71,6 +72,15 @@ public DummyHttpServer(bool withTokenAuth = false, bool withBasicAuth = false, b
7172
bld.Services.AddHealthChecks();
7273
bld.WebHost.ConfigureKestrel(o =>
7374
{
75+
if (requireClientCert)
76+
{
77+
o.ConfigureHttpsDefaults(https =>
78+
{
79+
https.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
80+
https.AllowAnyClientCertificate();
81+
});
82+
}
83+
7484
o.Limits.MaxRequestBodySize = 1073741824;
7585
o.ListenLocalhost(29474,
7686
options => { options.UseHttps(); });
@@ -253,4 +263,4 @@ public string PrintBuffer()
253263
sb.Append(Encoding.UTF8.GetString(bytes, lastAppend, i - lastAppend));
254264
return sb.ToString();
255265
}
256-
}
266+
}

src/net-questdb-client-tests/HttpTests.cs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
******************************************************************************/
2424

2525

26+
using System.Security.Cryptography.X509Certificates;
2627
using System.Text;
2728
using dummy_http_server;
2829
using NUnit.Framework;
@@ -1676,4 +1677,47 @@ await sender.Table("table name")
16761677
// ReSharper disable once DisposeOnUsingVariable
16771678
srv.Dispose();
16781679
}
1679-
}
1680+
1681+
[Test]
1682+
public async Task SendWithCert()
1683+
{
1684+
#if NET9_0_OR_GREATER
1685+
using var cert = X509CertificateLoader.LoadPkcs12FromFile("certificate.pfx", null);
1686+
#else
1687+
using var cert = new X509Certificate2("certificate.pfx", (string?)null);
1688+
#endif
1689+
1690+
Assert.NotNull(cert);
1691+
1692+
using var server = new DummyHttpServer(requireClientCert: true);
1693+
await server.StartAsync(HttpsPort);
1694+
using var sender = Sender.Configure($"https::addr=localhost:{HttpsPort};tls_verify=unsafe_off;")
1695+
.WithClientCert(cert)
1696+
.Build();
1697+
1698+
await sender.Table("metrics")
1699+
.Symbol("tag", "value")
1700+
.Column("number", 12.2)
1701+
.AtAsync(new DateTime(1970, 01, 01, 0, 0, 1));
1702+
1703+
await sender.SendAsync();
1704+
Assert.That(
1705+
server.PrintBuffer(),
1706+
Is.EqualTo("metrics,tag=value number=12.2 1000000000\n"));
1707+
await server.StopAsync();
1708+
}
1709+
1710+
[Test]
1711+
public async Task FailsWhenExpectingCert()
1712+
{
1713+
using var server = new DummyHttpServer(requireClientCert: true);
1714+
await server.StartAsync(HttpsPort);
1715+
1716+
Assert.That(
1717+
() => Sender.Configure($"https::addr=localhost:{HttpsPort};tls_verify=unsafe_off;").Build(),
1718+
Throws.TypeOf<IngressError>().With.Message.Contains("ServerFlushError")
1719+
);
1720+
1721+
await server.StopAsync();
1722+
}
1723+
}

src/net-questdb-client/Senders/HttpSender.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ private void Build()
114114
_handler.SslOptions.ClientCertificates.Add(
115115
X509Certificate2.CreateFromPemFile(Options.tls_roots!, Options.tls_roots_password));
116116
}
117+
118+
if (Options.client_cert is not null)
119+
{
120+
_handler.SslOptions.ClientCertificates ??= new X509Certificate2Collection();
121+
_handler.SslOptions.ClientCertificates.Add(Options.client_cert);
122+
}
117123
}
118124

119125
_handler.ConnectTimeout = Options.auth_timeout;
@@ -612,4 +618,4 @@ public override void Dispose()
612618
Buffer.Clear();
613619
Buffer.TrimExcessBuffers();
614620
}
615-
}
621+
}

src/net-questdb-client/Senders/TcpSender.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
using System.Buffers.Text;
2828
using System.Net.Security;
2929
using System.Net.Sockets;
30+
using System.Security.Cryptography.X509Certificates;
3031
using QuestDB.Enums;
3132
using QuestDB.Utils;
3233
using ProtocolType = QuestDB.Enums.ProtocolType;
@@ -83,6 +84,12 @@ private void Build()
8384
Options.tls_verify == TlsVerifyType.unsafe_off ? AllowAllCertCallback : null,
8485
};
8586

87+
if (Options.client_cert is not null)
88+
{
89+
sslOptions.ClientCertificates ??= new X509CertificateCollection();
90+
sslOptions.ClientCertificates.Add(Options.client_cert);
91+
}
92+
8693
sslStream.AuthenticateAsClient(sslOptions);
8794
if (!sslStream.IsEncrypted)
8895
{
@@ -253,4 +260,4 @@ public override void Dispose()
253260
Buffer.Clear();
254261
Buffer.TrimExcessBuffers();
255262
}
256-
}
263+
}

src/net-questdb-client/Utils/SenderOptions.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
using System.Data.Common;
2929
using System.Reflection;
3030
using System.Runtime.CompilerServices;
31+
using System.Security.Cryptography.X509Certificates;
3132
using System.Text.Json.Serialization;
3233
using QuestDB.Enums;
3334
using QuestDB.Senders;
@@ -81,6 +82,7 @@ public record SenderOptions
8182
private string? _tokenX;
8283
private string? _tokenY;
8384
private string? _username;
85+
private X509Certificate2? _clientCert;
8486

8587
/// <summary>
8688
/// Construct a <see cref="SenderOptions" /> object with default values.
@@ -473,6 +475,15 @@ public int Port
473475
}
474476
}
475477

478+
/// <summary>
479+
/// Specifies a client certificate to be used for TLS authentication.
480+
/// </summary>
481+
public X509Certificate2? client_cert
482+
{
483+
get => _clientCert;
484+
set => _clientCert = value;
485+
}
486+
476487
private void ParseIntWithDefault(string name, string defaultValue, out int field)
477488
{
478489
if (!int.TryParse(ReadOptionFromBuilder(name) ?? defaultValue, out field))
@@ -648,4 +659,17 @@ public ISender Build()
648659
{
649660
return Sender.New(this);
650661
}
651-
}
662+
663+
/// <summary>
664+
/// Sets a client certificate to be used for TLS authentication.
665+
/// </summary>
666+
/// <param name="cert"></param>
667+
/// <returns></returns>
668+
public SenderOptions WithClientCert(X509Certificate2 cert)
669+
{
670+
return this with
671+
{
672+
_clientCert = cert,
673+
};
674+
}
675+
}

0 commit comments

Comments
 (0)