Skip to content

Commit

Permalink
Domain Sockets and Named Pipes (#151)
Browse files Browse the repository at this point in the history
  • Loading branch information
pawelvds authored Jan 22, 2024
1 parent c64ab3d commit 85721d9
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 72 deletions.
15 changes: 12 additions & 3 deletions src/fiskaltrust.Launcher.Common/Configuration/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,18 @@ private T WithDefault<T>(T value, Func<T> defaultValue)
[JsonPropertyName("accessToken")]
public string? AccessToken { get => _accessToken; set => _accessToken = value; }

private int? _launcherPort;
[JsonPropertyName("launcherPort")]
public int? LauncherPort { get => WithDefault(_launcherPort, 0); set => _launcherPort = value; }
private string? _launcherServiceUri;
[JsonPropertyName("launcherServiceUri")]
public string? LauncherServiceUri
{
get => WithDefault(
_launcherServiceUri,
OperatingSystem.IsWindows()
? $"fiskaltrust-{_cashboxId}"
: $"/tmp/fiskaltrust-{_cashboxId}.sock"
);
set => _launcherServiceUri = value;
}

private string? _serviceFolder;
[JsonPropertyName("serviceFolder")]
Expand Down
4 changes: 2 additions & 2 deletions src/fiskaltrust.Launcher/Commands/DoctorCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public static async Task<int> HandleAsync(CommonOptions commonOptions, CommonPro

checkUp.Check("Setup monarch ProcessHostService", () =>
{
monarchBuilder.WebHost.ConfigureBinding(new Uri($"http://[::1]:{launcherConfiguration.LauncherPort}"), protocols: HttpProtocols.Http2);
monarchBuilder.WebHost.ConfigureBinding(new Uri($"http://[::1]:{launcherConfiguration.LauncherServiceUri}"), protocols: HttpProtocols.Http2);

monarchBuilder.Services.AddCodeFirstGrpc();
}, throws: true);
Expand Down Expand Up @@ -185,7 +185,7 @@ public static async Task<int> HandleAsync(CommonOptions commonOptions, CommonPro
Version = "1.0.0"
};

IProcessHostService? processHostService = checkUp.Check("Start plebeian processhostservice client", () => GrpcChannel.ForAddress($"http://localhost:{launcherConfiguration.LauncherPort}").CreateGrpcService<IProcessHostService>());
IProcessHostService? processHostService = checkUp.Check("Start plebeian processhostservice client", () => GrpcChannel.ForAddress($"http://localhost:{launcherConfiguration.LauncherServiceUri}").CreateGrpcService<IProcessHostService>());

var plebeianBuilder = Host.CreateDefaultBuilder()
.UseSerilog(new LoggerConfiguration().CreateLogger())
Expand Down
32 changes: 16 additions & 16 deletions src/fiskaltrust.Launcher/Commands/HostCommand.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using fiskaltrust.Launcher.ProcessHost;
using fiskaltrust.Launcher.Services;
using fiskaltrust.storage.serialization.V0;
Expand All @@ -15,12 +14,15 @@
using fiskaltrust.Launcher.Download;
using fiskaltrust.Launcher.Constants;
using System.Diagnostics;
using System.Net.Sockets;
using fiskaltrust.Launcher.Common.Extensions;
using fiskaltrust.Launcher.Common.Configuration;
using fiskaltrust.Launcher.Configuration;
using fiskaltrust.Launcher.Services.Interfaces;
using fiskaltrust.ifPOS.v1.it;
using fiskaltrust.Launcher.Helpers;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using fiskaltrust.Launcher.Factories;

namespace fiskaltrust.Launcher.Commands
{
Expand Down Expand Up @@ -75,28 +77,27 @@ public static async Task<int> HandleAsync(HostOptions hostOptions, HostServices
}
}

var launcherConfiguration = Common.Configuration.LauncherConfiguration.Deserialize(System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(hostOptions.LauncherConfiguration)));

var plebeianConfiguration = Configuration.PlebeianConfiguration.Deserialize(System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(hostOptions.PlebeianConfiguration)));
var launcherConfiguration = LauncherConfiguration.Deserialize(System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(hostOptions.LauncherConfiguration)));
var plebeianConfiguration = PlebeianConfiguration.Deserialize(System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(hostOptions.PlebeianConfiguration)));

var cashboxConfiguration = CashBoxConfigurationExt.Deserialize(await File.ReadAllTextAsync(launcherConfiguration.CashboxConfigurationFile!));

