Skip to content

Commit

Permalink
Add SSL support for Remote.Hosting (#345)
Browse files Browse the repository at this point in the history
* Add SSL support for Remote.Hosting

* Add missing cert file

* Update API approval list

* Remove multi-transport support
  • Loading branch information
Arkatufus committed Aug 3, 2023
1 parent c4b4a2a commit 3d64b68
Show file tree
Hide file tree
Showing 7 changed files with 321 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,31 @@
public static Akka.Hosting.AkkaConfigurationBuilder WithRemoting(this Akka.Hosting.AkkaConfigurationBuilder builder, System.Action<Akka.Remote.Hosting.RemoteOptions> configure) { }
public static Akka.Hosting.AkkaConfigurationBuilder WithRemoting(this Akka.Hosting.AkkaConfigurationBuilder builder, string? hostname = null, int? port = default, string? publicHostname = null, int? publicPort = default) { }
}
public sealed class RemoteOptions
public class RemoteOptions
{
public RemoteOptions() { }
public bool? EnableSsl { get; set; }
public string? HostName { get; set; }
public int? Port { get; set; }
public string? PublicHostName { get; set; }
public int? PublicPort { get; set; }
public override string ToString() { }
public Akka.Remote.Hosting.SslOptions Ssl { get; set; }
}
public sealed class SslCertificateOptions
{
public SslCertificateOptions() { }
public string? Password { get; set; }
public string? Path { get; set; }
public string? StoreLocation { get; set; }
public string? StoreName { get; set; }
public string? Thumbprint { get; set; }
public bool? UseThumbprintOverFile { get; set; }
}
public sealed class SslOptions
{
public SslOptions() { }
public Akka.Remote.Hosting.SslCertificateOptions CertificateOptions { get; set; }
public bool? SuppressValidation { get; set; }
public System.Security.Cryptography.X509Certificates.X509Certificate2? X509Certificate { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,10 @@
<ItemGroup>
<ProjectReference Include="..\Akka.Remote.Hosting\Akka.Remote.Hosting.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="Resources\akka-validcert.pfx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
176 changes: 175 additions & 1 deletion src/Akka.Remote.Hosting.Tests/RemoteConfigurationSpecs.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
using System.IO;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Akka.Actor;
using Akka.Configuration;
using Akka.Hosting;
using Akka.Remote.Transport.DotNetty;
using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -41,6 +45,37 @@ public async Task EmptyWithRemotingConfigTest()
tcpConfig.GetInt("port").Should().Be(2552);
tcpConfig.GetString("public-hostname").Should().BeEmpty();
tcpConfig.GetInt("public-port").Should().Be(0);
tcpConfig.GetBoolean("enable-ssl").Should().BeFalse();
}

[Fact(DisplayName = "Empty WithRemoting should return default remoting settings")]
public async Task WithRemotingWithEmptyOptionsConfigTest()
{
// arrange
using var host = new HostBuilder().ConfigureServices(services =>
{
services.AddAkka("RemoteSys", (builder, provider) =>
{
builder.WithRemoting(new RemoteOptions());
});
}).Build();

// act
await host.StartAsync();
var actorSystem = (ExtendedActorSystem)host.Services.GetRequiredService<ActorSystem>();
var config = actorSystem.Settings.Config;
var adapters = config.GetStringList("akka.remote.enabled-transports");
var tcpConfig = config.GetConfig("akka.remote.dot-netty.tcp");

// assert
adapters.Count.Should().Be(1);
adapters[0].Should().Be("akka.remote.dot-netty.tcp");

tcpConfig.GetString("hostname").Should().BeEmpty();
tcpConfig.GetInt("port").Should().Be(2552);
tcpConfig.GetString("public-hostname").Should().BeEmpty();
tcpConfig.GetInt("public-port").Should().Be(0);
tcpConfig.GetBoolean("enable-ssl").Should().BeFalse();
}

[Fact(DisplayName = "WithRemoting should override remote settings")]
Expand Down Expand Up @@ -136,6 +171,145 @@ public async Task WithRemotingConfigOverrideTest()
tcpConfig.GetInt("public-port").Should().Be(12345);
}

[Fact(DisplayName = "RemoteOptions should override remote settings that are overriden")]
public void WithRemotingOptionsOverrideTest()
{
// arrange
var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "test");
builder.WithRemoting(new RemoteOptions
{
HostName = "a",
PublicHostName = "b",
Port = 123,
PublicPort = 456,
EnableSsl = true,
Ssl = new SslOptions
{
SuppressValidation = true,
CertificateOptions = new SslCertificateOptions
{
Path = "c",
Password = "d",
UseThumbprintOverFile = true,
Thumbprint = "e",
StoreName = "f",
StoreLocation = "g",
}
}
});

