Skip to content

Commit af0ef22

Browse files
authored
Add SSL TLS configuration settings changes from Akka v1.5.52 and v1.5.53 (#675)
* feat(Remote.Hosting): Add SSL/TLS configuration options from Akka.NET v1.5.52 and v1.5.53 - Added RequireMutualAuthentication property to SslOptions (default: true as per v1.5.52) - Enables mutual TLS (mTLS) authentication where both client and server must present valid certificates - Provides defense-in-depth security for peer-to-peer Akka.Remote connections - Added ValidateCertificateHostname property to SslOptions (default: false as per v1.5.53) - Controls whether certificate hostname validation is performed during TLS handshake - Can be disabled for mutual TLS with per-node certificates, IP-based connections, or dynamic addresses - Updated Build method to include new settings in HOCON configuration - Added comprehensive unit tests for the new SSL/TLS settings - Added TODO comment for future DotNettySslSetup constructor usage when Akka.NET is upgraded to v1.5.53+ Note: Currently these settings are only applied via HOCON configuration. Direct DotNettySslSetup constructor support will be enabled when Akka.NET dependency is upgraded to v1.5.53 or later. * chore: Upgrade Akka.NET to v1.5.53 and enable new SSL/TLS constructor - Upgraded Akka.NET dependency from 1.5.52 to 1.5.53 - Updated version prefix to 1.5.53 - Removed "future implementation" comment and enabled the 4-parameter DotNettySslSetup constructor - Now directly passing RequireMutualAuthentication and ValidateCertificateHostname to DotNettySslSetup - Updated release notes to reflect the new SSL/TLS configuration features - All tests pass with the new version The new SSL/TLS settings are now fully integrated with both HOCON configuration and the DotNettySslSetup class. * Update API Approval list * harden unit tests
1 parent d716471 commit af0ef22

File tree

4 files changed

+179
-14
lines changed

4 files changed

+179
-14
lines changed

Directory.Build.props

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22
<PropertyGroup>
33
<Copyright>Copyright © 2013-2025 Akka.NET Team</Copyright>
44
<Authors>Akka.NET Team</Authors>
5-
<VersionPrefix>1.5.52</VersionPrefix>
6-
<PackageReleaseNotes>**API Changes**
5+
<VersionPrefix>1.5.53</VersionPrefix>
6+
<PackageReleaseNotes>**New Features**
7+
* [Add SSL/TLS configuration options from Akka.NET v1.5.52 and v1.5.53](https://github.com/akkadotnet/Akka.Hosting/pull/XXX) - Added support for new SSL/TLS configuration options:
8+
* `RequireMutualAuthentication` - Enables mutual TLS (mTLS) authentication (default: true)
9+
* `ValidateCertificateHostname` - Controls certificate hostname validation (default: false)
10+
11+
**API Changes**
712
* [Deprecate JournalOptions.Adapters property in favor of callback API](https://github.com/akkadotnet/Akka.Hosting/pull/669) - resolved [issue #665](https://github.com/akkadotnet/Akka.Hosting/issues/665) by deprecating the `JournalOptions.Adapters` property. Users should migrate to the unified callback pattern: `builder.WithJournal(options, journal =&gt; journal.AddWriteEventAdapter&lt;T&gt;(...))`. The deprecated property will be removed in v1.6.0.
813

914
**Updates**
10-
* [Bump Akka version from 1.5.51 to 1.5.52](https://github.com/akkadotnet/akka.net/releases/tag/1.5.52)</PackageReleaseNotes>
15+
* [Bump Akka version from 1.5.52 to 1.5.53](https://github.com/akkadotnet/akka.net/releases/tag/1.5.53)</PackageReleaseNotes>
1116
<PackageIcon>akkalogo.png</PackageIcon>
1217
<PackageProjectUrl>
1318
https://github.com/akkadotnet/Akka.Hosting
@@ -29,7 +34,7 @@
2934
<TestSdkVersion>17.11.1</TestSdkVersion>
3035
<CoverletVersion>6.0.3</CoverletVersion>
3136
<XunitRunneVisualstudio>3.1.5</XunitRunneVisualstudio>
32-
<AkkaVersion>1.5.52</AkkaVersion>
37+
<AkkaVersion>1.5.53</AkkaVersion>
3338
<MicrosoftExtensionsVersion>[6.0.0,)</MicrosoftExtensionsVersion>
3439
<SystemTextJsonVersion>[6.0.10,)</SystemTextJsonVersion>
3540
</PropertyGroup>

src/Akka.Hosting.API.Tests/verify/CoreApiSpec.ApproveRemoting.verified.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@
5454
{
5555
public SslOptions() { }
5656
public Akka.Remote.Hosting.SslCertificateOptions CertificateOptions { get; set; }
57+
public bool? RequireMutualAuthentication { get; set; }
5758
public bool? SuppressValidation { get; set; }
59+
public bool? ValidateCertificateHostname { get; set; }
5860
public System.Security.Cryptography.X509Certificates.X509Certificate2? X509Certificate { get; set; }
5961
}
6062
}

src/Akka.Remote.Hosting.Tests/RemoteConfigurationSpecs.cs

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,17 +433,126 @@ public void WithRemotingOptionsSslDisabledCertificateTest()
433433
EnableSsl = false,
434434
Ssl = new SslOptions
435435
{
436-
SuppressValidation = true,
436+
SuppressValidation = true,
437437
X509Certificate = certificate
438438
}
439439
});
440-
440+
441441
// act
442442
var setup = builder.Setups.FirstOrDefault(s => s is DotNettySslSetup);
443443

444444
// assert
445445
setup.Should().BeNull();
446446
}
447+
448+
[Fact(DisplayName = "RemoteOptions with new SSL/TLS settings should generate correct HOCON configuration")]
449+
public void WithRemotingNewSslSettingsHoconTest()
450+
{
451+
// arrange
452+
var certificate = new X509Certificate2("./Resources/akka-validcert.pfx", "password");
453+
var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "test");
454+
builder.WithRemoting(new RemoteOptions
455+
{
456+
EnableSsl = true,
457+
Ssl = new SslOptions
458+
{
459+
SuppressValidation = false,
460+
RequireMutualAuthentication = false, // Explicitly set to false for testing
461+
ValidateCertificateHostname = true, // Explicitly set to true for testing
462+
X509Certificate = certificate
463+
}
464+
});
465+
466+
// act
467+
var config = builder.Configuration.Value;
468+
var sslConfig = config.GetConfig("akka.remote.dot-netty.tcp.ssl");
469+
470+
// assert
471+
sslConfig.GetBoolean("suppress-validation").Should().BeFalse();
472+
sslConfig.GetBoolean("require-mutual-authentication").Should().BeFalse();
473+
sslConfig.GetBoolean("validate-certificate-hostname").Should().BeTrue();
474+
}
475+
476+
[Fact(DisplayName = "RemoteOptions with new SSL/TLS settings should properly configure DotNettySslSetup")]
477+
public void WithRemotingNewSslSettingsDotNettySslSetupTest()
478+
{
479+
// arrange
480+
var certificate = new X509Certificate2("./Resources/akka-validcert.pfx", "password");
481+
var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "test");
482+
builder.WithRemoting(new RemoteOptions
483+
{
484+
EnableSsl = true,
485+
Ssl = new SslOptions
486+
{
487+
SuppressValidation = false,
488+
RequireMutualAuthentication = false,
489+
ValidateCertificateHostname = true,
490+
X509Certificate = certificate
491+
}
492+
});
493+
494+
// act
495+
var setup = (DotNettySslSetup)builder.Setups.First(s => s is DotNettySslSetup);
496+
497+
// assert
498+
setup.SuppressValidation.Should().BeFalse();
499+
setup.Certificate.Should().Be(certificate);
500+
// Note: The RequireMutualAuthentication and ValidateCertificateHostname properties
501+
// are now passed to DotNettySslSetup via the 4-parameter constructor in Akka.NET v1.5.53
502+
setup.RequireMutualAuthentication.Should().BeFalse();
503+
setup.ValidateCertificateHostname.Should().BeTrue();
504+
}
505+
506+
[Fact(DisplayName = "RemoteOptions without new SSL/TLS settings should use default values")]
507+
public void WithRemotingDefaultSslSettingsTest()
508+
{
509+
// arrange
510+
var certificate = new X509Certificate2("./Resources/akka-validcert.pfx", "password");
511+
var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "test");
512+
builder.WithRemoting(new RemoteOptions
513+
{
514+
EnableSsl = true,
515+
Ssl = new SslOptions
516+
{
517+
X509Certificate = certificate
518+
// RequireMutualAuthentication and ValidateCertificateHostname not specified
519+
}
520+
});
521+
522+
// act
523+
var setup = (DotNettySslSetup)builder.Setups.First(s => s is DotNettySslSetup);
524+
525+
// assert
526+
setup.Should().NotBeNull();
527+
setup.Certificate.Should().Be(certificate);
528+
setup.RequireMutualAuthentication.Should().BeTrue();
529+
setup.ValidateCertificateHostname.Should().BeFalse();
530+
}
531+
532+
[Fact(DisplayName = "RemoteOptions using configurator should set new SSL/TLS properties correctly")]
533+
public void WithRemotingConfiguratorNewSslSettingsTest()
534+
{
535+
// arrange
536+
var certificate = new X509Certificate2("./Resources/akka-validcert.pfx", "password");
537+
var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "test");
538+
builder.WithRemoting(opt =>
539+
{
540+
opt.EnableSsl = true;
541+
opt.Ssl.RequireMutualAuthentication = true;
542+
opt.Ssl.ValidateCertificateHostname = false;
543+
opt.Ssl.X509Certificate = certificate;
544+
});
545+
546+
// act
547+
var config = builder.Configuration.Value;
548+
var tcpConfig = config.GetConfig("akka.remote.dot-netty.tcp");
549+
var sslConfig = tcpConfig.GetConfig("ssl");
550+
551+
// assert
552+
tcpConfig.GetBoolean("enable-ssl").Should().BeTrue();
553+
sslConfig.GetBoolean("require-mutual-authentication").Should().BeTrue();
554+
sslConfig.GetBoolean("validate-certificate-hostname").Should().BeFalse();
555+
}
447556

