Skip to content

Commit 9a595aa

Browse files
authored
Allow overriding SSL configuration (#697)
* Allow overriding SSL configuration * Default to legacy SSL configuration on NETFRAMEWORK * Add basic test of lifecycle * Add comment for net4.8 divergence
1 parent 83b6a56 commit 9a595aa

13 files changed

+312
-27
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// Copyright 2012-2013 Octopus Deploy Pty. Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System;
16+
using System.Threading;
17+
using System.Threading.Tasks;
18+
using FluentAssertions;
19+
using Halibut.Diagnostics;
20+
using Halibut.ServiceModel;
21+
using Halibut.Tests.Support;
22+
using NUnit.Framework;
23+
24+
namespace Halibut.Tests
25+
{
26+
public class ClientServerLifecycleTests : BaseTest
27+
{
28+
[Test]
29+
public async Task ListeningConfiguration()
30+
{
31+
await using var server = RunServer(out var serverPort);
32+
33+
await using var runtime = CreateRuntimeForListener();
34+
var client = CreateClient(runtime, serverPort);
35+
var result = await client.AddAsync(2, 2);
36+
result.Should().Be(4);
37+
}
38+
39+
[Test]
40+
public async Task PollingConfiguration()
41+
{
42+
await using var server = RunServer(out var serverPort);
43+
await using var runtime = CreateRuntimeForPoller(server, out var client);
44+
var result = await client.AddAsync(2, 2);
45+
result.Should().Be(4);
46+
}
47+
48+
[Test]
49+
public async Task ListeningThenPollingConfiguration()
50+
{
51+
// On NET4.8 with SslProtocols.None this will result in SSPI errors
52+
await ListeningConfiguration();
53+
await PollingConfiguration();
54+
}
55+
56+
HalibutRuntime CreateRuntimeForListener()
57+
{
58+
var runtime = new HalibutRuntimeBuilder()
59+
.WithServerCertificate(Certificates.TentacleListening)
60+
.WithLogFactory(new TestLogFactory(HalibutLog))
61+
.Build();
62+
return runtime;
63+
}
64+
65+
HalibutRuntime CreateRuntimeForPoller(HalibutRuntime serverRuntime, out IAsyncClientCalculatorService client)
66+
{
67+
var runtime = new HalibutRuntimeBuilder()
68+
.WithServerCertificate(Certificates.TentaclePolling)
69+
.WithLogFactory(new TestLogFactory(HalibutLog))
70+
.Build();
71+
var port = runtime.Listen();
72+
runtime.Trust(Certificates.OctopusPublicThumbprint);
73+
74+
var pollEndpoint = new ServiceEndPoint(
75+
baseUri: new Uri($"https://localhost:{port}/"),
76+
remoteThumbprint: Certificates.TentaclePollingPublicThumbprint,
77+
halibutTimeoutsAndLimits: runtime.TimeoutsAndLimits
78+
)
79+
{
80+
TcpClientConnectTimeout = TimeSpan.FromSeconds(5)
81+
};
82+
var pollingUri = new Uri("poll://TEST-POLL");
83+
serverRuntime.Poll(pollingUri, pollEndpoint, CancellationToken);
84+
var clientEndpoint = new ServiceEndPoint(
85+
baseUri: pollingUri,
86+
remoteThumbprint: Certificates.OctopusPublicThumbprint,
87+
halibutTimeoutsAndLimits: runtime.TimeoutsAndLimits
88+
);
89+
client = runtime.CreateAsyncClient<ICalculatorService, IAsyncClientCalculatorService>(clientEndpoint);
90+
91+
return runtime;
92+
}
93+
94+
static IAsyncClientCalculatorService CreateClient(HalibutRuntime runtime, int port)
95+
{
96+
var endpoint = new ServiceEndPoint(
97+
baseUri: $"https://localhost:{port}",
98+
remoteThumbprint: Certificates.OctopusPublicThumbprint,
99+
halibutTimeoutsAndLimits: runtime.TimeoutsAndLimits
100+
);
101+
var client = runtime
102+
.CreateAsyncClient<ICalculatorService, IAsyncClientCalculatorService>(endpoint);
103+
return client;
104+
}
105+
106+
static IServiceFactory CreateServiceFactory()
107+
{
108+
var services = new DelegateServiceFactory();
109+
services.Register<ICalculatorService, IAsyncCalculatorService>(() => new AsyncCalculatorService());
110+
return services;
111+
}
112+
113+
HalibutRuntime RunServer(out int port)
114+
{
115+
var services = CreateServiceFactory();
116+
117+
var runtime = new HalibutRuntimeBuilder()
118+
.WithServerCertificate(Certificates.Octopus)
119+
.WithServiceFactory(services)
120+
.WithLogFactory(new TestLogFactory(HalibutLog))
121+
.Build();
122+
123+
runtime.Trust(Certificates.TentacleListeningPublicThumbprint);
124+
runtime.Trust(Certificates.TentaclePollingPublicThumbprint);
125+
port = runtime.Listen();
126+
127+
return runtime;
128+
}
129+
130+
public class TestLogFactory : ILogFactory
131+
{
132+
readonly ILog _log;
133+
public TestLogFactory(ILog log)
134+
{
135+
_log = log;
136+
}
137+
138+
public ILog ForEndpoint(Uri endpoint) => _log;
139+
140+
public ILog ForPrefix(string endPoint) => _log;
141+
}
142+
143+
public interface ICalculatorService
144+
{
145+
long Add(long a, long b);
146+
}
147+
148+
public interface IAsyncCalculatorService
149+
{
150+
Task<long> AddAsync(long a, long b, CancellationToken cancellationToken);
151+
}
152+
153+
public interface IAsyncClientCalculatorService
154+
{
155+
Task<long> AddAsync(long a, long b);
156+
}
157+
158+
public class AsyncCalculatorService : IAsyncCalculatorService
159+
{
160+
public async Task<long> AddAsync(long a, long b, CancellationToken cancellationToken)
161+
{
162+
await Task.CompletedTask;
163+
return a + b;
164+
}
165+
}
166+
}
167+
}

source/Halibut.Tests/Transport/SecureClientFixture.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,13 @@ public async Task SecureClientClearsPoolWhenAllConnectionsCorrupt()
8080
Params = new object[] { "Fred" }
8181
};
8282

