diff --git a/src/SeqCli/Config/SeqCliEncryptionProviderConfig.cs b/src/SeqCli/Config/SeqCliEncryptionProviderConfig.cs
index e7273707..492b4a16 100644
--- a/src/SeqCli/Config/SeqCliEncryptionProviderConfig.cs
+++ b/src/SeqCli/Config/SeqCliEncryptionProviderConfig.cs
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+using System;
using SeqCli.Encryptor;
namespace SeqCli.Config;
@@ -26,14 +27,20 @@ class SeqCliEncryptionProviderConfig
public IDataProtector DataProtector()
{
-#if WINDOWS
- return new WindowsNativeDataProtector();
-#else
- if (!string.IsNullOrWhiteSpace(Encryptor) && !string.IsNullOrWhiteSpace(Decryptor))
+ if (!string.IsNullOrWhiteSpace(Encryptor) || !string.IsNullOrWhiteSpace(Decryptor))
{
- return new ExternalDataProtector(this);
+ if (string.IsNullOrWhiteSpace(Encryptor) || string.IsNullOrWhiteSpace(Decryptor))
+ {
+ throw new ArgumentException(
+ "If either of `encryption.encryptor` or `encryption.decryptor` is specified, both must be specified.");
+ }
+
+ return new ExternalDataProtector(Encryptor, EncryptorArgs, Decryptor, DecryptorArgs);
}
+#if WINDOWS
+ return new WindowsNativeDataProtector();
+#else
return new PlaintextDataProtector();
#endif
}
diff --git a/src/SeqCli/Encryptor/ExternalDataProtector.cs b/src/SeqCli/Encryptor/ExternalDataProtector.cs
index 0c84988b..e5a34905 100644
--- a/src/SeqCli/Encryptor/ExternalDataProtector.cs
+++ b/src/SeqCli/Encryptor/ExternalDataProtector.cs
@@ -9,13 +9,12 @@ namespace SeqCli.Encryptor;
class ExternalDataProtector : IDataProtector
{
- public ExternalDataProtector(SeqCliEncryptionProviderConfig providerConfig)
+ public ExternalDataProtector(string encryptor, string? encryptorArgs, string decryptor, string? decryptorArgs)
{
- _encryptor = providerConfig.Encryptor!;
- _encryptorArgs = providerConfig.EncryptorArgs;
-
- _decryptor = providerConfig.Decryptor!;
- _decryptorArgs = providerConfig.DecryptorArgs;
+ _encryptor = encryptor ?? throw new ArgumentNullException(nameof(encryptor));
+ _encryptorArgs = encryptorArgs;
+ _decryptor = decryptor ?? throw new ArgumentNullException(nameof(decryptor));
+ _decryptorArgs = decryptorArgs;
}
readonly string _encryptor;
@@ -28,7 +27,7 @@ public byte[] Encrypt(byte[] unencrypted)
var exit = Invoke(_encryptor, _encryptorArgs, unencrypted, out var encrypted, out var err);
if (exit != 0)
{
- throw new Exception($"Encryptor failed with exit code {exit} and produced: {err}");
+ throw new Exception($"Encryptor failed with exit code {exit} and produced: {err}.");
}
return encrypted;
@@ -39,7 +38,7 @@ public byte[] Decrypt(byte[] encrypted)
var exit = Invoke(_decryptor, _decryptorArgs, encrypted, out var decrypted, out var err);
if (exit != 0)
{
- throw new Exception($"Decryptor failed with exit code {exit} and produced: {err}");
+ throw new Exception($"Decryptor failed with exit code {exit} and produced: {err}.");
}
return decrypted;
@@ -50,6 +49,7 @@ static int Invoke(string fullExePath, string? args, byte[] stdin, out byte[] std
var startInfo = new ProcessStartInfo
{
UseShellExecute = false,
+ RedirectStandardInput = true,
RedirectStandardError = true,
RedirectStandardOutput = true,
WindowStyle = ProcessWindowStyle.Hidden,
diff --git a/src/SeqCli/SeqCli.csproj b/src/SeqCli/SeqCli.csproj
index e7ef671e..b62c6f98 100644
--- a/src/SeqCli/SeqCli.csproj
+++ b/src/SeqCli/SeqCli.csproj
@@ -22,16 +22,16 @@
true
- WINDOWS
+ $(DefineConstants);WINDOWS
- OSX
+ $(DefineConstants);OSX
- LINUX
+ $(DefineConstants);LINUX
- UNIX
+ $(DefineConstants);UNIX
diff --git a/test/SeqCli.EndToEnd/Support/TestDataFolder.cs b/test/SeqCli.EndToEnd/Support/TestDataFolder.cs
index fd4e106e..8de84187 100644
--- a/test/SeqCli.EndToEnd/Support/TestDataFolder.cs
+++ b/test/SeqCli.EndToEnd/Support/TestDataFolder.cs
@@ -11,7 +11,7 @@ public TestDataFolder()
{
_basePath = System.IO.Path.Combine(
System.IO.Path.GetTempPath(),
- "SeqCli Test",
+ "SeqCli.Tests.EndToEnd",
Guid.NewGuid().ToString("n"));
Directory.CreateDirectory(_basePath);
diff --git a/test/SeqCli.Tests/Config/ExternalDataProtectorTests.cs b/test/SeqCli.Tests/Config/ExternalDataProtectorTests.cs
new file mode 100644
index 00000000..1b14fa4c
--- /dev/null
+++ b/test/SeqCli.Tests/Config/ExternalDataProtectorTests.cs
@@ -0,0 +1,58 @@
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Text;
+using SeqCli.Encryptor;
+using SeqCli.Tests.Support;
+using Xunit;
+
+namespace SeqCli.Tests.Config;
+
+public class ExternalDataProtectorTests
+{
+ [Fact]
+ public void IfEncryptorDoesNotExistEncryptThrows()
+ {
+ var protector = new ExternalDataProtector(Some.String(), null, Some.String(), null);
+ Assert.Throws(() => protector.Encrypt(Some.Bytes(200)));
+ }
+
+#if UNIX
+ [Fact]
+ public void IfEncryptorFailsEncryptThrows()
+ {
+ var protector = new ExternalDataProtector("bash", "-c \"exit 1\"", Some.String(), null);
+ // May be `Exception` or `IOException`.
+ Assert.ThrowsAny(() => protector.Encrypt(Some.Bytes(200)));
+ }
+
+ [Fact]
+ public void EncryptCallsEncryptor()
+ {
+ const string prefix = "123";
+
+ var encoding = new UTF8Encoding(false);
+ using var temp = TempFolder.ForCaller();
+ var filename = temp.AllocateFilename();
+ File.WriteAllBytes(filename, encoding.GetBytes(prefix));
+
+ const string input = "Hello, world!";
+
+ var protector = new ExternalDataProtector("bash", $"-c \"cat '{filename}' -\"", Some.String(), null);
+ var actual = encoding.GetString(protector.Encrypt(encoding.GetBytes(input)));
+
+ Assert.Equal($"{prefix}{input}", actual);
+ }
+
+ [Fact]
+ public void EncryptionRoundTrips()
+ {
+ const string echo = "bash";
+ const string echoArgs = "-c \"cat -\"";
+ var protector = new ExternalDataProtector(echo, echoArgs, echo, echoArgs);
+ var expected = Some.Bytes(200);
+ var actual = protector.Decrypt(protector.Encrypt(expected));
+ Assert.Equal(expected, actual);
+ }
+#endif
+}
diff --git a/test/SeqCli.Tests/Config/SeqCliEncryptionProviderConfigTests.cs b/test/SeqCli.Tests/Config/SeqCliEncryptionProviderConfigTests.cs
new file mode 100644
index 00000000..3f171c97
--- /dev/null
+++ b/test/SeqCli.Tests/Config/SeqCliEncryptionProviderConfigTests.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Runtime.InteropServices;
+using SeqCli.Config;
+using SeqCli.Encryptor;
+using Xunit;
+
+namespace SeqCli.Tests.Config;
+
+public class SeqCliEncryptionProviderConfigTests
+{
+#if WINDOWS
+ [Fact]
+ public void DefaultDataProtectorOnWindowsIsDpapi()
+ {
+ Assert.True(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
+
+ var config = new SeqCliEncryptionProviderConfig();
+ var provider = config.DataProtector();
+ Assert.IsType(provider);
+ }
+#else
+ [Fact]
+ public void DefaultDataProtectorOnUnixIsPlaintext()
+ {
+ Assert.False(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
+
+ var config = new SeqCliEncryptionProviderConfig();
+ var provider = config.DataProtector();
+ Assert.IsType(provider);
+ }
+#endif
+
+ [Fact]
+ public void SpecifyingEncryptorRequiresDecryptor()
+ {
+ var config = new SeqCliEncryptionProviderConfig
+ {
+ Encryptor = "test"
+ };
+
+ Assert.Throws(() => config.DataProtector());
+ }
+
+ [Fact]
+ public void SpecifyingDecryptorRequiresEncryptor()
+ {
+ var config = new SeqCliEncryptionProviderConfig
+ {
+ Decryptor = "test"
+ };
+
+ Assert.Throws(() => config.DataProtector());
+ }
+
+ [Fact]
+ public void SpecifyingEncryptorAndDecryptorActivatesExternalDataProtector()
+ {
+ var config = new SeqCliEncryptionProviderConfig
+ {
+ Encryptor = "test",
+ Decryptor = "test"
+ };
+
+ Assert.IsType(config.DataProtector());
+ }
+}
\ No newline at end of file
diff --git a/test/SeqCli.Tests/SeqCli.Tests.csproj b/test/SeqCli.Tests/SeqCli.Tests.csproj
index b9f7df2d..73d17553 100644
--- a/test/SeqCli.Tests/SeqCli.Tests.csproj
+++ b/test/SeqCli.Tests/SeqCli.Tests.csproj
@@ -2,6 +2,21 @@
net9.0
$(TargetFrameworks);net9.0-windows
+ true
+ true
+ true
+
+
+ $(DefineConstants);WINDOWS
+
+
+ $(DefineConstants);OSX
+
+
+ $(DefineConstants);LINUX
+
+
+ $(DefineConstants);UNIX
diff --git a/test/SeqCli.Tests/Support/TempFolder.cs b/test/SeqCli.Tests/Support/TempFolder.cs
index 8a5e85a0..63a7e18b 100644
--- a/test/SeqCli.Tests/Support/TempFolder.cs
+++ b/test/SeqCli.Tests/Support/TempFolder.cs
@@ -15,7 +15,7 @@ public TempFolder(string name)
{
Path = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
- "SeqCli.Forwarder.Tests",
+ "SeqCli.Tests",
Session.ToString("n"),
name);