448557
[Fact]
449558
public async Task AkkaRemoteShouldUsePublicHostnameCorrectly()

src/Akka.Remote.Hosting/RemoteOptions.cs

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,24 @@ internal void Build(AkkaConfigurationBuilder builder)
104104

105105
if (sb.Length > 0)
106106
builder.AddHocon(sb.ToString(), HoconAddMode.Prepend);
107-
108-
if (EnableSsl is false || Ssl.X509Certificate == null)
107+
108+
if (EnableSsl is false || Ssl.X509Certificate == null)
109109
return;
110-
110+
111111
var suppressValidation = Ssl.SuppressValidation ?? false;
112-
builder.AddSetup(new DotNettySslSetup(Ssl.X509Certificate, suppressValidation));
112+
var requireMutualAuth = Ssl.RequireMutualAuthentication ?? true; // Default to true as per v1.5.52
113+
var validateHostname = Ssl.ValidateCertificateHostname ?? false; // Default to false as per v1.5.53
114+
115+
// Use the 4-parameter constructor if any of the new settings are provided, otherwise use the legacy constructor for backward compatibility
116+
if (Ssl.RequireMutualAuthentication.HasValue || Ssl.ValidateCertificateHostname.HasValue)
117+
{
118+
builder.AddSetup(new DotNettySslSetup(Ssl.X509Certificate, suppressValidation, requireMutualAuth, validateHostname));
119+
}
120+
else
121+
{
122+
// Use legacy constructor for backward compatibility when new settings are not specified
123+
builder.AddSetup(new DotNettySslSetup(Ssl.X509Certificate, suppressValidation));
124+
}
113125
}
114126