cashboxConfiguration.Decrypt(launcherConfiguration, await CommonHandler.LoadCurve(launcherConfiguration.CashboxId!.Value, launcherConfiguration.AccessToken!, launcherConfiguration.ServiceFolder!, launcherConfiguration.UseLegacyDataProtection!.Value));
cashboxConfiguration.Decrypt(launcherConfiguration, await CommonHandler.LoadCurve(launcherConfiguration.CashboxId!.Value, launcherConfiguration.AccessToken!, launcherConfiguration.ServiceFolder!));

var packageConfiguration = (plebeianConfiguration.PackageType switch
{
PackageType.Queue => cashboxConfiguration.ftQueues,
PackageType.SCU => cashboxConfiguration.ftSignaturCreationDevices,
PackageType.Helper => cashboxConfiguration.helpers,
var unknown => throw new Exception($"Unknown PackageType {unknown}")
_ => throw new Exception($"Unknown PackageType {plebeianConfiguration.PackageType}")
}).First(p => p.Id == plebeianConfiguration.PackageId);

packageConfiguration.Configuration = ProcessPackageConfiguration(packageConfiguration.Configuration, launcherConfiguration, cashboxConfiguration);

IProcessHostService? processHostService = null;
if (!hostOptions.NoProcessHostService)
{
processHostService = GrpcChannel.ForAddress($"http://localhost:{launcherConfiguration.LauncherPort}").CreateGrpcService<IProcessHostService>();
var handler = new SocketsHttpHandler { ConnectCallback = new IpcConnectionFactory(launcherConfiguration).ConnectAsync };
processHostService = GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions { HttpHandler = handler }).CreateGrpcService<IProcessHostService>();
}

