Skip to content

Commit

Permalink
Merge pull request #24 from ktmitton/sftpConnectionSettings
Browse files Browse the repository at this point in the history
Added sftp connection settings to the sftp container
  • Loading branch information
ktmitton authored Apr 7, 2022
2 parents d87f365 + 84dfc53 commit 8cd48b6
Show file tree
Hide file tree
Showing 7 changed files with 393 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Net;
using System.IO;
using System.Text.Json;
using System.Linq;

namespace Mittons.Fixtures.Tests.Integration.Docker.Gateways
{
Expand Down Expand Up @@ -383,6 +384,88 @@ public void ContainerRemoveFile_WhenCalled_ExpectFileToBeRemoved(string containe

Assert.Empty(output);
}

[Fact]
public void ContainerExecuteCommand_WhenCalled_ExpectResultsToBeReturned()
{
// Arrange
var gateway = new DefaultDockerGateway();

var containerId = gateway.ContainerRun("atmoz/sftp:alpine", "guest:guest", new Dictionary<string, string>());
_containerIds.Add(containerId);

// Act
var results = gateway.ContainerExecuteCommand(containerId, "ssh-keygen -l -E md5 -f /etc/ssh/ssh_host_rsa_key.pub");

// Assert
using var proc = new Process();
proc.StartInfo.FileName = "docker";
proc.StartInfo.Arguments = $"exec {containerId} ssh-keygen -l -E md5 -f /etc/ssh/ssh_host_rsa_key.pub";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;

proc.Start();
proc.WaitForExit();

var output = proc.StandardOutput.ReadLine();

Assert.Single(results);
Assert.Equal(output, results.First());
}

[Fact]
public void ContainerGetHostPortMapping_WhenCalledForSftp_ExpectHostPortToBeReturnedForContainerPort22()
{
// Arrange
var gateway = new DefaultDockerGateway();

var containerId = gateway.ContainerRun("atmoz/sftp:alpine", "guest:guest", new Dictionary<string, string>());
_containerIds.Add(containerId);

// Act
var actualPort = gateway.ContainerGetHostPortMapping(containerId, "tcp", 22);

// Assert
using var proc = new Process();
proc.StartInfo.FileName = "docker";
proc.StartInfo.Arguments = $"port {containerId} 22/tcp";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;

proc.Start();
proc.WaitForExit();

int.TryParse(proc.StandardOutput?.ReadLine()?.Split(':')?.Last(), out var expectedPort);

Assert.Equal(expectedPort, actualPort);
}

[Fact]
public void ContainerGetHostPortMapping_WhenCalledForRedis_ExpectHostPortToBeReturnedForContainerPort6379()
{
// Arrange
var gateway = new DefaultDockerGateway();

var containerId = gateway.ContainerRun("redis:alpine", string.Empty, new Dictionary<string, string>());
_containerIds.Add(containerId);

// Act
var actualPort = gateway.ContainerGetHostPortMapping(containerId, "tcp", 6379);

// Assert
using var proc = new Process();
proc.StartInfo.FileName = "docker";
proc.StartInfo.Arguments = $"port {containerId} 6379/tcp";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;

proc.Start();
proc.WaitForExit();

int.TryParse(proc.StandardOutput?.ReadLine()?.Split(':')?.Last(), out var expectedPort);

Assert.Equal(expectedPort, actualPort);
}
}

public class NetworkTests : IDisposable
Expand Down
173 changes: 173 additions & 0 deletions Mittons.Fixtures.Tests.Unit/Docker/Containers/SftpContainerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System;
using Mittons.Fixtures.Docker.Attributes;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Net;

namespace Mittons.Fixtures.Tests.Unit.Docker.Containers
{
Expand Down Expand Up @@ -81,5 +83,176 @@ public void Ctor_InitializedWithMultipleSetOfCredentials_ExpectTheCommandToSetup
// Assert
gatewayMock.Verify(x => x.ContainerRun(sftpImageName, $"testuser1:testpassword1 testuser2:testpassword2 guest:guest", It.Is<Dictionary<string, string>>(x => x.Count == 1 && x.ContainsKey("mittons.fixtures.run.id"))), Times.Once);
}

