-
Notifications
You must be signed in to change notification settings - Fork 0
chore(transport): retire the legacy TCP package #498
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
Merged
Merged
Changes from all commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
37b7b1a
feat(cluster): preserve independent replication identity
yordis e322bb8
fix(cluster): preserve replication identity for monitoring
yordis 9a1a9fc
fix(cluster): advertise the replication identity
yordis 28f5203
feat(cluster): isolate internal replication traffic
yordis 09439b0
fix(monitoring): match isolated replication members
yordis bcefb75
chore(transport): retire the internal TCP runtime
yordis 2e91033
fix(tests): prove read-only delete rejection
yordis 2e33e61
fix(tests): follow the gRPC replication endpoint
yordis 43d6ce1
fix(tests): follow the gRPC membership matcher
yordis d79424b
fix(protocol): keep retired API out of the lockfile
yordis f14644b
feat(monitoring): preserve connection visibility over gRPC
yordis 1a63389
fix(monitoring): preserve queue visibility
yordis 834235c
fix(monitoring): report replication status independently
yordis c3dc51f
chore(transport): retire the legacy TCP package
yordis 1c35ce7
fix(cluster): avoid drift from gRPC service contracts
yordis 5b387fb
fix(tests): preserve read-only replica write coverage
yordis a80b6af
chore(transport): align with merged cluster endpoint contract
yordis fc98ac5
fix(grpc): prevent orphaned subscriptions on disconnect
yordis 1e0b002
fix(grpc): preserve forwarded authentication failures
yordis d13926c
fix(monitoring): preserve connection visibility across endpoint changes
yordis fa68f7d
chore(transport): keep package retirement behind gRPC parity
yordis 7430d58
fix(tests): keep forwarded authentication coverage in CI
yordis bfe81b0
fix(monitoring): retain verified authentication coverage
yordis 403dc0a
chore(transport): retain verified authentication coverage
yordis bcb371f
chore(monitoring): guard against stale connection visibility
yordis 3a340dd
chore(transport): keep connection visibility regression in the retire…
yordis 0405597
chore(replication): protect secure cluster identity
yordis 5c1281e
fix(monitoring): preserve connection visibility during queue failures
yordis ec97ff0
fix(monitoring): distinguish unavailable from independent connection …
yordis c08032d
chore(transport): preserve independent connection visibility
yordis 8eadee8
fix(monitoring): restrict operational diagnostics to authorized callers
yordis 8101a97
fix(container): avoid retired certificate dependency
yordis 895ebaa
chore(transport): keep diagnostics protected through retirement
yordis df93d11
fix(monitoring): preserve independent replication diagnostics on queu…
yordis 4975499
chore(transport): preserve diagnostics through retirement
yordis c3b1ff7
fix(monitoring): protect replication diagnostics with their own permi…
yordis 6946ea6
chore(transport): preserve replication access boundaries
yordis 1692c4f
chore(monitoring): align diagnostics with merged transport parity
yordis 1de7dc3
chore(transport): keep package retirement aligned with merged parity
yordis 7133fb0
fix(monitoring): preserve replication failure visibility
yordis 028b784
chore(transport): keep retirement aligned with diagnostics
yordis 5b77efa
chore(transport): align retirement with merged monitoring
yordis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
144 changes: 144 additions & 0 deletions
144
src/EventStore.Core.Tests/Services/Transport/Grpc/Replication/ReplicationMutualTlsTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| using System; | ||
| using System.Collections.Concurrent; | ||
| 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.Authorization; | ||
| using EventStore.Core.Bus; | ||
| using EventStore.Core.Messages; | ||
| using EventStore.Core.Messaging; | ||
| using EventStore.Core.Services.Replication; | ||
| using EventStore.Core.Services.Transport.Grpc.Replication; | ||
| using EventStore.Core.Services.Transport.Http.NodeHttpClientFactory; | ||
| using EventStore.Core.Tests.Helpers; | ||
| using Grpc.Core; | ||
| using Grpc.Net.Client; | ||
| using Microsoft.AspNetCore.Builder; | ||
| using Microsoft.AspNetCore.Hosting; | ||
| using Microsoft.AspNetCore.Hosting.Server; | ||
| using Microsoft.AspNetCore.Hosting.Server.Features; | ||
| using Microsoft.AspNetCore.Server.Kestrel.Core; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Hosting; | ||
| using NUnit.Framework; | ||
| using Proto = EventStore.Replication; | ||
|
|
||
| namespace EventStore.Core.Tests.Services.Transport.Grpc.Replication; | ||
|
|
||
| [TestFixture] | ||
| public class ReplicationMutualTlsTests | ||
| { | ||
| [Test] | ||
| public async Task trusted_client_certificate_reaches_replication_with_certificate_identity() | ||
| { | ||
| using var root = TestCertificates.GetRootCertificate(); | ||
| using var serverCertificate = TestCertificates.GetServerCertificate(); | ||
| using var clientCertificate = TestCertificates.GetOtherServerCertificate(); | ||
| var publisher = new CapturingPublisher(); | ||
| using var host = StartServer(serverCertificate, new X509Certificate2Collection(root), publisher); | ||
| using var channel = CreateChannel(host, clientCertificate, new X509Certificate2Collection(root)); | ||
| var client = new Proto.Replication.ReplicationClient(channel); | ||
| using var call = client.Replicate(deadline: DateTime.UtcNow.AddSeconds(10)); | ||
|
|
||
| await call.RequestStream.WriteAsync(SubscribeFrame()); | ||
| await call.RequestStream.CompleteAsync(); | ||
| Assert.That(await call.ResponseStream.MoveNext(), Is.False); | ||
| Assert.That(publisher.Messages.OfType<ReplicationMessage.ReplicaSubscriptionRequest>().Count(), Is.EqualTo(1)); | ||
| Assert.That(publisher.Messages.OfType<ReplicationMessage.ReplicaSubscriptionRequest>().Single() | ||
| .Session.Identity.TransportIdentityKind, | ||
| Is.EqualTo(ReplicationTransportIdentityKind.ClientCertificateSha256)); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task secure_replication_without_client_certificate_is_rejected_by_application_identity() | ||
| { | ||
| using var root = TestCertificates.GetRootCertificate(); | ||
| using var serverCertificate = TestCertificates.GetServerCertificate(); | ||
| var publisher = new CapturingPublisher(); | ||
| using var host = StartServer(serverCertificate, new X509Certificate2Collection(root), publisher); | ||
| using var channel = CreateChannel(host, null, new X509Certificate2Collection(root)); | ||
| var client = new Proto.Replication.ReplicationClient(channel); | ||
| using var call = client.Replicate(deadline: DateTime.UtcNow.AddSeconds(10)); | ||
|
|
||
| await call.RequestStream.WriteAsync(SubscribeFrame()); | ||
| await call.RequestStream.CompleteAsync(); | ||
| var exception = Assert.ThrowsAsync<RpcException>(async () => await call.ResponseStream.MoveNext()); | ||
| Assert.That(exception!.StatusCode, Is.EqualTo(StatusCode.Unauthenticated)); | ||
| Assert.That(publisher.Messages, Is.Empty); | ||
| } | ||
|
|
||
| private static IHost StartServer( | ||
| X509Certificate2 serverCertificate, | ||
| X509Certificate2Collection trustedRoots, | ||
| IPublisher publisher) | ||
| { | ||
| var host = new HostBuilder() | ||
| .ConfigureWebHost(webHost => webHost | ||
| .UseKestrel(server => server.Listen(IPAddress.Loopback, 0, options => | ||
| { | ||
| options.Protocols = HttpProtocols.Http2; | ||
| options.UseHttps(Program.CreateServerOptionsSelectionCallback( | ||
| () => serverCertificate, | ||
| () => null, | ||
| (certificate, chain, errors) => ClusterVNode<string>.ValidateClientCertificate( | ||
| certificate, chain, errors, () => null, () => trustedRoots)), null); | ||
| })) | ||
| .ConfigureServices(services => | ||
| { | ||
| services.AddGrpc(); | ||
| services.AddSingleton(new ReplicationService( | ||
| publisher, new PassthroughAuthorizationProvider())); | ||
| }) | ||
| .Configure(app => | ||
| { | ||
| app.UseRouting(); | ||
| app.UseEndpoints(endpoints => endpoints.MapGrpcService<ReplicationService>()); | ||
| })) | ||
| .Build(); | ||
| host.Start(); | ||
| return host; | ||
| } | ||
|
|
||
| private static GrpcChannel CreateChannel( | ||
| IHost host, | ||
| X509Certificate2 clientCertificate, | ||
| X509Certificate2Collection trustedRoots) | ||
| { | ||
| var factory = new NodeHttpClientFactory( | ||
| Uri.UriSchemeHttps, | ||
| (certificate, chain, errors, names) => ClusterVNode<string>.ValidateServerCertificate( | ||
| certificate, chain, errors, () => null, () => trustedRoots, names), | ||
| () => clientCertificate); | ||
| var httpClient = factory.CreateHttpClient(["localhost"]); | ||
| var address = host.Services.GetRequiredService<IServer>() | ||
| .Features.Get<IServerAddressesFeature>()!.Addresses.Single(); | ||
| return GrpcChannel.ForAddress(address, new GrpcChannelOptions | ||
| { | ||
| HttpClient = httpClient, | ||
| DisposeHttpClient = true | ||
| }); | ||
| } | ||
|
|
||
| private static Proto.ReplicaFrame SubscribeFrame() => ReplicationGrpcCodec.ToGrpc( | ||
| new ReplicationMessage.SubscribeReplica( | ||
| ReplicationSubscriptionVersions.V_CURRENT, | ||
| 0, | ||
| Guid.NewGuid(), | ||
| [], | ||
| new DnsEndPoint("replica.internal", 1112), | ||
| Guid.NewGuid(), | ||
| Guid.NewGuid(), | ||
| true, | ||
| Guid.NewGuid())); | ||
|
|
||
| private sealed class CapturingPublisher : IPublisher | ||
| { | ||
| public ConcurrentQueue<Message> Messages { get; } = new(); | ||
|
|
||
| public void Publish(Message message) => Messages.Enqueue(message); | ||
| } | ||
| } |
168 changes: 168 additions & 0 deletions
168
src/EventStore.Core.Tests/Services/Transport/Http/ssl_connections_mutual_auth.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| 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.Services.Transport.Http.NodeHttpClientFactory; | ||
| using EventStore.Core.Tests.Helpers; | ||
| 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 ssl_connections_mutual_auth | ||
| { | ||
| [TestCase(true, true, true, true, true)] | ||
| [TestCase(true, false, true, true, false)] | ||
| [TestCase(false, true, true, true, false)] | ||
| [TestCase(false, false, true, true, false)] | ||
| [TestCase(true, true, true, false, true)] | ||
| [TestCase(true, false, true, false, true)] | ||
| [TestCase(false, true, true, false, false)] | ||
| [TestCase(false, false, true, false, false)] | ||
| [TestCase(true, true, false, true, true)] | ||
| [TestCase(true, false, false, true, false)] | ||
| [TestCase(false, true, false, true, true)] | ||
| [TestCase(false, false, false, true, false)] | ||
| [TestCase(true, true, false, false, true)] | ||
| [TestCase(true, false, false, false, true)] | ||
| [TestCase(false, true, false, false, true)] | ||
| [TestCase(false, false, false, false, true)] | ||
| public async Task connection_outcome_follows_server_and_client_certificate_policy( | ||
| bool useTrustedServerCertificate, | ||
| bool useTrustedClientCertificate, | ||
| bool validateServerCertificate, | ||
| bool validateClientCertificate, | ||
| bool shouldConnectSuccessfully) | ||
| { | ||
| using var rootCertificate = TestCertificates.GetRootCertificate(); | ||
| using var serverCertificate = useTrustedServerCertificate | ||
| ? TestCertificates.GetServerCertificate() | ||
| : TestCertificates.GetUntrustedCertificate(); | ||
| using var clientCertificate = useTrustedClientCertificate | ||
| ? TestCertificates.GetOtherServerCertificate() | ||
| : TestCertificates.GetUntrustedCertificate(); | ||
| var trustedRoots = new X509Certificate2Collection(rootCertificate); | ||
| CertificateDelegates.ClientCertificateValidator clientValidator = validateClientCertificate | ||
| ? (certificate, chain, errors) => ClusterVNode<string>.ValidateClientCertificate( | ||
| certificate, | ||
| chain, | ||
| errors, | ||
| () => null, | ||
| () => trustedRoots) | ||
| : (_, _, _) => (true, null); | ||
|
|
||
| using var host = StartServer(serverCertificate, clientValidator); | ||
| var connected = await TryConnect( | ||
| host, | ||
| clientCertificate, | ||
| validateServerCertificate, | ||
| trustedRoots); | ||
|
|
||
| Assert.That(connected, Is.EqualTo(shouldConnectSuccessfully)); | ||
| } | ||
|
|
||
| [TestCase(true)] | ||
| [TestCase(false)] | ||
| public async Task client_certificate_is_optional_at_the_transport_boundary(bool validateServerCertificate) | ||
| { | ||
| using var rootCertificate = TestCertificates.GetRootCertificate(); | ||
| using var serverCertificate = TestCertificates.GetServerCertificate(); | ||
| var trustedRoots = new X509Certificate2Collection(rootCertificate); | ||
| using var host = StartServer( | ||
| serverCertificate, | ||
| (_, _, _) => throw new AssertionException("Missing client certificates bypass node validation.")); | ||
|
|
||
| var connected = await TryConnect( | ||
| host, | ||
| clientCertificate: null, | ||
| validateServerCertificate, | ||
| trustedRoots); | ||
|
|
||
| Assert.That(connected, Is.True); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task server_certificate_is_required() | ||
| { | ||
| using var rootCertificate = TestCertificates.GetRootCertificate(); | ||
| var trustedRoots = new X509Certificate2Collection(rootCertificate); | ||
| using var host = StartServer( | ||
| serverCertificate: null, | ||
| (_, _, _) => (true, null)); | ||
|
|
||
| var connected = await TryConnect( | ||
| host, | ||
| clientCertificate: null, | ||
| validateServerCertificate: false, | ||
| trustedRoots); | ||
|
|
||
| Assert.That(connected, Is.False); | ||
| } | ||
|
|
||
| private static IHost StartServer( | ||
| X509Certificate2 serverCertificate, | ||
| CertificateDelegates.ClientCertificateValidator clientCertificateValidator) | ||
| { | ||
| var host = new HostBuilder() | ||
| .ConfigureWebHost(webHost => webHost | ||
| .UseKestrel(server => server.Listen(IPAddress.Loopback, 0, listenOptions => | ||
| listenOptions.UseHttps(Program.CreateServerOptionsSelectionCallback( | ||
| () => serverCertificate, | ||
| () => null, | ||
| clientCertificateValidator), null))) | ||
| .Configure(app => app.Run(context => context.Response.CompleteAsync()))) | ||
| .Build(); | ||
| host.Start(); | ||
| return host; | ||
| } | ||
|
|
||
| private static async Task<bool> TryConnect( | ||
| IHost host, | ||
| X509Certificate2 clientCertificate, | ||
| bool validateServerCertificate, | ||
| X509Certificate2Collection trustedRoots) | ||
| { | ||
| CertificateDelegates.ServerCertificateValidator serverValidator = validateServerCertificate | ||
| ? (certificate, chain, errors, otherNames) => ClusterVNode<string>.ValidateServerCertificate( | ||
| certificate, | ||
| chain, | ||
| errors, | ||
| () => null, | ||
| () => trustedRoots, | ||
| otherNames) | ||
| : (_, _, _, _) => (true, null); | ||
| var clientFactory = new NodeHttpClientFactory( | ||
| Uri.UriSchemeHttps, | ||
| serverValidator, | ||
| () => clientCertificate); | ||
| using var client = clientFactory.CreateHttpClient(["localhost"]); | ||
| client.Timeout = TimeSpan.FromSeconds(5); | ||
| 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, | ||
| }; | ||
|
|
||
| try | ||
| { | ||
| using var response = await client.SendAsync(request); | ||
| return response.IsSuccessStatusCode; | ||
| } | ||
| catch (HttpRequestException) | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.