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

improve fault tolerance of dataprotection #158

Merged
merged 32 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5298a3e
Improved resilience in data protection mechanism
pawelvds Jan 11, 2024
9ba1d47
Enhanced the Decrypt method in LauncherConfiguration to handle except…
pawelvds Jan 11, 2024
e79e470
Enhance error handling in decryption of LauncherConfiguration
pawelvds Jan 11, 2024
73a3630
Added log warning
pawelvds Jan 12, 2024
d2951d6
Added try-catch block around ECDH curve loading
pawelvds Jan 12, 2024
da46b84
Updated Common.cs
pawelvds Jan 22, 2024
4d66c72
Enhanced resilience in LoadCurve with error handling and automatic re…
pawelvds Jan 23, 2024
8925726
Resolved merge conflict
pawelvds Jan 23, 2024
d867424
push
pawelvds Jan 23, 2024
a58571d
Handle curve loading and decryption errors in LoadCurve
pawelvds Jan 23, 2024
f13409a
Changes related to suggestions from review
pawelvds Jan 23, 2024
325a927
Fixed Common.cs
pawelvds Jan 29, 2024
d176c14
fix
pawelvds Jan 29, 2024
f8992d0
Another try fix
pawelvds Jan 29, 2024
2119715
f
pawelvds Jan 29, 2024
e5d0842
ff
pawelvds Jan 29, 2024
55afd78
fix conflicts
pawelvds Jan 29, 2024
25422da
fff
pawelvds Jan 29, 2024
0bb99c2
Refactor Common.cs for improved readability
pawelvds Jan 29, 2024
69e6e9f
Add conditional return in Common.cs
pawelvds Jan 29, 2024
779982b
Add conditional return in Common.cs
pawelvds Jan 29, 2024
2c3d294
changes
pawelvds Jan 29, 2024
18a51ef
Fix indentation in Common.cs file
pawelvds Jan 29, 2024
fb54d22
Refactor code
pawelvds Jan 29, 2024
4ddffed
Revert "changes"
pawelvds Jan 29, 2024
691283f
Refactor code formatting in fiskaltrust.Launcher
pawelvds Jan 29, 2024
f9fb2c9
Resolve diff
pawelvds Jan 29, 2024
f4f3c17
Refactor codein Common.cs
pawelvds Jan 29, 2024
83d3f80
Merge branch 'main' of github.com:fiskaltrust/middleware-launcher int…
pawelvds Jan 29, 2024
63118e5
Improve code
pawelvds Jan 29, 2024
4609cfb
Refactor curve loading
pawelvds Jan 29, 2024
b1783d5
Improved error handling in ECDH file loading
pawelvds Jan 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/fiskaltrust.Launcher.Common/Configuration/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -312,9 +312,16 @@ public void Decrypt(IDataProtector dataProtector)
{
MapFieldsWithAttribute<EncryptAttribute>((value) =>
{
if (value is null) { return null; }

return dataProtector.Unprotect((string)value);
try
{
if (value is null) return null;
return dataProtector.Unprotect((string)value);
}
catch (Exception e)
{
Log.Warning($"Failed to decrypt field: {e.Message}. Consider using 'config set' to reset.");
pawelvds marked this conversation as resolved.
Show resolved Hide resolved
return null;
}
});
}

Expand Down
104 changes: 80 additions & 24 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 async Task<int> HandleAsync<O, S>(
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 async Task<int> HandleAsync<O, S>(

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 All @@ -153,7 +157,7 @@ public static async Task<int> HandleAsync<O, S>(
ECDiffieHellman? clientEcdh = null;
try
{
clientEcdh = await LoadCurve(launcherConfiguration.CashboxId!.Value, launcherConfiguration.AccessToken!, launcherConfiguration.ServiceFolder!, launcherConfiguration.UseOffline!.Value);
clientEcdh = await LoadCurve(launcherConfiguration.CashboxId!.Value, launcherConfiguration.AccessToken!, launcherConfiguration.ServiceFolder!, launcherConfiguration.UseOffline!.Value, useFallback: launcherConfiguration.UseLegacyDataProtection!.Value);
using var downloader = new ConfigurationDownloader(launcherConfiguration);
var exists = await downloader.DownloadConfigurationAsync(clientEcdh);
if (launcherConfiguration.UseOffline!.Value && !exists)
Expand All @@ -180,6 +184,7 @@ public static async Task<int> HandleAsync<O, S>(
}
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 async Task<int> HandleAsync<O, S>(
}
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 async Task<int> HandleAsync<O, S>(
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,42 +239,88 @@ public static async Task<int> HandleAsync<O, S>(
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.");
var dataProtector = DataProtectionExtensions.Create(accessToken, useFallback: useFallback).CreateProtector(CashBoxConfigurationExt.DATA_PROTECTION_DATA_PURPOSE);
var clientEcdhPath = Path.Combine(serviceFolder, $"client-{cashboxId}.ecdh");

if (File.Exists(clientEcdhPath))
{
return ECDiffieHellmanExt.Deserialize(dataProtector.Unprotect(await File.ReadAllTextAsync(clientEcdhPath)));
}
else
try
pawelvds marked this conversation as resolved.
Show resolved Hide resolved
{
const string offlineClientEcdhPath = "/client.ecdh";
ECDiffieHellman clientEcdh;

if (!dryRun && useOffline && File.Exists(offlineClientEcdhPath))
if (File.Exists(clientEcdhPath))
{
clientEcdh = ECDiffieHellmanExt.Deserialize(await File.ReadAllTextAsync(offlineClientEcdhPath));
var protectedData = await File.ReadAllTextAsync(clientEcdhPath);
try
{
File.Delete(offlineClientEcdhPath);
return ECDiffieHellmanExt.Deserialize(dataProtector.Unprotect(protectedData));
}
catch (Exception ex)
{
Log.Warning($"Could not decrypt ECDH curve, regenerating a new one. Error: {ex.Message}");
pawelvds marked this conversation as resolved.
Show resolved Hide resolved
// Handle failed decryption here if necessary, e.g., by deleting the existing file
// File.Delete(clientEcdhPath);
}
catch { }
}
else
{
clientEcdh = CashboxConfigEncryption.CreateCurve();
}

if (!dryRun)
{
await File.WriteAllTextAsync(clientEcdhPath, dataProtector.Protect(clientEcdh.Serialize()));
}
}
catch (Exception ex)
{
Log.Warning($"Error reading ECDH curve from file: {ex.Message}. Regenerating new curve.");
}

return clientEcdh;
// Rest of the method for regenerating curve if not loaded or in case of error
ECDiffieHellman clientEcdh = CashboxConfigEncryption.CreateCurve();
if (!dryRun)
{
var serializedCurve = clientEcdh.Serialize();
var protectedCurve = dataProtector.Protect(serializedCurve);
await File.WriteAllTextAsync(clientEcdhPath, protectedCurve);
}

return clientEcdh;
}
}
}
}
4 changes: 3 additions & 1 deletion src/fiskaltrust.Launcher/Commands/DoctorCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ public static async Task<int> HandleAsync(CommonOptions commonOptions, CommonPro
ftCashBoxConfiguration cashboxConfiguration = new();

if (clientEcdh is null)
{ }
{
Log.Warning("Failed to load ECDH curve. Unable to proceed with Doctor checks.");
pawelvds marked this conversation as resolved.
Show resolved Hide resolved
}
else
{
using var downloader = new ConfigurationDownloader(launcherConfiguration);
Expand Down
Loading