Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/EventStore.ClusterNode/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("EventStore.Core.Tests")]
24 changes: 16 additions & 8 deletions src/EventStore.ClusterNode/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -433,15 +433,24 @@ private static void TryListenOnUnixSocket(ClusterVNodeHostedService hostedServic
}

private static ServerOptionsSelectionCallback CreateServerOptionsSelectionCallback(
ClusterVNodeHostedService hostedService)
ClusterVNodeHostedService hostedService) =>
CreateServerOptionsSelectionCallback(
hostedService.Node.CertificateSelector,
hostedService.Node.IntermediateCertificatesSelector,
hostedService.Node.InternalClientCertificateValidator);

internal static ServerOptionsSelectionCallback CreateServerOptionsSelectionCallback(
Func<X509Certificate2> certificateSelector,
Func<X509Certificate2Collection> intermediateCertificatesSelector,
CertificateDelegates.ClientCertificateValidator clientCertificateValidator)
{
return ((_, _, _, _) =>
{
var serverOptions = new SslServerAuthenticationOptions
{
ServerCertificateContext = SslStreamCertificateContext.Create(
hostedService.Node.CertificateSelector(),
hostedService.Node.IntermediateCertificatesSelector(),
certificateSelector(),
intermediateCertificatesSelector(),
offline: true),
ClientCertificateRequired =
true, // request a client certificate but it's not necessary for the client to supply one
Expand All @@ -452,11 +461,10 @@ private static ServerOptionsSelectionCallback CreateServerOptionsSelectionCallba
return true;
}

var (isValid, error) =
hostedService.Node.InternalClientCertificateValidator(
certificate,
chain,
sslPolicyErrors);
var (isValid, error) = clientCertificateValidator(
certificate,
chain,
sslPolicyErrors);
if (!isValid && error != null)
{
Log.Error("Client certificate validation error: {e}", error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,63 @@ public void returns_permission_denied()
}
}

[TestFixture(typeof(LogFormat.V2), typeof(string))]
public class when_starting_scavenge_as_admin<TLogFormat, TStreamId> : GrpcSpecification<TLogFormat, TStreamId>
{
private ScavengeResp _response;

protected override Task Given() => Task.CompletedTask;

protected override async Task When()
{
_response = await Channel.CreateCallInvoker().AsyncUnaryCall(
StartScavengeMethod,
null,
GetCallOptions(AdminCredentials),
new StartScavengeReq());
}

[Test]
public void returns_started_with_a_scavenge_id()
{
Assert.AreEqual(ScavengeResp.Types.ScavengeResult.Started, _response.ScavengeResult);
Assert.IsNotEmpty(_response.ScavengeId);
}
}

[TestFixture(typeof(LogFormat.V2), typeof(string))]
public class when_starting_scavenge_while_one_is_running<TLogFormat, TStreamId>
: GrpcSpecification<TLogFormat, TStreamId>
{
private ScavengeResp _startedResponse;
private ScavengeResp _inProgressResponse;

protected override Task Given() => Task.CompletedTask;

protected override async Task When()
{
_startedResponse = await Channel.CreateCallInvoker().AsyncUnaryCall(
StartScavengeMethod,
null,
GetCallOptions(AdminCredentials),
new StartScavengeReq());

_inProgressResponse = await Channel.CreateCallInvoker().AsyncUnaryCall(
StartScavengeMethod,
null,
GetCallOptions(AdminCredentials),
new StartScavengeReq());
}

[Test]
public void returns_in_progress_with_the_running_scavenge_id()
{
Assert.AreEqual(ScavengeResp.Types.ScavengeResult.Started, _startedResponse.ScavengeResult);
Assert.AreEqual(ScavengeResp.Types.ScavengeResult.InProgress, _inProgressResponse.ScavengeResult);
Assert.AreEqual(_startedResponse.ScavengeId, _inProgressResponse.ScavengeId);
}
}

[TestFixture(typeof(LogFormat.V2), typeof(string))]
public class when_starting_scavenge_without_permissions<TLogFormat, TStreamId> : GrpcSpecification<TLogFormat, TStreamId>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class read_all_events_forward_with_hard_deleted_stream_should<TLogFormat,
{
private const string StreamName = nameof(read_all_events_forward_with_hard_deleted_stream_should<TLogFormat, TStreamId>);
private readonly List<ReadResp.Types.ReadEvent> _allEvents = new();
private readonly List<ReadResp.Types.ReadEvent> _allEventsBackward = new();
private RpcException _streamReadException;

protected override async Task Given()
Expand Down Expand Up @@ -56,10 +57,17 @@ protected override async Task When()
_streamReadException = ex;
}

using var allCall = StreamsClient.Read(ReadAllRequest(), GetCallOptions(AdminCredentials));
using var allCall = StreamsClient.Read(ReadAllRequest(
ReadReq.Types.Options.Types.ReadDirection.Forwards), GetCallOptions(AdminCredentials));
_allEvents.AddRange((await allCall.ResponseStream.ReadAllAsync().ToArrayAsync())
.Where(x => x.ContentCase == ReadResp.ContentOneofCase.Event)
.Select(x => x.Event));

using var allBackwardCall = StreamsClient.Read(ReadAllRequest(
ReadReq.Types.Options.Types.ReadDirection.Backwards), GetCallOptions(AdminCredentials));
_allEventsBackward.AddRange((await allBackwardCall.ResponseStream.ReadAllAsync().ToArrayAsync())
.Where(x => x.ContentCase == ReadResp.ContentOneofCase.Event)
.Select(x => x.Event));
}

[Test]
Expand All @@ -82,6 +90,20 @@ public void returns_all_events_including_tombstone()
Assert.That(streamEvents.Take(20).All(x => x.Event.Metadata[GrpcConstants.Metadata.Type] == "-"), Is.True);
Assert.That(streamEvents[^1].Event.Metadata[GrpcConstants.Metadata.Type],
Is.EqualTo(SystemEventTypes.StreamDeleted));
Assert.That(streamEvents[^1].Event.StreamRevision, Is.EqualTo((ulong)long.MaxValue));
}

[Test]
public void returns_tombstone_from_backward_read_without_downgrading_its_revision()
{
var streamEvents = _allEventsBackward
.Where(x => x.Event.StreamIdentifier.StreamName.ToStringUtf8() == StreamName)
.ToArray();

Assert.That(streamEvents, Has.Length.EqualTo(21));
Assert.That(streamEvents[0].Event.Metadata[GrpcConstants.Metadata.Type],
Is.EqualTo(SystemEventTypes.StreamDeleted));
Assert.That(streamEvents[0].Event.StreamRevision, Is.EqualTo((ulong)long.MaxValue));
}

private static ReadReq ReadStreamRequest() => new()
Expand All @@ -100,15 +122,17 @@ public void returns_all_events_including_tombstone()
}
};

