Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SSL support for Remote.Hosting #345

Merged
merged 5 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

{
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