[Fact]
public void Ctor_WhenIntitializedWithNoAccounts_ExpectGuestConnectionSettingsToBeSet()
{
// Arrange
var expectedHost = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "localhost" : "192.168.0.1";
var expectedPort = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 49621 : 22;

var expectedRsaMd5Fingerprint = "03:1e:ae:d2:78:33:8e:e2:3d:93:6c:73:95:b5:c3:ca";
var expectedRsaShaFingerprint = "w8tGM7exiFTGOjsjWccDgj9iSH4mkbvuUHhHK0euOeE";

var expectedEd25519Md5Fingerprint = "80:b4:c0:dc:dd:e8:4b:5c:2a:01:f5:ec:32:b1:e7:bf";
var expectedEd25519ShaFingerprint = "tSsvcVKxzqMNPFBrwraCuKCDQy6ADagQz77eOekTfTw";

var gatewayMock = new Mock<IDockerGateway>();

gatewayMock.Setup(x => x.ContainerGetDefaultNetworkIpAddress(It.IsAny<string>())).Returns(IPAddress.Parse("192.168.0.1"));

gatewayMock.Setup(x => x.ContainerGetHostPortMapping(It.IsAny<string>(), "tcp", 22)).Returns(49621);

gatewayMock.Setup(x => x.ContainerExecuteCommand(It.IsAny<string>(), "ssh-keygen -l -E md5 -f /etc/ssh/ssh_host_rsa_key.pub"))
.Returns(new[] { $"4096 MD5:{expectedRsaMd5Fingerprint} root@fec96a1bc7dc (RSA)" });
gatewayMock.Setup(x => x.ContainerExecuteCommand(It.IsAny<string>(), "ssh-keygen -l -E sha256 -f /etc/ssh/ssh_host_rsa_key.pub"))
.Returns(new[] { $"4096 SHA256:{expectedRsaShaFingerprint} root@fec96a1bc7dc (RSA)" });
gatewayMock.Setup(x => x.ContainerExecuteCommand(It.IsAny<string>(), "ssh-keygen -l -E md5 -f /etc/ssh/ssh_host_ed25519_key.pub"))
.Returns(new[] { $"256 MD5:{expectedEd25519Md5Fingerprint} root@fec96a1bc7dc (ED25519)" });
gatewayMock.Setup(x => x.ContainerExecuteCommand(It.IsAny<string>(), "ssh-keygen -l -E sha256 -f /etc/ssh/ssh_host_ed25519_key.pub"))
.Returns(new[] { $"256 SHA256:{expectedEd25519ShaFingerprint} root@fec96a1bc7dc (ED25519)" });

// Act
using var container = new SftpContainer(gatewayMock.Object, new SftpUserAccount[0]);

// Assert
Assert.Single(container.SftpConnectionSettings);
Assert.True(container.SftpConnectionSettings.ContainsKey("guest"));

var connectionSettings = container.SftpConnectionSettings["guest"];

Assert.Equal("guest", connectionSettings.Username);
Assert.Equal("guest", connectionSettings.Password);

Assert.Equal(expectedHost, connectionSettings.Host);

Assert.Equal(expectedPort, connectionSettings.Port);

Assert.Equal(expectedRsaShaFingerprint, connectionSettings.RsaFingerprint.Sha256);
Assert.Equal(expectedRsaMd5Fingerprint, connectionSettings.RsaFingerprint.Md5);

Assert.Equal(expectedEd25519ShaFingerprint, connectionSettings.Ed25519Fingerprint.Sha256);
Assert.Equal(expectedEd25519Md5Fingerprint, connectionSettings.Ed25519Fingerprint.Md5);
}