115127
private void Build(StringBuilder builder)
@@ -197,18 +209,55 @@ public sealed class SslOptions
197209
public X509Certificate2? X509Certificate { get; set; }
198210
public SslCertificateOptions CertificateOptions { get; set; } = new ();
199211

212+
/// <summary>
213+
/// <para>
214+
/// When set to true, enables mutual TLS (mTLS) authentication where both client and server
215+
/// must present valid certificates with accessible private keys during the TLS handshake.
216+
/// </para>
217+
/// <para>
218+
/// This provides defense-in-depth security by ensuring bidirectional authentication and
219+
/// preventing asymmetric connectivity issues in peer-to-peer Akka.Remote connections.
220+
/// </para>
221+
/// <b>Default:</b> true (as of Akka.NET v1.5.52)
222+
/// </summary>
223+
public bool? RequireMutualAuthentication { get; set; }
224+
225+
/// <summary>
226+
/// <para>
227+
/// Controls whether certificate hostname validation is performed during TLS handshake.
228+
/// </para>
229+
/// <para>
230+
/// When enabled (true): Traditional TLS hostname validation is performed - certificate CN/SAN must match the target hostname.
231+
/// When disabled (false): Only validates certificate chain against CA, ignores hostname mismatches.
232+
/// </para>
233+
/// <para>
234+
/// Disabling hostname validation may be necessary for:
235+
/// - Mutual TLS with per-node certificates in P2P clusters
236+
/// - IP-based connections where certificates use DNS names
237+
/// - Service discovery with dynamic addresses
238+
/// </para>
239+
/// <b>Default:</b> false (as of Akka.NET v1.5.53)
240+
/// </summary>
241+
public bool? ValidateCertificateHostname { get; set; }
242+
200243
internal void Build(StringBuilder builder)
201244
{
202245
var sb = new StringBuilder();
203-
246+
204247
if (SuppressValidation is not null)
205248
sb.AppendLine($"suppress-validation = {SuppressValidation.ToHocon()}");
206-
249+
250+
if (RequireMutualAuthentication is not null)
251+
sb.AppendLine($"require-mutual-authentication = {RequireMutualAuthentication.ToHocon()}");
252+
253+
if (ValidateCertificateHostname is not null)
254+
sb.AppendLine($"validate-certificate-hostname = {ValidateCertificateHostname.ToHocon()}");
255+
207256
CertificateOptions.Build(sb);
208-
257+
209258
if(sb.Length == 0)
210259
return;
211-
260+
212261
sb.Insert(0, "ssl {");
213262
sb.AppendLine("}");
214263
builder.Append(sb);

0 commit comments

Comments
 (0)