|
| 1 | +//----------------------------------------------------------------------- |
| 2 | +// <copyright file="DotNettySslSetupSpec.cs" company="Akka.NET Project"> |
| 3 | +// Copyright (C) 2009-2022 Lightbend Inc. <http://www.lightbend.com> |
| 4 | +// Copyright (C) 2013-2025 .NET Foundation <https://github.com/akkadotnet/akka.net> |
| 5 | +// </copyright> |
| 6 | +//----------------------------------------------------------------------- |
| 7 | + |
| 8 | +using System; |
| 9 | +using System.Security.Cryptography.X509Certificates; |
| 10 | +using System.Threading.Tasks; |
| 11 | +using Akka.Actor; |
| 12 | +using Akka.Actor.Setup; |
| 13 | +using Akka.Configuration; |
| 14 | +using Akka.Remote.Transport.DotNetty; |
| 15 | +using Akka.TestKit; |
| 16 | +using Xunit; |
| 17 | +using Xunit.Abstractions; |
| 18 | +using static Akka.Util.RuntimeDetector; |
| 19 | + |
| 20 | +namespace Akka.Remote.Tests.Transport |
| 21 | +{ |
| 22 | + public class DotNettySslSetupSpec : AkkaSpec |
| 23 | + { |
| 24 | + #region Setup / Config |
| 25 | + |
| 26 | + // valid to 01/01/2037 |
| 27 | + private const string ValidCertPath = "Resources/akka-validcert.pfx"; |
| 28 | + |
| 29 | + private const string Password = "password"; |
| 30 | + |
| 31 | + private static ActorSystemSetup TestActorSystemSetup(bool enableSsl, bool requireMutualAuth = false) |
| 32 | + { |
| 33 | + var setup = ActorSystemSetup.Empty |
| 34 | + .And(BootstrapSetup.Create() |
| 35 | + .WithConfig(ConfigurationFactory.ParseString($@" |
| 36 | +akka {{ |
| 37 | + loglevel = DEBUG |
| 38 | + actor.provider = ""Akka.Remote.RemoteActorRefProvider,Akka.Remote"" |
| 39 | + remote {{ |
| 40 | + dot-netty.tcp {{ |
| 41 | + port = 0 |
| 42 | + hostname = ""127.0.0.1"" |
| 43 | + enable-ssl = ""{enableSsl.ToString().ToLowerInvariant()}"" |
| 44 | + log-transport = true |
| 45 | + }} |
| 46 | + }} |
| 47 | +}}"))); |
| 48 | + |
| 49 | + if (!enableSsl) |
| 50 | + return setup; |
| 51 | + |
| 52 | + var certificate = new X509Certificate2(ValidCertPath, Password, X509KeyStorageFlags.DefaultKeySet); |
| 53 | + // Use the new constructor that supports mutual auth configuration |
| 54 | + return setup.And(new DotNettySslSetup(certificate, true, requireMutualAuth)); |
| 55 | + } |
| 56 | + |
| 57 | + private ActorSystem _sys2; |
| 58 | + private ActorPath _echoPath; |
| 59 | + |
| 60 | + private void Setup(bool enableSsl) |
| 61 | + { |
| 62 | + _sys2 = ActorSystem.Create("sys2", TestActorSystemSetup(enableSsl)); |
| 63 | + InitializeLogger(_sys2); |
| 64 | + |
| 65 | + _sys2.ActorOf(Props.Create<Echo>(), "echo"); |
| 66 | + |
| 67 | + var address = RARP.For(_sys2).Provider.DefaultAddress; |
| 68 | + _echoPath = new RootActorPath(address) / "user" / "echo"; |
| 69 | + } |
| 70 | + |
| 71 | + #endregion |
| 72 | + |
| 73 | + public DotNettySslSetupSpec(ITestOutputHelper output) : base(TestActorSystemSetup(true), output) |
| 74 | + { |
| 75 | + } |
| 76 | + |
| 77 | + #if !NET471 |
| 78 | + [Fact] |
| 79 | + public async Task Secure_transport_should_be_possible_between_systems_sharing_the_same_certificate() |
| 80 | + { |
| 81 | + // skip this test due to linux/mono certificate issues |
| 82 | + if (IsMono) return; |
| 83 | + |
| 84 | + Setup(true); |
| 85 | + |
| 86 | + var probe = CreateTestProbe(); |
| 87 | + |
| 88 | + await AwaitAssertAsync(async () => |
| 89 | + { |
| 90 | + Sys.ActorSelection(_echoPath).Tell("hello", probe.Ref); |
| 91 | + await probe.ExpectMsgAsync("hello", TimeSpan.FromSeconds(3)); |
| 92 | + }, TimeSpan.FromSeconds(30), TimeSpan.FromMilliseconds(100)); |
| 93 | + } |
| 94 | + #endif |
| 95 | + |
| 96 | + #if !NET471 |
| 97 | + [Fact] |
| 98 | + public async Task Mutual_TLS_should_be_configurable_via_Setup_API() |
| 99 | + { |
| 100 | + // skip this test due to linux/mono certificate issues |
| 101 | + if (IsMono) return; |
| 102 | + |
| 103 | + // Create a system with mutual TLS enabled via Setup API |
| 104 | + var setupWithMutualTls = ActorSystemSetup.Empty |
| 105 | + .And(BootstrapSetup.Create() |
| 106 | + .WithConfig(ConfigurationFactory.ParseString(@" |
| 107 | +akka { |
| 108 | + loglevel = DEBUG |
| 109 | + actor.provider = ""Akka.Remote.RemoteActorRefProvider,Akka.Remote"" |
| 110 | + remote.dot-netty.tcp { |
| 111 | + port = 0 |
| 112 | + hostname = ""127.0.0.1"" |
| 113 | + enable-ssl = true |
| 114 | + log-transport = true |
| 115 | + } |
| 116 | +}"))) |
| 117 | + .And(new DotNettySslSetup( |
| 118 | + new X509Certificate2(ValidCertPath, Password, X509KeyStorageFlags.DefaultKeySet), |
| 119 | + suppressValidation: true, |
| 120 | + requireMutualAuthentication: true)); |
| 121 | + |
| 122 | + var sys3 = ActorSystem.Create("sys3", setupWithMutualTls); |
| 123 | + try |
| 124 | + { |
| 125 | + InitializeLogger(sys3); |
| 126 | + sys3.ActorOf(Props.Create<Echo>(), "echo"); |
| 127 | + |
| 128 | + // System should start successfully with mutual TLS |
| 129 | + await Task.Delay(TimeSpan.FromSeconds(1)); |
| 130 | + Assert.False(sys3.WhenTerminated.IsCompleted); |
| 131 | + |
| 132 | + // Verify the transport settings have mutual TLS enabled |
| 133 | + var transport = RARP.For(sys3).Provider.Transport; |
| 134 | + Assert.NotNull(transport); |
| 135 | + } |
| 136 | + finally |
| 137 | + { |
| 138 | + await ShutdownAsync(sys3); |
| 139 | + } |
| 140 | + } |
| 141 | + #endif |
| 142 | + |
| 143 | + [Fact] |
| 144 | + public async Task Secure_transport_should_NOT_be_possible_between_systems_using_SSL_and_one_not_using_it() |
| 145 | + { |
| 146 | + Setup(false); |
| 147 | + |
| 148 | + var probe = CreateTestProbe(); |
| 149 | + await Assert.ThrowsAsync<RemoteTransportException>(async () => |
| 150 | + { |
| 151 | + Sys.ActorSelection(_echoPath).Tell("hello", probe.Ref); |
| 152 | + await probe.ExpectNoMsgAsync(); |
| 153 | + }); |
| 154 | + } |
| 155 | + |
| 156 | + #region helper classes / methods |
| 157 | + |
| 158 | + protected override void AfterAll() |
| 159 | + { |
| 160 | + base.AfterAll(); |
| 161 | + Shutdown(_sys2, TimeSpan.FromSeconds(3)); |
| 162 | + } |
| 163 | + |
| 164 | + private class Echo : ReceiveActor |
| 165 | + { |
| 166 | + public Echo() |
| 167 | + { |
| 168 | + Receive<string>(str => Sender.Tell(str)); |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + #endregion |
| 173 | + } |
| 174 | +} |
0 commit comments