[Theory]
[InlineData("user", "password", "192.168.0.2", 48621, "23:1e:ae:d2:78:33:8e:e2:3d:93:6c:73:95:b5:c3:ca", "28tGM7exiFTGOjsjWccDgj9iSH4mkbvuUHhHK0euOeE", "20:b4:c0:dc:dd:e8:4b:5c:2a:01:f5:ec:32:b1:e7:bf", "2SsvcVKxzqMNPFBrwraCuKCDQy6ADagQz77eOekTfTw")]
[InlineData("other", "other", "192.168.0.3", 48321, "33:1e:ae:d2:78:33:8e:e2:3d:93:6c:73:95:b5:c3:ca", "38tGM7exiFTGOjsjWccDgj9iSH4mkbvuUHhHK0euOeE", "30:b4:c0:dc:dd:e8:4b:5c:2a:01:f5:ec:32:b1:e7:bf", "3SsvcVKxzqMNPFBrwraCuKCDQy6ADagQz77eOekTfTw")]
public void Ctor_WhenIntitializedWithAnAccount_ExpectAccountConnectionSettingsToBeSet(
string username,
string password,
string containerIpAddress,
int hostPort,
string expectedRsaMd5Fingerprint,
string expectedRsaShaFingerprint,
string expectedEd25519Md5Fingerprint,
string expectedEd25519ShaFingerprint
)
{
// Arrange
var expectedHost = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "localhost" : containerIpAddress;
var expectedPort = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? hostPort : 22;

var gatewayMock = new Mock<IDockerGateway>();

gatewayMock.Setup(x => x.ContainerGetDefaultNetworkIpAddress(It.IsAny<string>())).Returns(IPAddress.Parse(containerIpAddress));

gatewayMock.Setup(x => x.ContainerGetHostPortMapping(It.IsAny<string>(), "tcp", 22)).Returns(hostPort);

gatewayMock.Setup(x => x.ContainerExecuteCommand(It.IsAny<string>(), "ssh-keygen -l -E md5 -f /etc/ssh/ssh_host_rsa_key.pub"))
.Returns(new[] { $"4096 MD5:{expectedRsaMd5Fingerprint} root@fec96a1bc7dc (RSA)" });
gatewayMock.Setup(x => x.ContainerExecuteCommand(It.IsAny<string>(), "ssh-keygen -l -E sha256 -f /etc/ssh/ssh_host_rsa_key.pub"))
.Returns(new[] { $"4096 SHA256:{expectedRsaShaFingerprint} root@fec96a1bc7dc (RSA)" });
gatewayMock.Setup(x => x.ContainerExecuteCommand(It.IsAny<string>(), "ssh-keygen -l -E md5 -f /etc/ssh/ssh_host_ed25519_key.pub"))
.Returns(new[] { $"256 MD5:{expectedEd25519Md5Fingerprint} root@fec96a1bc7dc (ED25519)" });
gatewayMock.Setup(x => x.ContainerExecuteCommand(It.IsAny<string>(), "ssh-keygen -l -E sha256 -f /etc/ssh/ssh_host_ed25519_key.pub"))
.Returns(new[] { $"256 SHA256:{expectedEd25519ShaFingerprint} root@fec96a1bc7dc (ED25519)" });

// Act
using var container = new SftpContainer(gatewayMock.Object, new SftpUserAccount[] { new SftpUserAccount(username, password) });

// Assert
Assert.Single(container.SftpConnectionSettings);
Assert.True(container.SftpConnectionSettings.ContainsKey(username));

var connectionSettings = container.SftpConnectionSettings[username];

Assert.Equal(username, connectionSettings.Username);
Assert.Equal(password, connectionSettings.Password);

Assert.Equal(expectedHost, connectionSettings.Host);

Assert.Equal(expectedPort, connectionSettings.Port);

Assert.Equal(expectedRsaShaFingerprint, connectionSettings.RsaFingerprint.Sha256);
Assert.Equal(expectedRsaMd5Fingerprint, connectionSettings.RsaFingerprint.Md5);

Assert.Equal(expectedEd25519ShaFingerprint, connectionSettings.Ed25519Fingerprint.Sha256);
Assert.Equal(expectedEd25519Md5Fingerprint, connectionSettings.Ed25519Fingerprint.Md5);
}