83-
var tcpConnectionFactory = new TcpConnectionFactory(Certificates.Octopus, halibutTimeoutsAndLimits, new StreamFactory(), NoOpSecureConnectionObserver.Instance);
83+
var tcpConnectionFactory = new TcpConnectionFactory(
84+
Certificates.Octopus,
85+
halibutTimeoutsAndLimits,
86+
new StreamFactory(),
87+
NoOpSecureConnectionObserver.Instance,
88+
SslConfiguration.Default
89+
);
8490
var secureClient = new SecureListeningClient(GetProtocol, endpoint, Certificates.Octopus, log, connectionManager, tcpConnectionFactory);
8591
ResponseMessage response = null!;
8692

source/Halibut.Tests/Transport/SecureListenerFixture.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ public async Task SecureListenerDoesNotCreateHundredsOfIoEventsPerSecondOnWindow
7474
timeoutsAndLimits,
7575
new StreamFactory(),
7676
NoOpConnectionsObserver.Instance,
77-
NoOpSecureConnectionObserver.Instance
77+
NoOpSecureConnectionObserver.Instance,
78+
SslConfiguration.Default
7879
);
7980

8081
var idleAverage = CollectCounterValues(opsPerSec)

source/Halibut/HalibutRuntime.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public class HalibutRuntime : IHalibutRuntime
4646
readonly ISecureConnectionObserver secureConnectionObserver;
4747
readonly IActiveTcpConnectionsLimiter activeTcpConnectionsLimiter;
4848
readonly IControlMessageObserver controlMessageObserver;
49+
readonly ISslConfigurationProvider sslConfigurationProvider;
4950

