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

Launcher-fails-to-start-on-Linux-without-root-permissions#135 #138

Merged
Merged
54 changes: 54 additions & 0 deletions src/fiskaltrust.Launcher/Commands/Common.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text.Json;
using fiskaltrust.Launcher.Common.Configuration;
Expand All @@ -10,6 +11,7 @@
using fiskaltrust.Launcher.Extensions;
using fiskaltrust.Launcher.Helpers;
using fiskaltrust.Launcher.Logging;
using fiskaltrust.Launcher.ServiceInstallation;
using fiskaltrust.storage.serialization.V0;
using Microsoft.AspNetCore.DataProtection;
using Serilog;
Expand Down Expand Up @@ -82,6 +84,7 @@ public static class CommonHandler
IHost host,
Func<CommonOptions, CommonProperties, O, S, Task<int>> handler) where S : notnull
{
// Log messages will be save here and logged later when we have the configuration options to create the logger.
var collectionSink = new CollectionSink();
Log.Logger = new LoggerConfiguration()
.WriteTo.Sink(collectionSink)
Expand Down Expand Up @@ -131,6 +134,7 @@ public static class CommonHandler

Log.Verbose("Merging launcher cli args.");
launcherConfiguration.OverwriteWith(options.ArgsLauncherConfiguration);
await EnsureServiceDirectoryExists(launcherConfiguration);

if (!launcherConfiguration.UseOffline!.Value && (launcherConfiguration.CashboxId is null || launcherConfiguration.AccessToken is null))
{
Expand Down Expand Up @@ -180,6 +184,7 @@ public static class CommonHandler
}
catch (Exception e)
{
// will exit with non-zero exit code later.
Log.Fatal(e, "Could not read Cashbox configuration file.");
}

Expand All @@ -191,9 +196,11 @@ public static class CommonHandler
}
catch (Exception e)
{
// will exit with non-zero exit code later.
Log.Fatal(e, "Could not parse Cashbox configuration.");
}

// Previous log messages will be logged here using this logger.
Log.Logger = new LoggerConfiguration()
.AddLoggingConfiguration(launcherConfiguration)
.AddFileLoggingConfiguration(launcherConfiguration, new[] { "fiskaltrust.Launcher", launcherConfiguration.CashboxId?.ToString() })
Expand All @@ -205,6 +212,9 @@ public static class CommonHandler
Log.Write(logEvent);
}

// If any critical errors occured, we exit with a non-zero exit code.
// In many cases we don't want to immediately exit the application,
// but we want to log the error and continue and see what else is going on before we exit.
if (collectionSink.Events.Where(e => e.Level == LogEventLevel.Fatal).Any())
{
return 1;
Expand All @@ -229,6 +239,50 @@ public static class CommonHandler
return await handler(options, new CommonProperties(launcherConfiguration, cashboxConfiguration, clientEcdh, dataProtectionProvider), specificOptions, host.Services.GetRequiredService<S>());
}

private static async Task EnsureServiceDirectoryExists(LauncherConfiguration config)
{
var serviceDirectory = config.ServiceFolder;
try
{
if (!Directory.Exists(serviceDirectory))
{
Directory.CreateDirectory(serviceDirectory);

if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
var user = Environment.GetEnvironmentVariable("USER");
if (!string.IsNullOrEmpty(user))
{
var chownResult = await ProcessHelper.RunProcess("chown", new[] { user, serviceDirectory }, LogEventLevel.Debug);
if (chownResult.exitCode != 0)
{
Log.Warning("Failed to change owner of the service directory.");
}

var chmodResult = await ProcessHelper.RunProcess("chmod", new[] { "774", serviceDirectory }, LogEventLevel.Debug);
if (chmodResult.exitCode != 0)
{
Log.Warning("Failed to change permissions of the service directory.");
}
}
else
{
Log.Warning("Service user name is not set. Owner of the service directory will not be changed.");
}
}
else
{
Log.Debug("Changing owner and permissions is skipped on non-Unix operating systems.");
}
}
}
catch (UnauthorizedAccessException e)
{
// will exit with non-zero exit code later.
Log.Fatal(e, "Access to the path '{ServiceDirectory}' is denied. Please run the application with sufficient permissions.", serviceDirectory);
}
}

public static async Task<ECDiffieHellman> LoadCurve(Guid cashboxId, string accessToken, string serviceFolder, bool useOffline = false, bool dryRun = false, bool useFallback = false)
{
Log.Verbose("Loading Curve.");
Expand Down
49 changes: 49 additions & 0 deletions src/fiskaltrust.Launcher/Helpers/ProcessHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Diagnostics;
using Serilog;
using Serilog.Events;

namespace fiskaltrust.Launcher.Helpers;

public static class ProcessHelper
{
public static async Task<(int exitCode, string output)> RunProcess(
string fileName,
IEnumerable<string> arguments,
LogEventLevel logLevel = LogEventLevel.Information)
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = string.Join(" ", arguments),
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};

process.Start();
await process.WaitForExitAsync();

var stdOut = await process.StandardOutput.ReadToEndAsync();
if (!string.IsNullOrEmpty(stdOut))
{
Log.Write(logLevel, stdOut);
}

var stdErr = await process.StandardError.ReadToEndAsync();
if (!string.IsNullOrEmpty(stdErr))
{
Log.Write(LogEventLevel.Warning, stdErr);
}

if (process.ExitCode != 0)
{
Log.Warning($"Process {fileName} exited with code {process.ExitCode}");
}

return (process.ExitCode, stdOut);
}
}
18 changes: 9 additions & 9 deletions src/fiskaltrust.Launcher/ServiceInstallation/LinuxSystemD.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ public override async Task<int> InstallService(string commandArgs, string? displ
var serviceFileContent = GetServiceFileContent(displayName ?? "Service installation of fiskaltrust launcher.", commandArgs);
var serviceFilePath = Path.Combine(_servicePath, $"{_serviceName}.service");
await File.AppendAllLinesAsync(serviceFilePath, serviceFileContent).ConfigureAwait(false);
await RunProcess("systemctl", new[] { "daemon-reload" });
await ProcessHelper.RunProcess("systemctl", new[] { "daemon-reload" });
Log.Information("Starting service.");
await RunProcess("systemctl", new[] { "start", _serviceName });
await ProcessHelper.RunProcess("systemctl", new[] { "start", _serviceName });
Log.Information("Enable service.");
return (await RunProcess("systemctl", new[] { "enable", _serviceName, "-q" })).exitCode;
return (await ProcessHelper.RunProcess("systemctl", new[] { "enable", _serviceName, "-q" })).exitCode;
}