// act
var config = builder.Configuration.Value;
var tcpConfig = config.GetConfig("akka.remote.dot-netty.tcp");

// assert
tcpConfig.GetString("hostname").Should().Be("a");
tcpConfig.GetInt("port").Should().Be(123);
tcpConfig.GetString("public-hostname").Should().Be("b");
tcpConfig.GetInt("public-port").Should().Be(456);

var sslConfig = tcpConfig.GetConfig("ssl");
sslConfig.GetBoolean("suppress-validation").Should().BeTrue();

var certConfig = sslConfig.GetConfig("certificate");
certConfig.GetString("path").Should().Be("c");
certConfig.GetString("password").Should().Be("d");
certConfig.GetBoolean("use-thumbprint-over-file").Should().BeTrue();
certConfig.GetString("thumbprint").Should().Be("e");
certConfig.GetString("store-name").Should().Be("f");
certConfig.GetString("store-location").Should().Be("g");
}

[Fact(DisplayName = "RemoteOptions using configurator should override remote settings that are overriden")]
public void WithRemotingOptionsConfiguratorOverrideTest()
{
// arrange
var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "test");
builder.WithRemoting(opt =>
{
opt.HostName = "a";
opt.PublicHostName = "b";
opt.Port = 123;
opt.PublicPort = 456;
opt.EnableSsl = true;
opt.Ssl.SuppressValidation = true;
opt.Ssl.CertificateOptions.Path = "c";
opt.Ssl.CertificateOptions.Password = "d";
opt.Ssl.CertificateOptions.UseThumbprintOverFile = true;
opt.Ssl.CertificateOptions.Thumbprint = "e";
opt.Ssl.CertificateOptions.StoreName = "f";
opt.Ssl.CertificateOptions.StoreLocation = "g";
});

// act
var config = builder.Configuration.Value;
var tcpConfig = config.GetConfig("akka.remote.dot-netty.tcp");

// assert
tcpConfig.GetString("hostname").Should().Be("a");
tcpConfig.GetInt("port").Should().Be(123);
tcpConfig.GetString("public-hostname").Should().Be("b");
tcpConfig.GetInt("public-port").Should().Be(456);

var sslConfig = tcpConfig.GetConfig("ssl");
sslConfig.GetBoolean("suppress-validation").Should().BeTrue();

var certConfig = sslConfig.GetConfig("certificate");
certConfig.GetString("path").Should().Be("c");
certConfig.GetString("password").Should().Be("d");
certConfig.GetBoolean("use-thumbprint-over-file").Should().BeTrue();
certConfig.GetString("thumbprint").Should().Be("e");
certConfig.GetString("store-name").Should().Be("f");
certConfig.GetString("store-location").Should().Be("g");
}

[Fact(DisplayName = "RemoteOptions with explicit certificate and ssl enabled should use provided certificate")]
public void WithRemotingOptionsSslEnabledCertificateTest()
{
// 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 = true,
X509Certificate = certificate
}
});

// act
var setup = (DotNettySslSetup) builder.Setups.First(s => s is DotNettySslSetup);

// assert
setup.SuppressValidation.Should().BeTrue();
setup.Certificate.Should().Be(certificate);
}

[Fact(DisplayName = "RemoteOptions with explicit certificate and ssl disabled should ignore provided certificate")]
public void WithRemotingOptionsSslDisabledCertificateTest()
{
// arrange
var certificate = new X509Certificate2("./Resources/akka-validcert.pfx", "password");
var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "test");
builder.WithRemoting(new RemoteOptions
{
EnableSsl = false,
Ssl = new SslOptions
{
SuppressValidation = true,
X509Certificate = certificate
}
});

// act
var setup = builder.Setups.FirstOrDefault(s => s is DotNettySslSetup);

// assert
setup.Should().BeNull();
}

[Fact]
public async Task AkkaRemoteShouldUsePublicHostnameCorrectly()
{
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion src/Akka.Remote.Hosting/Akka.Remote.Hosting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<TargetFramework>$(LibraryFramework)</TargetFramework>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Description>Akka.Remote Microsoft.Extensions.Hosting support.</Description>
<LangVersion>9.0</LangVersion>
<LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>

Expand Down
6 changes: 1 addition & 5 deletions src/Akka.Remote.Hosting/AkkaRemoteHostingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,7 @@ public static class AkkaRemoteHostingExtensions
this AkkaConfigurationBuilder builder,
RemoteOptions options)
{
var config = options.ToString();

// prepend the remoting configuration to the front
if(!string.IsNullOrEmpty(config))
builder.AddHocon(config, HoconAddMode.Prepend);
options.Build(builder);

if (builder.ActorRefProvider.HasValue)
{
Expand Down

0 comments on commit 3d64b68

Please sign in to comment.