Skip to content

Commit

Permalink
Add SshConfig Connect tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
tmds committed Jul 31, 2024
1 parent b34b99e commit eff9e3e
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 113 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,9 @@ class SshClientSettings
}
class SshConfigOptions
{
static SshConfigOptions DefaultConfig { get; } // use [ '~/.ssh/config', '/etc/ssh/ssh_config' ]
static SshConfigOptions DefaultConfig { get; } // use DefaultConfigFilePaths.
static SshConfigOptions NoConfig { get; } // use [ ]
static IReadOnlyList<string> DefaultConfigFilePaths { get; } // [ '~/.ssh/config', '/etc/ssh/ssh_config' ]
IReadOnlyList<string> ConfigFilePaths { get; set; }

Expand Down
2 changes: 1 addition & 1 deletion src/Tmds.Ssh/SshClientSettings.SshConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ internal static async ValueTask<SshClientSettings> LoadFromConfigAsync(string de
Port = sshConfig.Port ?? DefaultPort,
UserKnownHostsFilePaths = sshConfig.UserKnownHostsFiles ?? DefaultUserKnownHostsFilePaths,
GlobalKnownHostsFilePaths = sshConfig.GlobalKnownHostsFiles ?? DefaultGlobalKnownHostsFilePaths,
ConnectTimeout = sshConfig.ConnectTimeout > 0 ? TimeSpan.FromSeconds(sshConfig.ConnectTimeout.Value) : DefaultConnectTimeout,
ConnectTimeout = sshConfig.ConnectTimeout > 0 ? TimeSpan.FromSeconds(sshConfig.ConnectTimeout.Value) : options.ConnectTimeout,
KeyExchangeAlgorithms = kexAlgorithms,
ServerHostKeyAlgorithms = hostKeyAlgorithms,
PublicKeyAcceptedAlgorithms = publicKeyAcceptedAlgorithms,
Expand Down
23 changes: 20 additions & 3 deletions src/Tmds.Ssh/SshConfigOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ namespace Tmds.Ssh;

public sealed class SshConfigOptions
{
public static readonly SshConfigOptions DefaultConfig = CreateDefault();
public static IReadOnlyList<string> DefaultConfigFilePaths { get; } = CreateDefaultConfigFilePaths();
public static SshConfigOptions DefaultConfig { get; }= CreateDefault();

public static readonly SshConfigOptions NoConfig = CreateNoConfig();
public static SshConfigOptions NoConfig { get; }= CreateNoConfig();

private bool _locked;

Expand Down Expand Up @@ -136,10 +137,26 @@ private static SshConfigOptions CreateDefault()

private static SshConfigOptions CreateNoConfig()
{
var config = new SshConfigOptions([]);
var config = new SshConfigOptions(DefaultConfigFilePaths);

config.Lock();

return config;
}

private static IReadOnlyList<string> CreateDefaultConfigFilePaths()
{
string userConfigFilePath = Path.Combine(SshClientSettings.Home, ".ssh", "config");
string systemConfigFilePath;
if (Platform.IsWindows)
{
systemConfigFilePath = Path.Combine(Environment.GetFolderPath(SpecialFolder.CommonApplicationData, SpecialFolderOption.DoNotVerify), "ssh", "ssh_config");
}
else
{
systemConfigFilePath = "/etc/ssh/ssh_config";
}

return [userConfigFilePath, systemConfigFilePath];
}
}
179 changes: 179 additions & 0 deletions test/Tmds.Ssh.Tests/ConnectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,183 @@ public async Task VerificationExceptionWrapped()

Assert.Equal(exceptionThrown, ex.InnerException);
}

[InlineData(true)]
[InlineData(false)]
[Theory]
public async Task AutoConnect(bool autoConnect)
{
using var client = await _sshServer.CreateClientAsync(
configure: settings => settings.AutoConnect = autoConnect,
connect: false
);

if (autoConnect)
{
using var sftpClient = await client.OpenSftpClientAsync();
}
else
{
await Assert.ThrowsAsync<InvalidOperationException>(() => client.OpenSftpClientAsync());
}
}

[Fact]
public async Task AutoConnectAllowsExplicitConnectBeforeImplicitConnect()
{
using var client = await _sshServer.CreateClientAsync(
configure: settings => settings.AutoConnect = true,
connect: false
);

await client.ConnectAsync();

using var sftpClient = await client.OpenSftpClientAsync();
}

[Fact]
public async Task AutoConnectDisallowsExplicitConnectAfterImplicitConnect()
{
// If a user calls ConnectAsync, we require it to happen before performing operations.
// If there is an issue connecting, this ConnectAsync will throw the connect exception.
// And, its cancellation token enables cancelling the connect.
using var client = await _sshServer.CreateClientAsync(
configure: settings => settings.AutoConnect = true,
connect: false
);

var pending = client.OpenSftpClientAsync();

await Assert.ThrowsAsync<InvalidOperationException>(() => client.ConnectAsync());
}

[InlineData(true)]
[InlineData(false)]
[Theory]
public async Task AutoReconnect(bool autoReconnect)
{
using var client = await _sshServer.CreateClientAsync(
configure: settings => settings.AutoReconnect = autoReconnect
);

using var sftpClient = await client.OpenSftpClientAsync();

client.ForceConnectionClose();

if (autoReconnect)
{
using var sftpClient2 = await client.OpenSftpClientAsync();
}
else
{
await Assert.ThrowsAsync<SshConnectionClosedException>(() => client.OpenSftpClientAsync());
}
}

[InlineData(true)]
[InlineData(false)]
[Theory]
public async Task SshConfig_AutoConnect(bool autoConnect)
{
using var client = await _sshServer.CreateClientAsync(
new SshConfigOptions([_sshServer.SshConfigFilePath])
{
AutoConnect = autoConnect
},
connect: false
);

if (autoConnect)
{
using var sftpClient = await client.OpenSftpClientAsync();
}
else
{
await Assert.ThrowsAsync<InvalidOperationException>(() => client.OpenSftpClientAsync());
}
}

[InlineData(true)]
[InlineData(false)]
[Theory]
public async Task SshConfig_AutoReconnect(bool autoReconnect)
{
using var client = await _sshServer.CreateClientAsync(
new SshConfigOptions([_sshServer.SshConfigFilePath])
{
AutoReconnect = autoReconnect
}
);

using var sftpClient = await client.OpenSftpClientAsync();

client.ForceConnectionClose();

if (autoReconnect)
{
using var sftpClient2 = await client.OpenSftpClientAsync();
}
else
{
await Assert.ThrowsAsync<SshConnectionClosedException>(() => client.OpenSftpClientAsync());
}
}

[Theory]
[InlineData(1)]
[InlineData(1000)]
public async Task SshConfig_Timeout(int msTimeout)
{
IPAddress address = IPAddress.Loopback;
using var s = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
s.Bind(new IPEndPoint(address, 0));
s.Listen();
int port = (s.LocalEndPoint as IPEndPoint)!.Port;

using var client = new SshClient($"user@{address}:{port}",
new SshConfigOptions([_sshServer.SshConfigFilePath])
{
ConnectTimeout = TimeSpan.FromMilliseconds(msTimeout)
});

SshConnectionException exception = await Assert.ThrowsAnyAsync<SshConnectionException>(() => client.ConnectAsync());
Assert.IsType<TimeoutException>(exception.InnerException);
}

[Fact]
public async Task SshConfig_ConnectFailure()
{
await Assert.ThrowsAnyAsync<SshConnectionException>(() =>
_sshServer.CreateClientAsync(SshConfigOptions.NoConfig));
}

[Fact]
public async Task SshConfig_HostAuthentication()
{
using TempFile tempFile = new TempFile(Path.GetTempFileName());
File.WriteAllText(tempFile.Path,
$"""
IdentityFile "{_sshServer.TestUserIdentityFile}"
""");
using var _ = await _sshServer.CreateClientAsync(
new SshConfigOptions([tempFile.Path])
{
HostAuthentication =
(KnownHostResult knownHostResult, SshConnectionInfo connectionInfo, CancellationToken cancellationToken) =>
{
Assert.Equal(KnownHostResult.Unknown, knownHostResult);
Assert.Equal(_sshServer.ServerHost, connectionInfo.HostName);
Assert.Equal(_sshServer.ServerPort, connectionInfo.Port);
string[] serverKeyFingerPrints =
[
_sshServer.RsaKeySHA256FingerPrint,
_sshServer.Ed25519KeySHA256FingerPrint,
_sshServer.EcdsaKeySHA256FingerPrint
];
Assert.Contains(serverKeyFingerPrints, key => key == connectionInfo.ServerKey.SHA256FingerPrint);
return ValueTask.FromResult(true);
}
}
);
}
}
88 changes: 0 additions & 88 deletions test/Tmds.Ssh.Tests/SshClientTests.cs

This file was deleted.

20 changes: 0 additions & 20 deletions test/Tmds.Ssh.Tests/SshConfigTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -384,24 +384,4 @@ private static async Task<SshConfig> DetermineConfigAsync(string config, string?
File.WriteAllText(tempFile.Path, config);
return await SshConfig.DetermineConfigForHost(username, host, port, [tempFile.Path], cancellationToken: default);
}

struct TempFile : IDisposable
{
public string Path { get; }

public TempFile(string path)
{
Path = path;
}

public void Dispose()
{
try
{
File.Delete(Path);
}
catch
{ }
}
}
}
Loading

0 comments on commit eff9e3e

Please sign in to comment.