public override async Task<int> UninstallService()
Expand All @@ -37,21 +37,21 @@ public override async Task<int> UninstallService()
return -1;
}
Log.Information("Stop service on systemd.");
await RunProcess("systemctl", new[] { "stop ", _serviceName });
await ProcessHelper.RunProcess("systemctl", new[] { "stop ", _serviceName });
Log.Information("Disable service.");
await RunProcess("systemctl", new[] { "disable ", _serviceName, "-q" });
await ProcessHelper.RunProcess("systemctl", new[] { "disable ", _serviceName, "-q" });
Log.Information("Remove service.");
var serviceFilePath = Path.Combine(_servicePath, $"{_serviceName}.service");
await RunProcess("rm", new[] { serviceFilePath });
await ProcessHelper.RunProcess("rm", new[] { serviceFilePath });
Log.Information("Reload daemon.");
await RunProcess("systemctl", new[] { "daemon-reload" });
await ProcessHelper.RunProcess("systemctl", new[] { "daemon-reload" });
Log.Information("Reset failed.");
return (await RunProcess("systemctl", new[] { "reset-failed" })).exitCode;
return (await ProcessHelper.RunProcess("systemctl", new[] { "reset-failed" })).exitCode;
}

private static async Task<bool> IsSystemd()
{
var (exitCode, output) = await RunProcess("ps", new[] { "--no-headers", "-o", "comm", "1" });
var (exitCode, output) = await ProcessHelper.RunProcess("ps", new[] { "--no-headers", "-o", "comm", "1" });
if (exitCode != 0 && output.Contains("systemd"))
{
Log.Error("Service installation works only for systemd setup.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,42 +17,7 @@ protected ServiceInstaller(LauncherExecutablePath launcherExecutablePath)
public abstract Task<int> InstallService(string commandArgs, string? displayName, bool delayedStart = false);

public abstract Task<int> UninstallService();

protected static async Task<(int exitCode, string output)> RunProcess(string fileName, IEnumerable<string> arguments)
{
var process = new Process();
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = fileName;
process.StartInfo.CreateNoWindow = false;

process.StartInfo.Arguments = string.Join(" ", arguments);
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;

process.Start();

await process.WaitForExitAsync();

var withEnrichedContext = (Action log) =>
{
var enrichedContext = LogContext.PushProperty("EnrichedContext", $" {Path.GetFileName(fileName)}");
log();
enrichedContext.Dispose();
};

var stdOut = await process.StandardOutput.ReadToEndAsync();
if (!string.IsNullOrEmpty(stdOut))
{
withEnrichedContext(() => Log.Information(stdOut));
}

var stdErr = await process.StandardError.ReadToEndAsync();
if (!string.IsNullOrEmpty(stdErr))
{
withEnrichedContext(() => Log.Error(stdErr));
}

return (process.ExitCode, stdOut);
}

// The RunProcess method has been removed, as we now use ProcessHelper.RunProcess instead.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ public override async Task<int> InstallService(string commandArgs, string? displ
}

Log.Information("Installing service.");
if ((await RunProcess(@"C:\WINDOWS\system32\sc.exe", arguments)).exitCode != 0)
if ((await ProcessHelper.RunProcess(@"C:\WINDOWS\system32\sc.exe", arguments)).exitCode != 0)
{
Log.Information($"Could not install service \"{_serviceName}\".");
return 1;
}

Log.Information("Starting service.");
if ((await RunProcess(@"C:\WINDOWS\system32\sc.exe", new[] { "start", $"\"{_serviceName}\"" })).exitCode != 0)
if ((await ProcessHelper.RunProcess(@"C:\WINDOWS\system32\sc.exe", new[] { "start", $"\"{_serviceName}\"" })).exitCode != 0)
{
Log.Warning($"Could not start service \"{_serviceName}\".");
}
Expand Down Expand Up @@ -80,13 +80,13 @@ public override async Task<int> UninstallService()
}

Log.Information("Stopping service.");
if ((await RunProcess(@"C:\WINDOWS\system32\sc.exe", new[] { "stop", $"\"{_serviceName}\"" })).exitCode != 0)
if ((await ProcessHelper.RunProcess(@"C:\WINDOWS\system32\sc.exe", new[] { "stop", $"\"{_serviceName}\"" })).exitCode != 0)
{
Log.Warning($"Could not stop service \"{_serviceName}\".");
}

Log.Information("Uninstalling service.");
if ((await RunProcess(@"C:\WINDOWS\system32\sc.exe", new[] { "delete", $"\"{_serviceName}\"" })).exitCode != 0)
if ((await ProcessHelper.RunProcess(@"C:\WINDOWS\system32\sc.exe", new[] { "delete", $"\"{_serviceName}\"" })).exitCode != 0)
{
Log.Warning($"Could not uninstall service \"{_serviceName}\".");
return 1;
Expand Down
Loading