diff --git a/src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs index a598b87f..7bd11324 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs @@ -22,13 +22,13 @@ using System.ServiceProcess; using System.Threading.Tasks; using Seq.Forwarder.Cli.Features; -using Seq.Forwarder.ServiceProcess; using Seq.Forwarder.Util; using SeqCli; using SeqCli.Cli; using SeqCli.Cli.Features; using SeqCli.Config; using SeqCli.Config.Forwarder; +using SeqCli.Forwarder.ServiceProcess; using SeqCli.Forwarder.Util; // ReSharper disable once ClassNeverInstantiated.Global @@ -97,13 +97,13 @@ int Setup() { Console.WriteLine("Checking the status of the Seq Forwarder service..."); - controller = new ServiceController(SeqForwarderWindowsService.WindowsServiceName); + controller = new ServiceController(SeqCliForwarderWindowsService.WindowsServiceName); Console.WriteLine("Status is {0}", controller.Status); } catch (InvalidOperationException) { Install(); - var controller2 = new ServiceController(SeqForwarderWindowsService.WindowsServiceName); + var controller2 = new ServiceController(SeqCliForwarderWindowsService.WindowsServiceName); return Start(controller2); } @@ -219,7 +219,7 @@ void Install() var binPath = forwarderRunCmdline.Replace("\"", "\\\""); - var scCmdline = "create \"" + SeqForwarderWindowsService.WindowsServiceName + "\"" + + var scCmdline = "create \"" + SeqCliForwarderWindowsService.WindowsServiceName + "\"" + " binPath= \"" + binPath + "\"" + " start= auto" + " depend= Winmgmt/Tcpip/CryptSvc"; @@ -234,10 +234,10 @@ void Install() } Console.WriteLine("Setting service restart policy..."); - if (0 != CaptiveProcess.Run(sc, $"failure \"{SeqForwarderWindowsService.WindowsServiceName}\" actions= restart/60000/restart/60000/restart/60000// reset= 600000", Console.WriteLine, Console.WriteLine)) + if (0 != CaptiveProcess.Run(sc, $"failure \"{SeqCliForwarderWindowsService.WindowsServiceName}\" actions= restart/60000/restart/60000/restart/60000// reset= 600000", Console.WriteLine, Console.WriteLine)) Console.WriteLine("Could not set service restart policy; ignoring"); Console.WriteLine("Setting service description..."); - if (0 != CaptiveProcess.Run(sc, $"description \"{SeqForwarderWindowsService.WindowsServiceName}\" \"Durable storage and forwarding of application log events\"", Console.WriteLine, Console.WriteLine)) + if (0 != CaptiveProcess.Run(sc, $"description \"{SeqCliForwarderWindowsService.WindowsServiceName}\" \"Durable storage and forwarding of application log events\"", Console.WriteLine, Console.WriteLine)) Console.WriteLine("Could not set service description; ignoring"); Console.WriteLine("Service installed successfully."); diff --git a/src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs index c53f9b70..7d26d097 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs @@ -19,8 +19,8 @@ using System.IO; using System.ServiceProcess; using System.Threading.Tasks; -using Seq.Forwarder.ServiceProcess; using SeqCli.Cli; +using SeqCli.Forwarder.ServiceProcess; // ReSharper disable UnusedType.Global @@ -34,7 +34,7 @@ protected override Task Run() { try { - var controller = new ServiceController(SeqForwarderWindowsService.WindowsServiceName); + var controller = new ServiceController(SeqCliForwarderWindowsService.WindowsServiceName); if (controller.Status != ServiceControllerStatus.Stopped) { diff --git a/src/SeqCli/Cli/Commands/Forwarder/RunCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/RunCommand.cs index 62b79989..80b59a9d 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/RunCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/RunCommand.cs @@ -33,6 +33,10 @@ using Serilog.Events; using Serilog.Formatting.Compact; +#if WINDOWS +using SeqCli.Forwarder.ServiceProcess; +#endif + // ReSharper disable UnusedType.Global namespace SeqCli.Cli.Commands.Forwarder; @@ -170,7 +174,7 @@ static int RunService(ServerService service) { #if WINDOWS System.ServiceProcess.ServiceBase.Run([ - new SeqForwarderWindowsService(service) + new SeqCliForwarderWindowsService(service) ]); return 0; #else diff --git a/src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs index 5353e142..dcbefb38 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs @@ -19,8 +19,8 @@ using System.IO; using System.ServiceProcess; using System.Threading.Tasks; -using Seq.Forwarder.ServiceProcess; using SeqCli.Cli; +using SeqCli.Forwarder.ServiceProcess; namespace Seq.Forwarder.Cli.Commands { @@ -32,7 +32,7 @@ protected override Task Run() { try { - var controller = new ServiceController(SeqForwarderWindowsService.WindowsServiceName); + var controller = new ServiceController(SeqCliForwarderWindowsService.WindowsServiceName); if (controller.Status != ServiceControllerStatus.Stopped) { Console.WriteLine("Cannot start {0}, current status is: {1}", controller.ServiceName, controller.Status); diff --git a/src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs index 1b0aa06d..3eb6b0d3 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs @@ -19,8 +19,8 @@ using System.IO; using System.ServiceProcess; using System.Threading.Tasks; -using Seq.Forwarder.ServiceProcess; using SeqCli.Cli; +using SeqCli.Forwarder.ServiceProcess; namespace Seq.Forwarder.Cli.Commands { @@ -32,7 +32,7 @@ protected override Task Run() { try { - var controller = new ServiceController(SeqForwarderWindowsService.WindowsServiceName); + var controller = new ServiceController(SeqCliForwarderWindowsService.WindowsServiceName); Console.WriteLine("The Seq Forwarder service is installed and {0}.", controller.Status.ToString().ToLowerInvariant()); } catch (InvalidOperationException) diff --git a/src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs index a003bb22..59761a19 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs @@ -19,8 +19,8 @@ using System.IO; using System.ServiceProcess; using System.Threading.Tasks; -using Seq.Forwarder.ServiceProcess; using SeqCli.Cli; +using SeqCli.Forwarder.ServiceProcess; namespace Seq.Forwarder.Cli.Commands { @@ -32,7 +32,7 @@ protected override Task Run() { try { - var controller = new ServiceController(SeqForwarderWindowsService.WindowsServiceName); + var controller = new ServiceController(SeqCliForwarderWindowsService.WindowsServiceName); if (controller.Status != ServiceControllerStatus.Running) { diff --git a/src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs index c823a04c..7ca95726 100644 --- a/src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs +++ b/src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs @@ -17,9 +17,9 @@ using System; using System.IO; using System.Threading.Tasks; -using Seq.Forwarder.ServiceProcess; using Seq.Forwarder.Util; using SeqCli.Cli; +using SeqCli.Forwarder.ServiceProcess; using SeqCli.Forwarder.Util; namespace Seq.Forwarder.Cli.Commands @@ -34,7 +34,7 @@ protected override Task Run() Console.WriteLine("Uninstalling service..."); var sc = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "sc.exe"); - var exitCode = CaptiveProcess.Run(sc, $"delete \"{SeqForwarderWindowsService.WindowsServiceName}\"", Console.WriteLine, Console.WriteLine); + var exitCode = CaptiveProcess.Run(sc, $"delete \"{SeqCliForwarderWindowsService.WindowsServiceName}\"", Console.WriteLine, Console.WriteLine); if (exitCode != 0) throw new InvalidOperationException($"The `sc.exe delete` call failed with exit code {exitCode}."); diff --git a/src/SeqCli/Cli/Commands/Profile/CreateCommand.cs b/src/SeqCli/Cli/Commands/Profile/CreateCommand.cs index d6769739..866ee9f7 100644 --- a/src/SeqCli/Cli/Commands/Profile/CreateCommand.cs +++ b/src/SeqCli/Cli/Commands/Profile/CreateCommand.cs @@ -49,7 +49,9 @@ int RunSync() try { var config = SeqCliConfig.Read(); - config.Profiles[_name] = new ConnectionConfig { ServerUrl = _url, ApiKey = _apiKey }; + var connectionConfig = new ConnectionConfig { ServerUrl = _url }; + connectionConfig.EncodeApiKey(_apiKey, config.Encryption.DataProtector()); + config.Profiles[_name] = connectionConfig; SeqCliConfig.Write(config); return 0; } diff --git a/src/SeqCli/Cli/Features/StoragePathFeature.cs b/src/SeqCli/Cli/Features/StoragePathFeature.cs index f706eb38..50523ca8 100644 --- a/src/SeqCli/Cli/Features/StoragePathFeature.cs +++ b/src/SeqCli/Cli/Features/StoragePathFeature.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using SeqCli.Forwarder.ServiceProcess; namespace SeqCli.Cli.Features; @@ -49,7 +50,7 @@ static string GetDefaultStorageRoot() { #if WINDOWS if (Seq.Forwarder.Util.ServiceConfiguration.GetServiceStoragePath( - Seq.Forwarder.ServiceProcess.SeqForwarderWindowsService.WindowsServiceName, out var storage)) + SeqCliForwarderWindowsService.WindowsServiceName, out var storage)) return storage; #endif diff --git a/src/SeqCli/Config/ConnectionConfig.cs b/src/SeqCli/Config/ConnectionConfig.cs index 821b6ad6..d2f31289 100644 --- a/src/SeqCli/Config/ConnectionConfig.cs +++ b/src/SeqCli/Config/ConnectionConfig.cs @@ -13,8 +13,9 @@ // limitations under the License. using System; +using System.Text; using Newtonsoft.Json; -using SeqCli.Forwarder.Cryptography; +using SeqCli.Encryptor; using SeqCli.Util; namespace SeqCli.Config; @@ -23,47 +24,37 @@ public class ConnectionConfig { const string ProtectedDataPrefix = "pd."; + static readonly Encoding ProtectedDataEncoding = new UTF8Encoding(false); + public string ServerUrl { get; set; } = "http://localhost:5341"; [JsonProperty("apiKey")] public string? EncodedApiKey { get; set; } - [JsonIgnore] - public string? ApiKey + public string? DecodeApiKey(IDataProtector dataProtector) { - get - { - if (string.IsNullOrWhiteSpace(EncodedApiKey)) - return null; - - if (!OperatingSystem.IsWindows()) - return EncodedApiKey; + if (string.IsNullOrWhiteSpace(EncodedApiKey)) + return null; + + if (!EncodedApiKey.StartsWith(ProtectedDataPrefix)) + return EncodedApiKey; - if (!EncodedApiKey.StartsWith(ProtectedDataPrefix)) - return EncodedApiKey; + return ProtectedDataEncoding.GetString(dataProtector.Decrypt(Convert.FromBase64String(EncodedApiKey[ProtectedDataPrefix.Length..]))); + } - return UserScopeDataProtection.Unprotect(EncodedApiKey.Substring(ProtectedDataPrefix.Length)); - } - set + public void EncodeApiKey(string? apiKey, IDataProtector dataProtector) + { + if (apiKey == null) { - if (string.IsNullOrWhiteSpace(value)) - { - EncodedApiKey = null; - return; - } - - if (OperatingSystem.IsWindows()) - EncodedApiKey = $"{ProtectedDataPrefix}{UserScopeDataProtection.Protect(value)}"; - else - EncodedApiKey = value; + EncodedApiKey = null; + return; } - } - public string? GetApiKey(IStringDataProtector dataProtector) - { - throw new NotImplementedException(); + var encoded = dataProtector.Encrypt(ProtectedDataEncoding.GetBytes(apiKey)); + + EncodedApiKey = $"{ProtectedDataPrefix}{Convert.ToBase64String(encoded)}"; } - + public uint? PooledConnectionLifetimeMilliseconds { get; set; } = null; public ulong EventBodyLimitBytes { get; set; } = 256 * 1024; public ulong PayloadLimitBytes { get; set; } = 10 * 1024 * 1024; diff --git a/src/SeqCli/Config/SeqCliConfig.cs b/src/SeqCli/Config/SeqCliConfig.cs index 88ac1b08..752476b4 100644 --- a/src/SeqCli/Config/SeqCliConfig.cs +++ b/src/SeqCli/Config/SeqCliConfig.cs @@ -57,7 +57,7 @@ public static void Write(SeqCliConfig data) public ConnectionConfig Connection { get; set; } = new(); public OutputConfig Output { get; set; } = new(); public ForwarderConfig Forwarder { get; set; } = new(); - public SeqCliEncryptionProviderConfig EncryptionProviderProvider { get; set; } = new SeqCliEncryptionProviderConfig(); + public SeqCliEncryptionProviderConfig Encryption { get; set; } = new SeqCliEncryptionProviderConfig(); public Dictionary Profiles { get; } = new(StringComparer.OrdinalIgnoreCase); } \ No newline at end of file diff --git a/src/SeqCli/Config/SeqCliEncryptionProviderConfig.cs b/src/SeqCli/Config/SeqCliEncryptionProviderConfig.cs index c7818e18..d94750aa 100644 --- a/src/SeqCli/Config/SeqCliEncryptionProviderConfig.cs +++ b/src/SeqCli/Config/SeqCliEncryptionProviderConfig.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +using SeqCli.Encryptor; + namespace SeqCli.Config; public class SeqCliEncryptionProviderConfig @@ -21,4 +23,18 @@ public class SeqCliEncryptionProviderConfig public string? Decryptor { get; set; } public string? DecryptorArgs { get; set; } + + public IDataProtector DataProtector() + { +#if WINDOWS + return new WindowsNativeDataProtector(); +#else + if (!string.IsNullOrWhiteSpace(Encryptor) && !string.IsNullOrWhiteSpace(Decryptor)) + { + return new ExternalDataProtector(this); + } + + return new PlaintextDataProtector(); +#endif + } } \ No newline at end of file diff --git a/src/SeqCli/Connection/SeqConnectionFactory.cs b/src/SeqCli/Connection/SeqConnectionFactory.cs index 08ef3cfb..05a82d73 100644 --- a/src/SeqCli/Connection/SeqConnectionFactory.cs +++ b/src/SeqCli/Connection/SeqConnectionFactory.cs @@ -16,6 +16,7 @@ using Seq.Api; using SeqCli.Cli.Features; using SeqCli.Config; +using SeqCli.Encryptor; namespace SeqCli.Connection; @@ -50,12 +51,12 @@ public SeqConnection Connect(ConnectionFeature connection) throw new ArgumentException($"A profile named `{connection.ProfileName}` was not found; see `seqcli profile list` for available profiles."); url = profile.ServerUrl; - apiKey = profile.ApiKey; + apiKey = profile.DecodeApiKey(_config.Encryption.DataProtector()); } else { url = _config.Connection.ServerUrl; - apiKey = connection.IsApiKeySpecified ? connection.ApiKey : _config.Connection.ApiKey; + apiKey = connection.IsApiKeySpecified ? connection.ApiKey : _config.Connection.DecodeApiKey(_config.Encryption.DataProtector()); } return (url, apiKey); diff --git a/src/SeqCli/Encryptor/ExternalEncryption.cs b/src/SeqCli/Encryptor/ExternalDataProtector.cs similarity index 96% rename from src/SeqCli/Encryptor/ExternalEncryption.cs rename to src/SeqCli/Encryptor/ExternalDataProtector.cs index b9db753f..3444b37b 100644 --- a/src/SeqCli/Encryptor/ExternalEncryption.cs +++ b/src/SeqCli/Encryptor/ExternalDataProtector.cs @@ -7,9 +7,9 @@ namespace SeqCli.Encryptor; -public class ExternalEncryption : IEncryption +public class ExternalDataProtector : IDataProtector { - public ExternalEncryption(SeqCliEncryptionProviderConfig providerConfig) + public ExternalDataProtector(SeqCliEncryptionProviderConfig providerConfig) { _encryptor = providerConfig.Encryptor!; _encryptorArgs = providerConfig.EncryptorArgs; diff --git a/src/SeqCli/Encryptor/IEncryption.cs b/src/SeqCli/Encryptor/IDataProtector.cs similarity index 79% rename from src/SeqCli/Encryptor/IEncryption.cs rename to src/SeqCli/Encryptor/IDataProtector.cs index 0294fa82..06db6d34 100644 --- a/src/SeqCli/Encryptor/IEncryption.cs +++ b/src/SeqCli/Encryptor/IDataProtector.cs @@ -1,6 +1,6 @@ namespace SeqCli.Encryptor; -public interface IEncryption +public interface IDataProtector { public byte[] Encrypt(byte[] unencrypted); public byte[] Decrypt(byte[] encrypted); diff --git a/src/SeqCli/Encryptor/PlaintextEncryption.cs b/src/SeqCli/Encryptor/PlaintextDataProtector.cs similarity index 81% rename from src/SeqCli/Encryptor/PlaintextEncryption.cs rename to src/SeqCli/Encryptor/PlaintextDataProtector.cs index 53a8df3e..e464002d 100644 --- a/src/SeqCli/Encryptor/PlaintextEncryption.cs +++ b/src/SeqCli/Encryptor/PlaintextDataProtector.cs @@ -1,6 +1,6 @@ namespace SeqCli.Encryptor; -class PlaintextEncryption : IEncryption +class PlaintextDataProtector : IDataProtector { public byte[] Encrypt(byte[] unencrypted) { diff --git a/src/SeqCli/Encryptor/WindowsNativeEncryption.cs b/src/SeqCli/Encryptor/WindowsNativeDataProtector.cs similarity index 94% rename from src/SeqCli/Encryptor/WindowsNativeEncryption.cs rename to src/SeqCli/Encryptor/WindowsNativeDataProtector.cs index 323a82df..203d0f20 100644 --- a/src/SeqCli/Encryptor/WindowsNativeEncryption.cs +++ b/src/SeqCli/Encryptor/WindowsNativeDataProtector.cs @@ -5,7 +5,7 @@ namespace SeqCli.Encryptor; -public class WindowsNativeEncryption : IEncryption +public class WindowsNativeDataProtector : IDataProtector { public byte[] Encrypt(byte[] unencrypted) { diff --git a/src/SeqCli/Forwarder/Cryptography/DpapiMachineScopeDataProtection.cs b/src/SeqCli/Forwarder/Cryptography/DpapiMachineScopeDataProtection.cs deleted file mode 100644 index 2eb74421..00000000 --- a/src/SeqCli/Forwarder/Cryptography/DpapiMachineScopeDataProtection.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright © Datalust Pty Ltd and Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#if WINDOWS - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Security.Cryptography; -using System.Text; -using SeqCli.Forwarder.Cryptography; - -namespace Seq.Forwarder.Cryptography -{ - [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] - class DpapiMachineScopeDataProtect : IStringDataProtector - { - public string Unprotect(string @protected) - { - var parts = @protected.Split(new[] { '$' }, StringSplitOptions.RemoveEmptyEntries); - if (parts.Length != 2) - throw new InvalidOperationException("Encoded data format is invalid."); - - var bytes = Convert.FromBase64String(parts[0]); - var salt = Convert.FromBase64String(parts[1]); - var decoded = ProtectedData.Unprotect(bytes, salt, DataProtectionScope.LocalMachine); - return Encoding.UTF8.GetString(decoded); - } - - public string Protect(string value) - { - var salt = RandomNumberGenerator.GetBytes(16); - var bytes = ProtectedData.Protect(Encoding.UTF8.GetBytes(value), salt, DataProtectionScope.LocalMachine); - return $"{Convert.ToBase64String(bytes)}${Convert.ToBase64String(salt)}"; - } - } -} - -#endif diff --git a/src/SeqCli/Forwarder/Cryptography/IStringDataProtector.cs b/src/SeqCli/Forwarder/Cryptography/IStringDataProtector.cs deleted file mode 100644 index cdc930c1..00000000 --- a/src/SeqCli/Forwarder/Cryptography/IStringDataProtector.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeqCli.Forwarder.Cryptography; - -public interface IStringDataProtector -{ - string Protect(string value); - string Unprotect(string @protected); -} \ No newline at end of file diff --git a/src/SeqCli/Forwarder/Cryptography/StringDataProtector.cs b/src/SeqCli/Forwarder/Cryptography/StringDataProtector.cs deleted file mode 100644 index 6b67c890..00000000 --- a/src/SeqCli/Forwarder/Cryptography/StringDataProtector.cs +++ /dev/null @@ -1,17 +0,0 @@ -#if WINDOWS -using Seq.Forwarder.Cryptography; -#endif - -namespace SeqCli.Forwarder.Cryptography; - -static class StringDataProtector -{ - public static IStringDataProtector CreatePlatformDefault() - { -#if WINDOWS - return new DpapiMachineScopeDataProtect(); -#else - return new UnprotectedStringData(); -#endif - } -} \ No newline at end of file diff --git a/src/SeqCli/Forwarder/Cryptography/UnprotectedStringData.cs b/src/SeqCli/Forwarder/Cryptography/UnprotectedStringData.cs deleted file mode 100644 index 6148081e..00000000 --- a/src/SeqCli/Forwarder/Cryptography/UnprotectedStringData.cs +++ /dev/null @@ -1,21 +0,0 @@ -#if !WINDOWS - -using Serilog; - -namespace SeqCli.Forwarder.Cryptography; - -public class UnprotectedStringData : IStringDataProtector -{ - public string Protect(string value) - { - Log.Warning("Data protection is not available on this platform; sensitive values will be stored in plain text"); - return value; - } - - public string Unprotect(string @protected) - { - return @protected; - } -} - -#endif diff --git a/src/SeqCli/Forwarder/ForwarderModule.cs b/src/SeqCli/Forwarder/ForwarderModule.cs index 2a5005ab..019afd4f 100644 --- a/src/SeqCli/Forwarder/ForwarderModule.cs +++ b/src/SeqCli/Forwarder/ForwarderModule.cs @@ -17,7 +17,7 @@ using System.Threading; using Autofac; using SeqCli.Config; -using SeqCli.Forwarder.Cryptography; +using SeqCli.Encryptor; using SeqCli.Forwarder.Multiplexing; using SeqCli.Forwarder.Web.Host; @@ -46,8 +46,8 @@ protected override void Load(ContainerBuilder builder) builder.Register(c => { - var outputConfig = c.Resolve(); - var baseUri = outputConfig.ServerUrl; + var config = c.Resolve(); + var baseUri = config.Connection.ServerUrl; if (string.IsNullOrWhiteSpace(baseUri)) throw new ArgumentException("The destination Seq server URL must be configured in SeqForwarder.json."); @@ -58,13 +58,13 @@ protected override void Load(ContainerBuilder builder) // this expression, using an "or" operator. var hasSocketHandlerOption = - outputConfig.PooledConnectionLifetimeMilliseconds.HasValue; + config.Connection.PooledConnectionLifetimeMilliseconds.HasValue; if (hasSocketHandlerOption) { var httpMessageHandler = new SocketsHttpHandler { - PooledConnectionLifetime = outputConfig.PooledConnectionLifetimeMilliseconds.HasValue ? TimeSpan.FromMilliseconds(outputConfig.PooledConnectionLifetimeMilliseconds.Value) : Timeout.InfiniteTimeSpan, + PooledConnectionLifetime = config.Connection.PooledConnectionLifetimeMilliseconds.HasValue ? TimeSpan.FromMilliseconds(config.Connection.PooledConnectionLifetimeMilliseconds.Value) : Timeout.InfiniteTimeSpan, }; return new HttpClient(httpMessageHandler) { BaseAddress = new Uri(baseUri) }; @@ -74,11 +74,6 @@ protected override void Load(ContainerBuilder builder) }).SingleInstance(); - builder.RegisterInstance(StringDataProtector.CreatePlatformDefault()); - builder.RegisterInstance(_config); - builder.RegisterInstance(_config.Forwarder.Api); - builder.RegisterInstance(_config.Forwarder.Diagnostics); - builder.RegisterInstance(_config.Forwarder.Storage); } } \ No newline at end of file diff --git a/src/SeqCli/Forwarder/Multiplexing/ActiveLogBufferMap.cs b/src/SeqCli/Forwarder/Multiplexing/ActiveLogBufferMap.cs index bcd09c65..92f96f2b 100644 --- a/src/SeqCli/Forwarder/Multiplexing/ActiveLogBufferMap.cs +++ b/src/SeqCli/Forwarder/Multiplexing/ActiveLogBufferMap.cs @@ -16,23 +16,25 @@ using System.Collections.Generic; using System.IO; using System.Net; +using System.Text; using SeqCli.Config; -using SeqCli.Config.Forwarder; -using SeqCli.Forwarder.Cryptography; +using SeqCli.Encryptor; using SeqCli.Forwarder.Storage; using SeqCli.Forwarder.Web; using Serilog; namespace SeqCli.Forwarder.Multiplexing; -public class ActiveLogBufferMap : IDisposable +class ActiveLogBufferMap : IDisposable { const string DataFileName = "data.mdb", LockFileName = "lock.mdb", ApiKeyFileName = ".apikey"; + static readonly Encoding ApiKeyEncoding = new UTF8Encoding(false); + readonly ulong _bufferSizeBytes; readonly ConnectionConfig _connectionConfig; readonly ILogShipperFactory _shipperFactory; - readonly IStringDataProtector _dataProtector; + readonly IDataProtector _dataProtector; readonly string _bufferPath; readonly ILogger _log = Log.ForContext(); @@ -43,15 +45,14 @@ public class ActiveLogBufferMap : IDisposable public ActiveLogBufferMap( string bufferPath, - ForwarderStorageConfig storageConfig, - ConnectionConfig outputConfig, - ILogShipperFactory logShipperFactory, - IStringDataProtector dataProtector) + SeqCliConfig config, + ILogShipperFactory logShipperFactory) { - _bufferSizeBytes = storageConfig.BufferSizeBytes; - _connectionConfig = outputConfig ?? throw new ArgumentNullException(nameof(outputConfig)); + ArgumentNullException.ThrowIfNull(config, nameof(config)); + _bufferSizeBytes = config.Forwarder.Storage.BufferSizeBytes; + _connectionConfig = config.Connection; _shipperFactory = logShipperFactory ?? throw new ArgumentNullException(nameof(logShipperFactory)); - _dataProtector = dataProtector ?? throw new ArgumentNullException(nameof(dataProtector)); + _dataProtector = config.Encryption.DataProtector(); _bufferPath = bufferPath ?? throw new ArgumentNullException(nameof(bufferPath)); } @@ -86,7 +87,7 @@ public void Load() } else { - _noApiKeyLogBuffer = new ActiveLogBuffer(buffer, _shipperFactory.Create(buffer, _connectionConfig.GetApiKey(_dataProtector))); + _noApiKeyLogBuffer = new ActiveLogBuffer(buffer, _shipperFactory.Create(buffer, _connectionConfig.DecodeApiKey(_dataProtector))); } } @@ -100,7 +101,7 @@ public void Load() } _log.Information("Loading an API-key specific buffer in {Path}", subfolder); - var apiKey = _dataProtector.Unprotect(File.ReadAllText(encodedApiKeyFilePath)); + var apiKey = ApiKeyEncoding.GetString(_dataProtector.Decrypt(File.ReadAllBytes(encodedApiKeyFilePath))); var buffer = new LogBuffer(subfolder, _bufferSizeBytes); if (buffer.Peek(0).Length == 0) @@ -159,7 +160,7 @@ public LogBuffer GetLogBuffer(string? apiKey) { _log.Information("Creating a new default log buffer in {Path}", _bufferPath); var buffer = new LogBuffer(_bufferPath, _bufferSizeBytes); - _noApiKeyLogBuffer = new ActiveLogBuffer(buffer, _shipperFactory.Create(buffer, _connectionConfig.GetApiKey(_dataProtector))); + _noApiKeyLogBuffer = new ActiveLogBuffer(buffer, _shipperFactory.Create(buffer, _connectionConfig.DecodeApiKey(_dataProtector))); _noApiKeyLogBuffer.Shipper.Start(); } return _noApiKeyLogBuffer.Buffer; @@ -171,7 +172,7 @@ public LogBuffer GetLogBuffer(string? apiKey) var subfolder = Path.Combine(_bufferPath, Guid.NewGuid().ToString("n")); _log.Information("Creating a new API key-specific log buffer in {Path}", subfolder); Directory.CreateDirectory(subfolder); - File.WriteAllText(Path.Combine(subfolder, ".apikey"), _dataProtector.Protect(apiKey)); + File.WriteAllBytes(Path.Combine(subfolder, ".apikey"), _dataProtector.Encrypt(ApiKeyEncoding.GetBytes(apiKey))); var newBuffer = new LogBuffer(subfolder, _bufferSizeBytes); var newActiveBuffer = new ActiveLogBuffer(newBuffer, _shipperFactory.Create(newBuffer, apiKey)); _buffersByApiKey.Add(apiKey, newActiveBuffer); diff --git a/src/SeqCli/Forwarder/Multiplexing/HttpLogShipperFactory.cs b/src/SeqCli/Forwarder/Multiplexing/HttpLogShipperFactory.cs index 7c95215d..3101421a 100644 --- a/src/SeqCli/Forwarder/Multiplexing/HttpLogShipperFactory.cs +++ b/src/SeqCli/Forwarder/Multiplexing/HttpLogShipperFactory.cs @@ -26,11 +26,13 @@ class HttpLogShipperFactory : ILogShipperFactory readonly ServerResponseProxy _serverResponseProxy; readonly ConnectionConfig _outputConfig; - public HttpLogShipperFactory(ServerResponseProxy serverResponseProxy, ConnectionConfig outputConfig, HttpClient outputHttpClient) + public HttpLogShipperFactory(SeqCliConfig config, ServerResponseProxy serverResponseProxy, HttpClient outputHttpClient) { + ArgumentNullException.ThrowIfNull(config, nameof(config)); + _outputHttpClient = outputHttpClient; _serverResponseProxy = serverResponseProxy ?? throw new ArgumentNullException(nameof(serverResponseProxy)); - _outputConfig = outputConfig ?? throw new ArgumentNullException(nameof(outputConfig)); + _outputConfig = config.Connection; } public LogShipper Create(LogBuffer logBuffer, string? apiKey) diff --git a/src/SeqCli/Forwarder/ServiceProcess/SeqForwarderWindowsService.cs b/src/SeqCli/Forwarder/ServiceProcess/SeqCliForwarderWindowsService.cs similarity index 89% rename from src/SeqCli/Forwarder/ServiceProcess/SeqForwarderWindowsService.cs rename to src/SeqCli/Forwarder/ServiceProcess/SeqCliForwarderWindowsService.cs index ec944c8b..b0885128 100644 --- a/src/SeqCli/Forwarder/ServiceProcess/SeqForwarderWindowsService.cs +++ b/src/SeqCli/Forwarder/ServiceProcess/SeqCliForwarderWindowsService.cs @@ -19,16 +19,16 @@ using System.ServiceProcess; using SeqCli.Forwarder.Web.Host; -namespace Seq.Forwarder.ServiceProcess +namespace SeqCli.Forwarder.ServiceProcess { [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] - class SeqForwarderWindowsService : ServiceBase + class SeqCliForwarderWindowsService : ServiceBase { readonly ServerService _serverService; public static string WindowsServiceName { get; } = "Seq Forwarder"; - public SeqForwarderWindowsService(ServerService serverService) + public SeqCliForwarderWindowsService(ServerService serverService) { // Enable TLS 1.2 Support. // .NET Framework 4.5.2 does not have it enabled by default diff --git a/src/SeqCli/Forwarder/Web/Api/IngestionController.cs b/src/SeqCli/Forwarder/Web/Api/IngestionController.cs index 047a8898..6510df77 100644 --- a/src/SeqCli/Forwarder/Web/Api/IngestionController.cs +++ b/src/SeqCli/Forwarder/Web/Api/IngestionController.cs @@ -32,7 +32,7 @@ namespace SeqCli.Forwarder.Web.Api; -public class IngestionController : Controller +class IngestionController : Controller { static readonly Encoding Encoding = new UTF8Encoding(false); const string ClefMediaType = "application/vnd.serilog.clef"; diff --git a/src/SeqCli/SeqCliModule.cs b/src/SeqCli/SeqCliModule.cs index 009d5172..658153bc 100644 --- a/src/SeqCli/SeqCliModule.cs +++ b/src/SeqCli/SeqCliModule.cs @@ -17,6 +17,7 @@ using SeqCli.Cli; using SeqCli.Config; using SeqCli.Connection; +using SeqCli.Encryptor; namespace SeqCli; @@ -32,5 +33,6 @@ protected override void Load(ContainerBuilder builder) builder.Register(c => SeqCliConfig.Read()).SingleInstance(); builder.Register(c => c.Resolve().Connection).SingleInstance(); builder.Register(c => c.Resolve().Output).SingleInstance(); + builder.Register(c => c.Resolve().Encryption.DataProtector()).As(); } } \ No newline at end of file diff --git a/test/SeqCli.Tests/Forwarder/Multiplexing/ActiveLogBufferMapTests.cs b/test/SeqCli.Tests/Forwarder/Multiplexing/ActiveLogBufferMapTests.cs index 11db09d1..a4ca2484 100644 --- a/test/SeqCli.Tests/Forwarder/Multiplexing/ActiveLogBufferMapTests.cs +++ b/test/SeqCli.Tests/Forwarder/Multiplexing/ActiveLogBufferMapTests.cs @@ -1,7 +1,6 @@ using System.IO; using System.Linq; using SeqCli.Config; -using SeqCli.Forwarder.Cryptography; using SeqCli.Forwarder.Multiplexing; using SeqCli.Tests.Support; using Xunit; @@ -74,7 +73,7 @@ public void EntriesSurviveReloads() static ActiveLogBufferMap CreateActiveLogBufferMap(TempFolder tmp) { var config = new SeqCliConfig(); - var map = new ActiveLogBufferMap(tmp.Path, config.Forwarder.Storage, config.Connection, new InertLogShipperFactory(), StringDataProtector.CreatePlatformDefault()); + var map = new ActiveLogBufferMap(tmp.Path, config, new InertLogShipperFactory()); map.Load(); return map; }