Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 12 additions & 5 deletions src/SeqCli/Config/SeqCliEncryptionProviderConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
}
Expand Down
16 changes: 8 additions & 8 deletions src/SeqCli/Encryptor/ExternalDataProtector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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,
Expand Down
8 changes: 4 additions & 4 deletions src/SeqCli/SeqCli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'=='true'">
<DefineConstants>WINDOWS</DefineConstants>
<DefineConstants>$(DefineConstants);WINDOWS</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)'=='true'">
<DefineConstants>OSX</DefineConstants>
<DefineConstants>$(DefineConstants);OSX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)'=='true'">
<DefineConstants>LINUX</DefineConstants>
<DefineConstants>$(DefineConstants);LINUX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'!='true'">
<DefineConstants>UNIX</DefineConstants>
<DefineConstants>$(DefineConstants);UNIX</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Content Include="..\..\asset\SeqCli.ico" Link="SeqCli.ico" />
Expand Down
2 changes: 1 addition & 1 deletion test/SeqCli.EndToEnd/Support/TestDataFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
58 changes: 58 additions & 0 deletions test/SeqCli.Tests/Config/ExternalDataProtectorTests.cs
Original file line number Diff line number Diff line change
@@ -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<Win32Exception>(() => 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<Exception>(() => 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
}
66 changes: 66 additions & 0 deletions test/SeqCli.Tests/Config/SeqCliEncryptionProviderConfigTests.cs
Original file line number Diff line number Diff line change
@@ -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<WindowsNativeDataProtector>(provider);
}
#else
[Fact]
public void DefaultDataProtectorOnUnixIsPlaintext()
{
Assert.False(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));

var config = new SeqCliEncryptionProviderConfig();
var provider = config.DataProtector();
Assert.IsType<PlaintextDataProtector>(provider);
}
#endif

[Fact]
public void SpecifyingEncryptorRequiresDecryptor()
{
var config = new SeqCliEncryptionProviderConfig
{
Encryptor = "test"
};

Assert.Throws<ArgumentException>(() => config.DataProtector());
}

[Fact]
public void SpecifyingDecryptorRequiresEncryptor()
{
var config = new SeqCliEncryptionProviderConfig
{
Decryptor = "test"
};

Assert.Throws<ArgumentException>(() => config.DataProtector());
}

[Fact]
public void SpecifyingEncryptorAndDecryptorActivatesExternalDataProtector()
{
var config = new SeqCliEncryptionProviderConfig
{
Encryptor = "test",
Decryptor = "test"
};

Assert.IsType<ExternalDataProtector>(config.DataProtector());
}
}
15 changes: 15 additions & 0 deletions test/SeqCli.Tests/SeqCli.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@
<PropertyGroup>
<TargetFrameworks>net9.0</TargetFrameworks>
<TargetFrameworks Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">$(TargetFrameworks);net9.0-windows</TargetFrameworks>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'=='true'">
<DefineConstants>$(DefineConstants);WINDOWS</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)'=='true'">
<DefineConstants>$(DefineConstants);OSX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)'=='true'">
<DefineConstants>$(DefineConstants);LINUX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'!='true'">
<DefineConstants>$(DefineConstants);UNIX</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
Expand Down
2 changes: 1 addition & 1 deletion test/SeqCli.Tests/Support/TempFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down