Log.Logger = new LoggerConfiguration()
Expand All @@ -110,14 +111,14 @@ public static async Task<int> HandleAsync(HostOptions hostOptions, HostServices
.UseSerilog()
.ConfigureServices(services =>
{
services.AddSingleton(_ => launcherConfiguration);
services.AddSingleton(_ => packageConfiguration);
services.AddSingleton(_ => plebeianConfiguration);

services.Configure<Microsoft.Extensions.Hosting.HostOptions>(opts =>
{
opts.ShutdownTimeout = TimeSpan.FromSeconds(30);
opts.BackgroundServiceExceptionBehavior = BackgroundServiceExceptionBehavior.StopHost;
});
services.AddSingleton(_ => launcherConfiguration);
services.AddSingleton(_ => packageConfiguration);
services.AddSingleton(_ => plebeianConfiguration);

var pluginLoader = new PluginLoader();
services.AddSingleton(_ => pluginLoader);
Expand All @@ -141,7 +142,7 @@ public static async Task<int> HandleAsync(HostOptions hostOptions, HostServices
var bootstrapper = pluginLoader
.LoadComponent<IMiddlewareBootstrapper>(
downloader.GetPackagePath(packageConfiguration),
new[] {
[
typeof(IMiddlewareBootstrapper),
typeof(IPOS),
typeof(IDESSCD),
Expand All @@ -153,11 +154,10 @@ public static async Task<int> HandleAsync(HostOptions hostOptions, HostServices
typeof(JournalResponse),
typeof(IHelper),
typeof(IServiceCollection),
typeof(Microsoft.Extensions.Logging.ILogger),
typeof(ILogger),
typeof(ILoggerFactory),
typeof(ILogger<>)
});

]);
bootstrapper.Id = packageConfiguration.Id;
bootstrapper.Configuration = packageConfiguration.Configuration.ToDictionary(c => c.Key, c => (object?)c.Value.ToString());

Expand Down
22 changes: 21 additions & 1 deletion src/fiskaltrust.Launcher/Commands/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,27 @@ public static async Task<int> HandleAsync(CommonOptions commonOptions, CommonPro
services.AddSingleton(_ => runServices.LauncherExecutablePath);
});

builder.WebHost.ConfigureBinding(new Uri($"http://[::1]:{commonProperties.LauncherConfiguration.LauncherPort}"), protocols: HttpProtocols.Http2);
//Configure Kestrel for ProcessHostService
if (OperatingSystem.IsWindows())
{
builder.WebHost.UseKestrel(serverOptions =>
{
serverOptions.ListenNamedPipe(commonProperties.LauncherConfiguration.LauncherServiceUri!, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
});
});
}
else
{
builder.WebHost.UseKestrel(serverOptions =>
{
serverOptions.ListenUnixSocket(commonProperties.LauncherConfiguration.LauncherServiceUri!, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
});
});
}

builder.Services.AddCodeFirstGrpc();

Expand Down
64 changes: 64 additions & 0 deletions src/fiskaltrust.Launcher/Factories/IpcConnectionFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.IO.Pipes;
using System.Net;
using System.Net.Sockets;
using System.Security.Principal;
using fiskaltrust.Launcher.Common.Configuration;

namespace fiskaltrust.Launcher.Factories
{
public interface IConnectionFactory
{
public ValueTask<Stream> ConnectAsync(SocketsHttpConnectionContext _, CancellationToken cancellationToken);
}

public class IpcConnectionFactory : IConnectionFactory
{
private IConnectionFactory _connectionFactory;
public IpcConnectionFactory(LauncherConfiguration configuration)
{
if (OperatingSystem.IsWindows())
{
_connectionFactory = new NamedPipesConnectionFactory(configuration.LauncherServiceUri!);
}
else
{
_connectionFactory = new UnixDomainSocketsConnectionFactory(configuration.LauncherServiceUri!);
}
}
public ValueTask<Stream> ConnectAsync(SocketsHttpConnectionContext _, CancellationToken cancellationToken) => _connectionFactory.ConnectAsync(_, cancellationToken);
}

public class NamedPipesConnectionFactory : IConnectionFactory
{
private readonly string _uri;

public NamedPipesConnectionFactory(string uri)
{
_uri = uri;
}

public async ValueTask<Stream> ConnectAsync(SocketsHttpConnectionContext _, CancellationToken cancellationToken)
{
var clientStream = new NamedPipeClientStream(".", _uri, PipeDirection.InOut, PipeOptions.WriteThrough | PipeOptions.Asynchronous, TokenImpersonationLevel.Anonymous);
await clientStream.ConnectAsync(cancellationToken).ConfigureAwait(false);
return clientStream;
}
}

public class UnixDomainSocketsConnectionFactory : IConnectionFactory
{
private readonly EndPoint _endPoint;

public UnixDomainSocketsConnectionFactory(string uri)
{
_endPoint = new UnixDomainSocketEndPoint(uri);
}

public async ValueTask<Stream> ConnectAsync(SocketsHttpConnectionContext _, CancellationToken cancellationToken)
{
var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
await socket.ConnectAsync(_endPoint, cancellationToken).ConfigureAwait(false);
return new NetworkStream(socket, ownsSocket: true);
}
}
}
33 changes: 0 additions & 33 deletions src/fiskaltrust.Launcher/ProcessHost/ProcessHostMonarcStartup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using fiskaltrust.Launcher.Helpers;
using fiskaltrust.storage.serialization.V0;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.Hosting.WindowsServices;

namespace fiskaltrust.Launcher.ProcessHost
Expand All @@ -25,7 +24,6 @@ public class AlreadyLoggedException : Exception { }
private readonly ILoggerFactory _loggerFactory;
private readonly ILifetime _lifetime;
private readonly LauncherExecutablePath _launcherExecutablePath;
private readonly TaskCompletionSource<Uri> _kestrelReady;

public ProcessHostMonarcStartup(ILoggerFactory loggerFactory, ILogger<ProcessHostMonarcStartup> logger, Dictionary<Guid, IProcessHostMonarch> hosts, LauncherConfiguration launcherConfiguration, ftCashBoxConfiguration cashBoxConfiguration, PackageDownloader downloader, ILifetime lifetime, LauncherExecutablePath launcherExecutablePath, IHostApplicationLifetime hostApplicationLifetime, IServer server)
{
Expand All @@ -37,45 +35,14 @@ public ProcessHostMonarcStartup(ILoggerFactory loggerFactory, ILogger<ProcessHos
_downloader = downloader;
_lifetime = lifetime;
_launcherExecutablePath = launcherExecutablePath;
_kestrelReady = new TaskCompletionSource<Uri>();

hostApplicationLifetime.ApplicationStarted.Register(() =>
{
try
{
_kestrelReady.TrySetResult(new Uri(server.Features.Get<IServerAddressesFeature>()!.Addresses!.First()));
}
catch (Exception e)
{
_kestrelReady.TrySetException(e);
}
});
}


protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
_lifetime.ApplicationLifetime.ApplicationStopping.Register(() => _logger.LogInformation("Shutting down launcher."));
cancellationToken.Register(() => _kestrelReady.TrySetCanceled());

StartupLogging();

if (_launcherConfiguration.LauncherPort == 0)
{
try
{
var url = await _kestrelReady.Task.ConfigureAwait(false);
_launcherConfiguration.LauncherPort = url.Port;
_logger.LogInformation("ProcessHostService running on {url}", url);
}
catch (Exception e)
{
if (cancellationToken.IsCancellationRequested) { return; }
_logger.LogError(e, "Could not get Kestrel port.");
throw new AlreadyLoggedException();
}
}

_downloader.CopyPackagesToCache();

try
Expand Down
2 changes: 0 additions & 2 deletions src/fiskaltrust.Launcher/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
using fiskaltrust.Launcher.Extensions;
using fiskaltrust.Launcher.Helpers;
using System.CommandLine.NamingConventionBinder;
using fiskaltrust.Launcher.Common.Configuration;
using fiskaltrust.Launcher.Common.Constants;

var runCommand = new RunCommand()
{
Expand Down
14 changes: 12 additions & 2 deletions src/fiskaltrust.Launcher/Services/HostingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@ public async Task<WebApplication> HostService<T>(Uri uri, HostingType hostingTyp
{
var builder = WebApplication.CreateBuilder();

// Configure Serilog for logging
builder.Host.UseSerilog((_, __, loggerConfiguration) =>
loggerConfiguration
.AddLoggingConfiguration(_launcherConfiguration, aspLogging: true)
.WriteTo.GrpcSink(_packageConfiguration, _processHostService));

// Add HTTP logging if the log level is set to Debug or lower
if (_launcherConfiguration.LogLevel <= LogLevel.Debug)
{
builder.Services.AddHttpLogging(options =>
Expand All @@ -71,9 +73,10 @@ public async Task<WebApplication> HostService<T>(Uri uri, HostingType hostingTyp
HttpLoggingFields.ResponseStatusCode |
HttpLoggingFields.ResponseBody);
}
WebApplication app;

WebApplication app;

// Check if UseHttpSysBinding is enabled and log warnings if necessary
if (_launcherConfiguration.UseHttpSysBinding!.Value)
{
const string message = $"The configuration parameter {{parametername}} will be ignored because {nameof(_launcherConfiguration.UseHttpSysBinding)} is enabled.";
Expand All @@ -98,6 +101,7 @@ public async Task<WebApplication> HostService<T>(Uri uri, HostingType hostingTyp
}
}

// Create the appropriate host based on the hosting type
switch (hostingType)
{
case HostingType.REST:
Expand All @@ -117,6 +121,7 @@ public async Task<WebApplication> HostService<T>(Uri uri, HostingType hostingTyp
throw new NotImplementedException();
}

// Use HTTP logging if the log level is set to Debug or lower
if (_launcherConfiguration.LogLevel <= LogLevel.Debug)
{
app.UseHttpLogging();
Expand All @@ -130,6 +135,7 @@ public async Task<WebApplication> HostService<T>(Uri uri, HostingType hostingTyp

private WebApplication CreateRestHost<T>(WebApplicationBuilder builder, Uri uri, T instance, Action<WebApplication> addEndpoints)
{
// Configure JSON options
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString;
Expand All @@ -140,6 +146,7 @@ private WebApplication CreateRestHost<T>(WebApplicationBuilder builder, Uri uri,
options.SerializerOptions.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull;
});

// Configure Kestrel server binding
builder.WebHost.ConfigureBinding(uri, listenOptions => ConfigureTls(listenOptions), isHttps: !string.IsNullOrEmpty(_launcherConfiguration.TlsCertificatePath) || !string.IsNullOrEmpty(_launcherConfiguration.TlsCertificateBase64), allowSynchronousIO: true, useHttpSys: _launcherConfiguration.UseHttpSysBinding!.Value);

var app = builder.Build();
Expand Down Expand Up @@ -247,7 +254,10 @@ private WebApplication CreateGrpcHost<T>(WebApplicationBuilder builder, Uri uri,
builder.Services.AddSingleton(instance);

var app = builder.Build();
if (!OperatingSystem.IsWindows() || _launcherConfiguration.UseHttpSysBinding!.Value == false) { app.UsePathBase(uri.AbsolutePath); }
if (!OperatingSystem.IsWindows() || _launcherConfiguration.UseHttpSysBinding!.Value == false)
{
app.UsePathBase(uri.AbsolutePath);
}

app.UseRouting();
#pragma warning disable ASP0014
Expand Down
Loading

0 comments on commit 85721d9

Please sign in to comment.