private static ReadReq ReadAllRequest() => new()
private static ReadReq ReadAllRequest(ReadReq.Types.Options.Types.ReadDirection direction) => new()
{
Options = new()
{
UuidOption = new() { Structured = new() },
NoFilter = new(),
ReadDirection = ReadReq.Types.Options.Types.ReadDirection.Forwards,
ReadDirection = direction,
Count = 100,
All = new() { Start = new() }
All = direction == ReadReq.Types.Options.Types.ReadDirection.Forwards
? new() { Start = new() }
: new() { End = new() }
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,6 @@ await StreamsClient.TombstoneAsync(new()
Assert.That(deleted, Is.Not.Null);
Assert.That(deleted.Event.Event.StreamIdentifier.StreamName.ToStringUtf8(), Is.EqualTo(streamName));
Assert.That(deleted.Event.Event.Metadata[GrpcMetadata.Type], Is.EqualTo(SystemEventTypes.StreamDeleted));
Assert.That(deleted.Event.Event.StreamRevision, Is.EqualTo((ulong)long.MaxValue));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ await StreamsClient.TombstoneAsync(new()
var deleted = await ReadNextEventResponse(subscription);
Assert.That(deleted.Event.StreamIdentifier.StreamName.ToStringUtf8(), Is.EqualTo(streamName));
Assert.That(deleted.Event.Metadata[GrpcMetadata.Type], Is.EqualTo(SystemEventTypes.StreamDeleted));
Assert.That(deleted.Event.StreamRevision, Is.EqualTo((ulong)long.MaxValue));
}

private static ReadReq SubscribeRequest() => new()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using EventStore.ClusterNode;
using EventStore.Common.Utils;
using EventStore.Core.Tests.Certificates;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NUnit.Framework;

namespace EventStore.Core.Tests.Services.Transport.Http;

[TestFixture]
public class with_intermediate_certificates : with_certificate_chain_of_length_3
{
private IHost _host;

[SetUp]
public void SetUp()
{
var certificate = X509CertificateLoader.LoadPkcs12(_leaf.ExportToPkcs12(), null);
_host = new HostBuilder()
.ConfigureWebHost(webHost => webHost
.UseKestrel(server => server.Listen(IPAddress.Loopback, 0, listenOptions =>
listenOptions.UseHttps(Program.CreateServerOptionsSelectionCallback(
() => certificate,
() => new X509Certificate2Collection(_intermediate),
(_, _, _) => (true, null)), null)))
.Configure(app => app.Run(context => context.Response.CompleteAsync())))
.Build();
_host.Start();
}

[Test]
public async Task server_should_send_intermediate_certificate_during_handshake()
{
var handler = new SocketsHttpHandler();
var gotLeaf = false;
var gotIntermediate = false;
handler.SslOptions.RemoteCertificateValidationCallback = (_, certificate, chain, _) =>
{
gotLeaf = certificate is not null && certificate.GetCertHashString() == _leaf.GetCertHashString();
gotIntermediate = chain is not null && chain.ChainElements.Cast<X509ChainElement>()
.Any(element => element.Certificate.Thumbprint == _intermediate.Thumbprint);
return true;
};
using var client = new HttpClient(handler);
var address = _host.Services.GetRequiredService<IServer>()
.Features.Get<IServerAddressesFeature>()!.Addresses.Single();
using var request = new HttpRequestMessage(HttpMethod.Get, address)
{
Version = HttpVersion.Version20,
VersionPolicy = HttpVersionPolicy.RequestVersionExact,
};

using var response = await client.SendAsync(request);

Assert.That(gotLeaf, Is.True);
Assert.That(gotIntermediate, Is.True);
}

[TearDown]
public void TearDown()
{
_host?.Dispose();
}
}
Loading
Loading