5051
internal HalibutRuntime(
5152
IServiceFactory serviceFactory,
@@ -61,7 +62,8 @@ internal HalibutRuntime(
6162
IRpcObserver rpcObserver,
6263
IConnectionsObserver connectionsObserver,
6364
IControlMessageObserver controlMessageObserver,
64-
ISecureConnectionObserver secureConnectionObserver
65+
ISecureConnectionObserver secureConnectionObserver,
66+
ISslConfigurationProvider sslConfigurationProvider
6567
)
6668
{
6769
this.serverCertificate = serverCertificate;
@@ -78,9 +80,10 @@ ISecureConnectionObserver secureConnectionObserver
7880
this.connectionsObserver = connectionsObserver;
7981
this.secureConnectionObserver = secureConnectionObserver;
8082
this.controlMessageObserver = controlMessageObserver;
83+
this.sslConfigurationProvider = sslConfigurationProvider;
8184

8285
connectionManager = new ConnectionManagerAsync();
83-
this.tcpConnectionFactory = new TcpConnectionFactory(serverCertificate, TimeoutsAndLimits, streamFactory, secureConnectionObserver);
86+
tcpConnectionFactory = new TcpConnectionFactory(serverCertificate, TimeoutsAndLimits, streamFactory, secureConnectionObserver, sslConfigurationProvider);
8487
activeTcpConnectionsLimiter = new ActiveTcpConnectionsLimiter(TimeoutsAndLimits);
8588
}
8689

@@ -135,7 +138,8 @@ public int Listen(IPEndPoint endpoint)
135138
TimeoutsAndLimits,
136139
streamFactory,
137140
connectionsObserver,
138-
secureConnectionObserver
141+
secureConnectionObserver,
142+
sslConfigurationProvider
139143
);
140144