[Theory]
[InlineData("192.168.0.2", 48621, "23:1e:ae:d2:78:33:8e:e2:3d:93:6c:73:95:b5:c3:ca", "28tGM7exiFTGOjsjWccDgj9iSH4mkbvuUHhHK0euOeE", "20:b4:c0:dc:dd:e8:4b:5c:2a:01:f5:ec:32:b1:e7:bf", "2SsvcVKxzqMNPFBrwraCuKCDQy6ADagQz77eOekTfTw")]
[InlineData("192.168.0.3", 48321, "33:1e:ae:d2:78:33:8e:e2:3d:93:6c:73:95:b5:c3:ca", "38tGM7exiFTGOjsjWccDgj9iSH4mkbvuUHhHK0euOeE", "30:b4:c0:dc:dd:e8:4b:5c:2a:01:f5:ec:32:b1:e7:bf", "3SsvcVKxzqMNPFBrwraCuKCDQy6ADagQz77eOekTfTw")]
public void Ctor_WhenIntitializedWithMultipleAccounts_ExpectAllAccountConnectionSettingsToBeSet(
string containerIpAddress,
int hostPort,
string expectedRsaMd5Fingerprint,
string expectedRsaShaFingerprint,
string expectedEd25519Md5Fingerprint,
string expectedEd25519ShaFingerprint
)
{
// Arrange
var accounts = new[]
{
new SftpUserAccount("user", "password"),
new SftpUserAccount("test", "test")
};

var expectedHost = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "localhost" : containerIpAddress;
var expectedPort = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? hostPort : 22;

var gatewayMock = new Mock<IDockerGateway>();

gatewayMock.Setup(x => x.ContainerGetDefaultNetworkIpAddress(It.IsAny<string>())).Returns(IPAddress.Parse(containerIpAddress));

gatewayMock.Setup(x => x.ContainerGetHostPortMapping(It.IsAny<string>(), "tcp", 22)).Returns(hostPort);

gatewayMock.Setup(x => x.ContainerExecuteCommand(It.IsAny<string>(), "ssh-keygen -l -E md5 -f /etc/ssh/ssh_host_rsa_key.pub"))
.Returns(new[] { $"4096 MD5:{expectedRsaMd5Fingerprint} root@fec96a1bc7dc (RSA)" });
gatewayMock.Setup(x => x.ContainerExecuteCommand(It.IsAny<string>(), "ssh-keygen -l -E sha256 -f /etc/ssh/ssh_host_rsa_key.pub"))
.Returns(new[] { $"4096 SHA256:{expectedRsaShaFingerprint} root@fec96a1bc7dc (RSA)" });
gatewayMock.Setup(x => x.ContainerExecuteCommand(It.IsAny<string>(), "ssh-keygen -l -E md5 -f /etc/ssh/ssh_host_ed25519_key.pub"))
.Returns(new[] { $"256 MD5:{expectedEd25519Md5Fingerprint} root@fec96a1bc7dc (ED25519)" });
gatewayMock.Setup(x => x.ContainerExecuteCommand(It.IsAny<string>(), "ssh-keygen -l -E sha256 -f /etc/ssh/ssh_host_ed25519_key.pub"))
.Returns(new[] { $"256 SHA256:{expectedEd25519ShaFingerprint} root@fec96a1bc7dc (ED25519)" });

// Act
using var container = new SftpContainer(gatewayMock.Object, accounts);

// Assert
Assert.Equal(accounts.Length, container.SftpConnectionSettings.Count);

foreach(var account in accounts)
{
Assert.True(container.SftpConnectionSettings.ContainsKey(account.Username));

var connectionSettings = container.SftpConnectionSettings[account.Username];

Assert.Equal(account.Username, connectionSettings.Username);
Assert.Equal(account.Password, connectionSettings.Password);

Assert.Equal(expectedHost, connectionSettings.Host);

Assert.Equal(expectedPort, connectionSettings.Port);

Assert.Equal(expectedRsaShaFingerprint, connectionSettings.RsaFingerprint.Sha256);
Assert.Equal(expectedRsaMd5Fingerprint, connectionSettings.RsaFingerprint.Md5);

Assert.Equal(expectedEd25519ShaFingerprint, connectionSettings.Ed25519Fingerprint.Sha256);
Assert.Equal(expectedEd25519Md5Fingerprint, connectionSettings.Ed25519Fingerprint.Md5);
}
}
}
}
68 changes: 66 additions & 2 deletions Mittons.Fixtures/Docker/Containers/SftpContainer.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,85 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Mittons.Fixtures.Docker.Attributes;
using Mittons.Fixtures.Docker.Gateways;
using Mittons.Fixtures.Models;

