diff --git a/Directory.Build.props b/Directory.Build.props
index 1e0c7fbb..2f0a8d92 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,12 +2,17 @@
Copyright © 2013-2025 Akka.NET Team
Akka.NET Team
- 1.5.52
- **API Changes**
+ 1.5.53
+ **New Features**
+* [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:
+ * `RequireMutualAuthentication` - Enables mutual TLS (mTLS) authentication (default: true)
+ * `ValidateCertificateHostname` - Controls certificate hostname validation (default: false)
+
+**API Changes**
* [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 => journal.AddWriteEventAdapter<T>(...))`. The deprecated property will be removed in v1.6.0.
**Updates**
-* [Bump Akka version from 1.5.51 to 1.5.52](https://github.com/akkadotnet/akka.net/releases/tag/1.5.52)
+* [Bump Akka version from 1.5.52 to 1.5.53](https://github.com/akkadotnet/akka.net/releases/tag/1.5.53)
akkalogo.png
https://github.com/akkadotnet/Akka.Hosting
@@ -29,7 +34,7 @@
17.11.1
6.0.3
3.1.5
- 1.5.52
+ 1.5.53
[6.0.0,)
[6.0.10,)
diff --git a/src/Akka.Hosting.API.Tests/verify/CoreApiSpec.ApproveRemoting.verified.txt b/src/Akka.Hosting.API.Tests/verify/CoreApiSpec.ApproveRemoting.verified.txt
index cd562a4f..ad14c62d 100644
--- a/src/Akka.Hosting.API.Tests/verify/CoreApiSpec.ApproveRemoting.verified.txt
+++ b/src/Akka.Hosting.API.Tests/verify/CoreApiSpec.ApproveRemoting.verified.txt
@@ -54,7 +54,9 @@
{
public SslOptions() { }
public Akka.Remote.Hosting.SslCertificateOptions CertificateOptions { get; set; }
+ public bool? RequireMutualAuthentication { get; set; }
public bool? SuppressValidation { get; set; }
+ public bool? ValidateCertificateHostname { get; set; }
public System.Security.Cryptography.X509Certificates.X509Certificate2? X509Certificate { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Akka.Remote.Hosting.Tests/RemoteConfigurationSpecs.cs b/src/Akka.Remote.Hosting.Tests/RemoteConfigurationSpecs.cs
index ad434e79..995b18e7 100644
--- a/src/Akka.Remote.Hosting.Tests/RemoteConfigurationSpecs.cs
+++ b/src/Akka.Remote.Hosting.Tests/RemoteConfigurationSpecs.cs
@@ -433,17 +433,126 @@ public void WithRemotingOptionsSslDisabledCertificateTest()
EnableSsl = false,
Ssl = new SslOptions
{
- SuppressValidation = true,
+ SuppressValidation = true,
X509Certificate = certificate
}
});
-
+
// act
var setup = builder.Setups.FirstOrDefault(s => s is DotNettySslSetup);
// assert
setup.Should().BeNull();
}
+
+ [Fact(DisplayName = "RemoteOptions with new SSL/TLS settings should generate correct HOCON configuration")]
+ public void WithRemotingNewSslSettingsHoconTest()
+ {
+ // arrange
+ var certificate = new X509Certificate2("./Resources/akka-validcert.pfx", "password");
+ var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "test");
+ builder.WithRemoting(new RemoteOptions
+ {
+ EnableSsl = true,
+ Ssl = new SslOptions
+ {
+ SuppressValidation = false,
+ RequireMutualAuthentication = false, // Explicitly set to false for testing
+ ValidateCertificateHostname = true, // Explicitly set to true for testing
+ X509Certificate = certificate
+ }
+ });
+
+ // act
+ var config = builder.Configuration.Value;
+ var sslConfig = config.GetConfig("akka.remote.dot-netty.tcp.ssl");
+
+ // assert
+ sslConfig.GetBoolean("suppress-validation").Should().BeFalse();
+ sslConfig.GetBoolean("require-mutual-authentication").Should().BeFalse();
+ sslConfig.GetBoolean("validate-certificate-hostname").Should().BeTrue();
+ }
+
+ [Fact(DisplayName = "RemoteOptions with new SSL/TLS settings should properly configure DotNettySslSetup")]
+ public void WithRemotingNewSslSettingsDotNettySslSetupTest()
+ {
+ // arrange
+ var certificate = new X509Certificate2("./Resources/akka-validcert.pfx", "password");
+ var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "test");
+ builder.WithRemoting(new RemoteOptions
+ {
+ EnableSsl = true,
+ Ssl = new SslOptions
+ {
+ SuppressValidation = false,
+ RequireMutualAuthentication = false,
+ ValidateCertificateHostname = true,
+ X509Certificate = certificate
+ }
+ });
+
+ // act
+ var setup = (DotNettySslSetup)builder.Setups.First(s => s is DotNettySslSetup);
+
+ // assert
+ setup.SuppressValidation.Should().BeFalse();
+ setup.Certificate.Should().Be(certificate);
+ // Note: The RequireMutualAuthentication and ValidateCertificateHostname properties
+ // are now passed to DotNettySslSetup via the 4-parameter constructor in Akka.NET v1.5.53
+ setup.RequireMutualAuthentication.Should().BeFalse();
+ setup.ValidateCertificateHostname.Should().BeTrue();
+ }
+
+ [Fact(DisplayName = "RemoteOptions without new SSL/TLS settings should use default values")]
+ public void WithRemotingDefaultSslSettingsTest()
+ {
+ // arrange
+ var certificate = new X509Certificate2("./Resources/akka-validcert.pfx", "password");
+ var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "test");
+ builder.WithRemoting(new RemoteOptions
+ {
+ EnableSsl = true,
+ Ssl = new SslOptions
+ {
+ X509Certificate = certificate
+ // RequireMutualAuthentication and ValidateCertificateHostname not specified
+ }
+ });
+
+ // act
+ var setup = (DotNettySslSetup)builder.Setups.First(s => s is DotNettySslSetup);
+
+ // assert
+ setup.Should().NotBeNull();
+ setup.Certificate.Should().Be(certificate);
+ setup.RequireMutualAuthentication.Should().BeTrue();
+ setup.ValidateCertificateHostname.Should().BeFalse();
+ }
+
+ [Fact(DisplayName = "RemoteOptions using configurator should set new SSL/TLS properties correctly")]
+ public void WithRemotingConfiguratorNewSslSettingsTest()
+ {
+ // arrange
+ var certificate = new X509Certificate2("./Resources/akka-validcert.pfx", "password");
+ var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "test");
+ builder.WithRemoting(opt =>
+ {
+ opt.EnableSsl = true;
+ opt.Ssl.RequireMutualAuthentication = true;
+ opt.Ssl.ValidateCertificateHostname = false;
+ opt.Ssl.X509Certificate = certificate;
+ });
+
+ // act
+ var config = builder.Configuration.Value;
+ var tcpConfig = config.GetConfig("akka.remote.dot-netty.tcp");
+ var sslConfig = tcpConfig.GetConfig("ssl");
+
+ // assert
+ tcpConfig.GetBoolean("enable-ssl").Should().BeTrue();
+ sslConfig.GetBoolean("require-mutual-authentication").Should().BeTrue();
+ sslConfig.GetBoolean("validate-certificate-hostname").Should().BeFalse();
+ }
[Fact]
public async Task AkkaRemoteShouldUsePublicHostnameCorrectly()
diff --git a/src/Akka.Remote.Hosting/RemoteOptions.cs b/src/Akka.Remote.Hosting/RemoteOptions.cs
index 7bebad6a..9d93954e 100644
--- a/src/Akka.Remote.Hosting/RemoteOptions.cs
+++ b/src/Akka.Remote.Hosting/RemoteOptions.cs
@@ -104,12 +104,24 @@ internal void Build(AkkaConfigurationBuilder builder)
if (sb.Length > 0)
builder.AddHocon(sb.ToString(), HoconAddMode.Prepend);
-
- if (EnableSsl is false || Ssl.X509Certificate == null)
+
+ if (EnableSsl is false || Ssl.X509Certificate == null)
return;
-
+
var suppressValidation = Ssl.SuppressValidation ?? false;
- builder.AddSetup(new DotNettySslSetup(Ssl.X509Certificate, suppressValidation));
+ var requireMutualAuth = Ssl.RequireMutualAuthentication ?? true; // Default to true as per v1.5.52
+ var validateHostname = Ssl.ValidateCertificateHostname ?? false; // Default to false as per v1.5.53
+
+ // Use the 4-parameter constructor if any of the new settings are provided, otherwise use the legacy constructor for backward compatibility
+ if (Ssl.RequireMutualAuthentication.HasValue || Ssl.ValidateCertificateHostname.HasValue)
+ {
+ builder.AddSetup(new DotNettySslSetup(Ssl.X509Certificate, suppressValidation, requireMutualAuth, validateHostname));
+ }
+ else
+ {
+ // Use legacy constructor for backward compatibility when new settings are not specified
+ builder.AddSetup(new DotNettySslSetup(Ssl.X509Certificate, suppressValidation));
+ }
}
private void Build(StringBuilder builder)
@@ -197,18 +209,55 @@ public sealed class SslOptions
public X509Certificate2? X509Certificate { get; set; }
public SslCertificateOptions CertificateOptions { get; set; } = new ();
+ ///
+ ///
+ /// When set to true, enables mutual TLS (mTLS) authentication where both client and server
+ /// must present valid certificates with accessible private keys during the TLS handshake.
+ ///
+ ///
+ /// This provides defense-in-depth security by ensuring bidirectional authentication and
+ /// preventing asymmetric connectivity issues in peer-to-peer Akka.Remote connections.
+ ///
+ /// Default: true (as of Akka.NET v1.5.52)
+ ///
+ public bool? RequireMutualAuthentication { get; set; }
+
+ ///
+ ///
+ /// Controls whether certificate hostname validation is performed during TLS handshake.
+ ///
+ ///
+ /// When enabled (true): Traditional TLS hostname validation is performed - certificate CN/SAN must match the target hostname.
+ /// When disabled (false): Only validates certificate chain against CA, ignores hostname mismatches.
+ ///
+ ///
+ /// Disabling hostname validation may be necessary for:
+ /// - Mutual TLS with per-node certificates in P2P clusters
+ /// - IP-based connections where certificates use DNS names
+ /// - Service discovery with dynamic addresses
+ ///
+ /// Default: false (as of Akka.NET v1.5.53)
+ ///
+ public bool? ValidateCertificateHostname { get; set; }
+
internal void Build(StringBuilder builder)
{
var sb = new StringBuilder();
-
+
if (SuppressValidation is not null)
sb.AppendLine($"suppress-validation = {SuppressValidation.ToHocon()}");
-
+
+ if (RequireMutualAuthentication is not null)
+ sb.AppendLine($"require-mutual-authentication = {RequireMutualAuthentication.ToHocon()}");
+
+ if (ValidateCertificateHostname is not null)
+ sb.AppendLine($"validate-certificate-hostname = {ValidateCertificateHostname.ToHocon()}");
+
CertificateOptions.Build(sb);
-
+
if(sb.Length == 0)
return;
-
+
sb.Insert(0, "ssl {");
sb.AppendLine("}");
builder.Append(sb);