141145
listeners.DoWithExclusiveAccess(l =>
@@ -202,7 +206,7 @@ public async Task<ServiceEndPoint> DiscoverAsync(Uri uri, CancellationToken canc
202206

203207
public async Task<ServiceEndPoint> DiscoverAsync(ServiceEndPoint endpoint, CancellationToken cancellationToken)
204208
{
205-
var client = new DiscoveryClient(streamFactory);
209+
var client = new DiscoveryClient(streamFactory, sslConfigurationProvider);
206210
return await client.DiscoverAsync(endpoint, TimeoutsAndLimits, cancellationToken);
207211
}
208212

source/Halibut/HalibutRuntimeBuilder.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Halibut.Queue;
66
using Halibut.Queue.MessageStreamWrapping;
77
using Halibut.ServiceModel;
8+
using Halibut.Transport;
89
using Halibut.Transport.Observability;
910
using Halibut.Transport.Protocol;
1011
using Halibut.Transport.Streams;
@@ -31,6 +32,7 @@ public class HalibutRuntimeBuilder
3132
ISecureConnectionObserver? secureConnectionObserver;
3233
IControlMessageObserver? controlMessageObserver;
3334
MessageStreamWrappers queueMessageStreamWrappers = new();
35+
ISslConfigurationProvider? sslConfigurationProvider;
3436

3537
public HalibutRuntimeBuilder WithQueueMessageStreamWrappers(MessageStreamWrappers queueMessageStreamWrappers)
3638
{
@@ -50,6 +52,12 @@ public HalibutRuntimeBuilder WithSecureConnectionObserver(ISecureConnectionObser
5052
return this;
5153
}
5254

55+
public HalibutRuntimeBuilder WithSslConfigurationProvider(ISslConfigurationProvider sslConfigurationProvider)
56+
{
57+
this.sslConfigurationProvider = sslConfigurationProvider;
58+
return this;
59+
}
60+
5361
internal HalibutRuntimeBuilder WithStreamFactory(IStreamFactory streamFactory)
5462
{
5563
this.streamFactory = streamFactory;
@@ -185,6 +193,7 @@ public HalibutRuntime Build()
185193
var secureConnectionObserver = this.secureConnectionObserver ?? NoOpSecureConnectionObserver.Instance;
186194
var rpcObserver = this.rpcObserver ?? new NoRpcObserver();
187195
var controlMessageObserver = this.controlMessageObserver ?? new NoOpControlMessageObserver();
196+
var sslConfigurationProvider = this.sslConfigurationProvider ?? SslConfiguration.Default;
188197

189198
var halibutRuntime = new HalibutRuntime(
190199
serviceFactory,
@@ -200,7 +209,8 @@ public HalibutRuntime Build()
200209
rpcObserver,
201210
connectionsObserver,
202211
controlMessageObserver,
203-
secureConnectionObserver
212+
secureConnectionObserver,
213+
sslConfigurationProvider
204214
);
205215

206216
if (onUnauthorizedClientConnect is not null)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2012-2013 Octopus Deploy Pty. Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System;
16+
using System.Security.Authentication;
17+
18+
namespace Halibut.Transport
19+
{
20+
/// <summary>
21+
/// Provides a default implementation of ISslConfigurationProvider that uses the system defaults.
22+
/// </summary>
23+
public class DefaultSslConfigurationProvider : ISslConfigurationProvider
24+
{
25+
public SslProtocols SupportedProtocols => SslProtocols.None;
26+
}
27+
}

source/Halibut/Transport/DiscoveryClient.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,17 @@ public class DiscoveryClient
1616
readonly LogFactory logs = new ();
1717

1818
readonly IStreamFactory streamFactory;
19+
readonly ISslConfigurationProvider sslConfigurationProvider;
1920

2021
public DiscoveryClient(IStreamFactory streamFactory)
22+
: this(streamFactory, SslConfiguration.Default)
23+
{
24+
}
25+
26+
public DiscoveryClient(IStreamFactory streamFactory, ISslConfigurationProvider sslConfigurationProvider)
2127
{
2228
this.streamFactory = streamFactory;
29+
this.sslConfigurationProvider = sslConfigurationProvider;
2330
}
2431

2532
public async Task<ServiceEndPoint> DiscoverAsync(ServiceEndPoint serviceEndpoint, HalibutTimeoutsAndLimits halibutTimeoutsAndLimits, CancellationToken cancellationToken)
@@ -45,10 +52,15 @@ public async Task<ServiceEndPoint> DiscoverAsync(ServiceEndPoint serviceEndpoint
4552
await ssl.AuthenticateAsClientAsync(
4653
serviceEndpoint.BaseUri.Host,
4754
new X509Certificate2Collection(),
48-
SslConfiguration.SupportedProtocols,
55+
sslConfigurationProvider.SupportedProtocols,
4956
false);
5057
#else
51-
await ssl.AuthenticateAsClientEnforcingTimeout(serviceEndpoint, new X509Certificate2Collection(), cancellationToken);
58+
await ssl.AuthenticateAsClientEnforcingTimeout(
59+
serviceEndpoint,
60+
new X509Certificate2Collection(),
61+
sslConfigurationProvider,
62+
cancellationToken
63+
);
5264
#endif
5365
await ssl.WriteAsync(HelloLine, 0, HelloLine.Length, cancellationToken);
5466
await ssl.FlushAsync(cancellationToken);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2012-2013 Octopus Deploy Pty. Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System.Security.Authentication;
16+
17+
namespace Halibut.Transport
18+
{
19+
public interface ISslConfigurationProvider
20+
{
21+
public SslProtocols SupportedProtocols { get; }
22+
}
23+
}

0 commit comments

Comments
 (0)