namespace Mittons.Fixtures.Docker.Containers
{
public class SftpContainer : Container
{
private const string ImageName = "atmoz/sftp:alpine";

public Dictionary<string, SftpConnectionSettings> SftpConnectionSettings { get; }

public SftpContainer(IDockerGateway dockerGateway, IEnumerable<Attribute> attributes)
: base(dockerGateway, attributes.Concat(new Attribute[] { new Image(ImageName), new Command(BuildCommand(attributes.OfType<SftpUserAccount>()))}))
: base(dockerGateway, attributes.Concat(new Attribute[] { new Image(ImageName), new Command(BuildCommand(ExtractSftpUserAccounts(attributes))) }))
{
var accounts = ExtractSftpUserAccounts(attributes);

var host = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "localhost" : IpAddress.ToString();
var port = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? dockerGateway.ContainerGetHostPortMapping(Id, "tcp", 22) : 22;
var rsaFingerprint = new Fingerprint { Md5 = GetFingerprint(dockerGateway, "rsa", "md5"), Sha256 = GetFingerprint(dockerGateway, "rsa", "sha256") };
var ed25519Fingerprint = new Fingerprint { Md5 = GetFingerprint(dockerGateway, "ed25519", "md5"), Sha256 = GetFingerprint(dockerGateway, "ed25519", "sha256") };

SftpConnectionSettings = accounts.Select(
x =>
new KeyValuePair<string, SftpConnectionSettings>(
x.Username,
new SftpConnectionSettings
{
Host = host,
Port = port,
Username = x.Username,
Password = x.Password,
RsaFingerprint = rsaFingerprint,
Ed25519Fingerprint = ed25519Fingerprint
}
)
).ToDictionary(x => x.Key, x => x.Value);
}

private string GetFingerprint(IDockerGateway dockerGateway, string algorithm, string hash)
{
var execResults = dockerGateway.ContainerExecuteCommand(Id, $"ssh-keygen -l -E {hash} -f /etc/ssh/ssh_host_{algorithm}_key.pub").ToArray();

if (execResults.Length != 1)
{
return string.Empty;
}

var parts = execResults.Single().Split(new[] { ' ' }, 3);

if (parts.Length != 3)
{
return string.Empty;
}

var fingerprint = parts[1].Split(new[] { ':' }, 2);

if (fingerprint.Length != 2)
{
return string.Empty;
}

return fingerprint[1];
}

private static IEnumerable<SftpUserAccount> ExtractSftpUserAccounts(IEnumerable<Attribute> attributes)
{
var accounts = attributes.OfType<SftpUserAccount>();

if (accounts.Any())
{
return accounts;
}

return new[] { new SftpUserAccount("guest", "guest") };
}

private static string BuildCommand(IEnumerable<SftpUserAccount> userAccounts)
=> string.Join(" ", ((userAccounts?.Any() ?? false) ? userAccounts : new SftpUserAccount[] { new SftpUserAccount("guest", "guest") }).Select(x => $"{x.Username}:{x.Password}"));
=> string.Join(" ", userAccounts.Select(x => $"{x.Username}:{x.Password}"));
}
}
Loading

0 comments on commit 8cd48b6

Please sign in to comment.