Skip to content

Commit

Permalink
Fixup
Browse files Browse the repository at this point in the history
  • Loading branch information
w1am committed Jan 8, 2025
1 parent b278f7d commit fd0b009
Show file tree
Hide file tree
Showing 34 changed files with 713 additions and 126 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
fail-fast: false
matrix:
docker-tag: [ ci, lts, previous-lts ]
test: [ Streams, PersistentSubscriptions, Operations, Projections, Security, Misc ]
test: [ Streams, PersistentSubscriptions, Operations, ProjectionManagement, UserManagement, Security, Misc ]
name: Test CE (${{ matrix.docker-tag }})
with:
docker-tag: ${{ matrix.docker-tag }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dispatch-ce.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
strategy:
fail-fast: false
matrix:
test: [ Streams, PersistentSubscriptions, Operations, Projections, Security, Misc ]
test: [ Streams, PersistentSubscriptions, Operations, ProjectionManagement, UserManagement, Security, Misc ]
name: Test CE (${{ inputs.docker-tag }})
with:
docker-tag: ${{ inputs.docker-tag }}
Expand Down
28 changes: 7 additions & 21 deletions src/Kurrent.Client/Core/Certificates/X509Certificates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
#pragma warning disable SYSLIB0057

#if NET48
using Org.BouncyCastle.Crypto;
Expand All @@ -13,35 +14,20 @@
namespace EventStore.Client;

static class X509Certificates {
// TODO SS: Use .NET 8 X509Certificate2.CreateFromPemFile(certPemFilePath, keyPemFilePath) once the Windows32Exception issue is resolved
public static X509Certificate2 CreateFromPemFile(string certPemFilePath, string keyPemFilePath) {
try {
#if NET9_0_OR_GREATER
using var publicCert = X509CertificateLoader.LoadCertificateFromFile(certPemFilePath);
#if NET8_0_OR_GREATER
using var certificate = X509Certificate2.CreateFromPemFile(certPemFilePath, keyPemFilePath);
#else
using var publicCert = new X509Certificate2(certPemFilePath);
#endif
using var privateKey = RSA.Create().ImportPrivateKeyFromFile(keyPemFilePath);
using var privateKey = RSA.Create().ImportPrivateKeyFromFile(keyPemFilePath);
using var certificate = publicCert.CopyWithPrivateKey(privateKey);

#if NET48
return new(certificate.Export(X509ContentType.Pfx));
#else
return X509Certificate2.CreateFromPemFile(certPemFilePath, keyPemFilePath);
#endif

return new X509Certificate2(certificate.Export(X509ContentType.Pfx));
} catch (Exception ex) {
throw new CryptographicException($"Failed to load private key: {ex.Message}");
}

// Notes:
// using X509Certificate2.CreateFromPemFile(certPemFilePath, keyPemFilePath) would be the ideal choice here,
// but it's currently causing a Win32Exception specifically on Windows. Alternative implementation is used until the issue is resolved.
//
// Error: The SSL connection could not be established, see inner exception. AuthenticationException: Authentication failed because the platform
// does not support ephemeral keys. Win32Exception: No credentials are available in the security package
//
// public static X509Certificate2 CreateFromPemFile(string certPemFilePath, string keyPemFilePath) =>
// X509Certificate2.CreateFromPemFile(certPemFilePath, keyPemFilePath);
}
}

Expand All @@ -66,7 +52,7 @@ public static RSA ImportPrivateKeyFromFile(this RSA rsa, string privateKeyPath)
public static RSA ImportPrivateKeyFromFile(this RSA rsa, string privateKeyPath) {
var (content, label) = LoadPemKeyFile(privateKeyPath);

var privateKey = string.Join(string.Empty, content[1..^1]);
var privateKey = string.Join(string.Empty, content[1..^1]);
var privateKeyBytes = Convert.FromBase64String(privateKey);

if (label == RsaPemLabels.Pkcs8PrivateKey)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,7 @@ static void ConfigureClientCertificate(KurrentClientSettings settings, IReadOnly
);

try {
settings.ConnectivitySettings.ClientCertificate =
X509Certificates.CreateFromPemFile(certPemFilePath, keyPemFilePath);
settings.ConnectivitySettings.ClientCertificate = X509Certificates.CreateFromPemFile(certPemFilePath, keyPemFilePath);
} catch (Exception ex) {
throw new InvalidClientCertificateException("Failed to create client certificate.", ex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ public static KurrentFixtureOptions DefaultOptions() {
["EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS"] = $"{NetworkPortProvider.DefaultEsdbPort}"
};

if (GlobalEnvironment.DockerImage.Contains("commercial")) {
defaultEnvironment["EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH"] = "/etc/eventstore/certs/ca";
defaultEnvironment["EventStore__Plugins__UserCertificates__Enabled"] = "true";
}

if (port != NetworkPortProvider.DefaultEsdbPort) {
if (GlobalEnvironment.Variables.TryGetValue("ES_DOCKER_TAG", out var tag) && tag == "ci")
defaultEnvironment["EVENTSTORE_ADVERTISE_NODE_PORT_TO_CLIENT_AS"] = $"{port}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ public static KurrentFixtureOptions DefaultOptions() {
["EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS"] = $"{NetworkPortProvider.DefaultEsdbPort}"
};

if (GlobalEnvironment.DockerImage.Contains("commercial")) {
defaultEnvironment["EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH"] = "/etc/eventstore/certs/ca";
defaultEnvironment["EventStore__Plugins__UserCertificates__Enabled"] = "true";
}

if (port != NetworkPortProvider.DefaultEsdbPort) {
if (GlobalEnvironment.Variables.TryGetValue("ES_DOCKER_TAG", out var tag) && tag == "ci")
defaultEnvironment["EVENTSTORE_ADVERTISE_NODE_PORT_TO_CLIENT_AS"] = $"{port}";
Expand Down Expand Up @@ -181,7 +186,7 @@ async Task<int> GetNextAvailablePort(TimeSpan delay = default) {
#if NET
if (socket.Connected) await socket.DisconnectAsync(true);
#else
if (socket.Connected) socket.Disconnect(true);
if (socket.Connected) socket.Disconnect(true);
#endif
}
}
Expand Down
29 changes: 18 additions & 11 deletions test/Kurrent.Client.Tests/ClientCertificatesTests.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
using EventStore.Client;
using Humanizer;
using Kurrent.Client.Tests.TestNode;

namespace Kurrent.Client.Tests;

[Trait("Category", "Target:Misc")]
[Trait("Category", "Target:Plugins")]
[Trait("Category", "Type:UserCertificate")]
public class ClientCertificateTests(ITestOutputHelper output, KurrentPermanentFixture fixture)
: KurrentPermanentTests<KurrentPermanentFixture>(output, fixture) {
public class ClientCertificateTests(ITestOutputHelper output, KurrentTemporaryFixture fixture)
: KurrentTemporaryTests<KurrentTemporaryFixture>(output, fixture) {
[SupportsPlugins.Theory(EventStoreRepository.Commercial, "This server version does not support plugins"), BadClientCertificatesTestCases]
async Task bad_certificates_combinations_should_return_authentication_error(string userCertFile, string userKeyFile, string tlsCaFile) {
var stream = Fixture.GetStreamName();
var seedEvents = Fixture.CreateTestEvents();
var connectionString = $"esdb://localhost:2113/?tls=true&userCertFile={userCertFile}&userKeyFile={userKeyFile}&tlsCaFile={tlsCaFile}";
var stream = Fixture.GetStreamName();
var seedEvents = Fixture.CreateTestEvents();
var port = Fixture.Options.ClientSettings.ConnectivitySettings.ResolvedAddressOrDefault.Port;

var connectionString = $"esdb://localhost:{port}/?tls=true&userCertFile={userCertFile}&userKeyFile={userKeyFile}&tlsCaFile={tlsCaFile}";

var settings = KurrentClientSettings.Create(connectionString);
settings.ConnectivitySettings.TlsVerifyCert.ShouldBeTrue();
Expand All @@ -24,9 +27,11 @@ async Task bad_certificates_combinations_should_return_authentication_error(stri

[SupportsPlugins.Theory(EventStoreRepository.Commercial, "This server version does not support plugins"), ValidClientCertificatesTestCases]
async Task valid_certificates_combinations_should_write_to_stream(string userCertFile, string userKeyFile, string tlsCaFile) {
var stream = Fixture.GetStreamName();
var seedEvents = Fixture.CreateTestEvents();
var connectionString = $"esdb://localhost:2113/?userCertFile={userCertFile}&userKeyFile={userKeyFile}&tlsCaFile={tlsCaFile}";
var stream = Fixture.GetStreamName();
var seedEvents = Fixture.CreateTestEvents();
var port = Fixture.Options.ClientSettings.ConnectivitySettings.ResolvedAddressOrDefault.Port;

var connectionString = $"esdb://localhost:{port}/?userCertFile={userCertFile}&userKeyFile={userKeyFile}&tlsCaFile={tlsCaFile}";

var settings = KurrentClientSettings.Create(connectionString);
settings.ConnectivitySettings.TlsVerifyCert.ShouldBeTrue();
Expand All @@ -39,9 +44,11 @@ async Task valid_certificates_combinations_should_write_to_stream(string userCer

[SupportsPlugins.Theory(EventStoreRepository.Commercial, "This server version does not support plugins"), BadClientCertificatesTestCases]
async Task basic_authentication_should_take_precedence(string userCertFile, string userKeyFile, string tlsCaFile) {
var stream = Fixture.GetStreamName();
var seedEvents = Fixture.CreateTestEvents();
var connectionString = $"esdb://admin:changeit@localhost:2113/?userCertFile={userCertFile}&userKeyFile={userKeyFile}&tlsCaFile={tlsCaFile}";
var stream = Fixture.GetStreamName();
var seedEvents = Fixture.CreateTestEvents();
var port = Fixture.Options.ClientSettings.ConnectivitySettings.ResolvedAddressOrDefault.Port;

var connectionString = $"esdb://admin:changeit@localhost:{port}/?userCertFile={userCertFile}&userKeyFile={userKeyFile}&tlsCaFile={tlsCaFile}";

var settings = KurrentClientSettings.Create(connectionString);
settings.ConnectivitySettings.TlsVerifyCert.ShouldBeTrue();
Expand Down
28 changes: 28 additions & 0 deletions test/Kurrent.Client.Tests/InvalidCredentialsTestCases.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Collections;
using EventStore.Client;

namespace Kurrent.Client.Tests;

public abstract record InvalidCredentialsTestCase(TestUser User, Type ExpectedException);

public class InvalidCredentialsTestCases : IEnumerable<object?[]> {
public IEnumerator<object?[]> GetEnumerator() {
yield return new object?[] { new MissingCredentials() };
yield return new object?[] { new WrongUsername() };
yield return new object?[] { new WrongPassword() };
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public record MissingCredentials() : InvalidCredentialsTestCase(Fakers.Users.WithNoCredentials(), typeof(AccessDeniedException)) {
public override string ToString() => nameof(MissingCredentials);
}

public record WrongUsername() : InvalidCredentialsTestCase(Fakers.Users.WithInvalidCredentials(false), typeof(NotAuthenticatedException)) {
public override string ToString() => nameof(WrongUsername);
}

public record WrongPassword() : InvalidCredentialsTestCase(Fakers.Users.WithInvalidCredentials(wrongPassword: false), typeof(NotAuthenticatedException)) {
public override string ToString() => nameof(WrongPassword);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using EventStore.Client;
using Kurrent.Client.Tests.TestNode;

namespace Kurrent.Client.Tests.PersistentSubscriptions;

[Trait("Category", "Target:PersistentSubscriptions")]
public class SubscribeToAllConnectToExistingWithStartFromNotSetTests(ITestOutputHelper output, KurrentTemporaryFixture fixture)
: KurrentTemporaryTests<KurrentTemporaryFixture>(output, fixture) {
[RetryFact]
public async Task connect_to_existing_with_start_from_not_set() {
var group = Fixture.GetGroupName();
var stream = Fixture.GetStreamName();

foreach (var @event in Fixture.CreateTestEvents(10))
await Fixture.Streams.AppendToStreamAsync(
stream,
StreamState.Any,
[@event]
);

await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root);
var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root);

await Assert.ThrowsAsync<TimeoutException>(
() => subscription.Messages
.OfType<PersistentSubscriptionMessage.Event>()
.Where(e => !SystemStreams.IsSystemStream(e.ResolvedEvent.OriginalStreamId))
.AnyAsync()
.AsTask()
.WithTimeout()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using EventStore.Client;
using Kurrent.Client.Tests.TestNode;

namespace Kurrent.Client.Tests.PersistentSubscriptions;

[Trait("Category", "Target:PersistentSubscriptions")]
public class SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests(ITestOutputHelper output, KurrentTemporaryFixture fixture)
: KurrentTemporaryTests<KurrentTemporaryFixture>(output, fixture) {
[RetryFact]
public async Task connect_to_existing_with_start_from_set_to_end_position() {
var group = Fixture.GetGroupName();
var stream = Fixture.GetStreamName();

foreach (var @event in Fixture.CreateTestEvents(10)) {
await Fixture.Streams.AppendToStreamAsync(
stream,
StreamState.Any,
[@event]
);
}

await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.End), userCredentials: TestCredentials.Root);

var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root);

await Assert.ThrowsAsync<TimeoutException>(
() => subscription.Messages
.OfType<PersistentSubscriptionMessage.Event>()
.Where(e => !SystemStreams.IsSystemStream(e.ResolvedEvent.OriginalStreamId))
.AnyAsync()
.AsTask()
.WithTimeout()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,31 +86,6 @@ await Fixture.Streams.AppendToStreamAsync(
Assert.Equal(events[0].Event.EventId, resolvedEvent.Event.EventId);
}

[RetryFact]
public async Task connect_to_existing_with_start_from_not_set() {
var group = Fixture.GetGroupName();
var stream = Fixture.GetStreamName();

foreach (var @event in Fixture.CreateTestEvents(10))
await Fixture.Streams.AppendToStreamAsync(
stream,
StreamState.Any,
[@event]
);

await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root);
var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root);

await Assert.ThrowsAsync<TimeoutException>(
() => subscription.Messages
.OfType<PersistentSubscriptionMessage.Event>()
.Where(e => !SystemStreams.IsSystemStream(e.ResolvedEvent.OriginalStreamId))
.AnyAsync()
.AsTask()
.WithTimeout(TimeSpan.FromMilliseconds(250))
);
}

[RetryFact]
public async Task connect_to_existing_with_start_from_not_set_then_event_written() {
var group = Fixture.GetGroupName();
Expand Down Expand Up @@ -141,33 +116,6 @@ await Fixture.Streams.AppendToStreamAsync(
Assert.Equal(expectedStreamId, resolvedEvent.Event.EventStreamId);
}

[RetryFact]
public async Task connect_to_existing_with_start_from_set_to_end_position() {
var group = Fixture.GetGroupName();
var stream = Fixture.GetStreamName();

foreach (var @event in Fixture.CreateTestEvents(10)) {
await Fixture.Streams.AppendToStreamAsync(
stream,
StreamState.Any,
[@event]
);
}

await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.End), userCredentials: TestCredentials.Root);

var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root);

await Assert.ThrowsAsync<TimeoutException>(
() => subscription.Messages
.OfType<PersistentSubscriptionMessage.Event>()
.Where(e => !SystemStreams.IsSystemStream(e.ResolvedEvent.OriginalStreamId))
.AnyAsync()
.AsTask()
.WithTimeout(TimeSpan.FromMilliseconds(250))
);
}

[RetryFact]
public async Task connect_to_existing_with_start_from_set_to_end_position_then_event_written() {
var stream = Fixture.GetStreamName();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using EventStore.Client;
using Kurrent.Client.Tests.TestNode;

namespace Kurrent.Client.Tests.PersistentSubscriptions;

[Trait("Category", "Target:PersistentSubscriptions")]
public class SubscribeToStreamConnectToExistingWithStartFromBeginningTests(ITestOutputHelper output, KurrentTemporaryFixture fixture)
: KurrentTemporaryTests<KurrentTemporaryFixture>(output, fixture) {
[RetryFact]
public async Task connect_to_existing_with_start_from_beginning_and_no_streamconnect_to_existing_with_start_from_not_set_and_events_in_it() {
var stream = Fixture.GetStreamName();
var group = Fixture.GetGroupName();
var events = Fixture.CreateTestEvents(10).ToArray();

await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events);
await Fixture.Subscriptions.CreateToStreamAsync(
stream,
group,
new(),
userCredentials: TestCredentials.Root
);

await using var subscription = Fixture.Subscriptions.SubscribeToStream(
stream,
group,
userCredentials: TestCredentials.Root
);

await Assert.ThrowsAsync<TimeoutException>(
() => subscription.Messages.AnyAsync(message => message is PersistentSubscriptionMessage.Event).AsTask().WithTimeout()
);
}
}
Loading

0 comments on commit fd0b009

Please sign in to comment.