Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jump channel #954

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
Open

Jump channel #954

wants to merge 14 commits into from

Conversation

JoostJM
Copy link

@JoostJM JoostJM commented Apr 28, 2022

Implements the option of connecting to a SSH server through another SSH server (jump-host).
This is achieved through a new ProxyConnector class: SshConnector.

In addition, refactor the structure of ConnectionInfo objects, improving the configuration of proxy connections.
Proxy connections are now defined using ConnectionInfo objects, which are set to the ProxyConnection property of a ConnectionInfo object. This allows for specification of multiple proxies through which to connect.

As SSH requires different configuration compared to other proxy types, create new sub-interfaces of the IConnectionInfo interface.

Update tests to reflect new interface.

Allow specification of jumphosts. Sessions opened using this configuration will connect to the remote host through the specified jump servers.
Rewrite some interfaces to allow this new type of ProxyConnection
Additionally, store and use information on proxy connections as a IConnectionInfo object "ProxyConnection" in the IConnectionInfo object. This allows the user to specify multiple proxies to connect through before making the final connection to the target (which will be done using a "DirectConnector" instance).

Update tests to reflect new interface
@JoostJM
Copy link
Author

JoostJM commented Apr 28, 2022

Related to #852.

connectionInfo is allowed to be of type other than ProxyConnectionInfo.
Due to new interface with proxy connections, ensure the correct Mock objects are used and setup.
When testing connectors and proxy connectors, initialize (proxy) servers first with port set to 0 (resulting in dynamic assignment of the port number). Then use the actual port number to initialize the connection info.

This prevents failing test due to already occupied ports.
@JoostJM
Copy link
Author

JoostJM commented Apr 28, 2022

A number of tests appear to be failing due to issues with the 8122 port used in tests. It looks like that port is already being used in AppVeyor. Commit 8f0da78 seems to fix this for most of the Connection tests, but the error occurs more often throughout the other tests.

Port 8122 is already bound in AppVeyor and therefore causes unexpected errors. Use dynamic port assignment (using port 0) where possible, port 8121 otherwise.

Addtionally, add mock setup for IConnector.Dispose() where these mocks are used.
Due to the new style of defining proxy connections, connection timeout should be set for each connection seperately.

Set the connection timeout on the proxy connection test (HTTP, SOCKS4 and SOCKS5) to reflect this.
@JoostJM
Copy link
Author

JoostJM commented May 11, 2022

Fixed failing tests. All changes in testing were due to the following reasons:

  • Many of the tests used port 8122 to simulate connections, either by setting up a listener or expecting it to fail due to no listener on that port. However, port 8122 is already bound on appveyor, which causes either the listener to be unable to bind the socket, or producing an unexpected result in other tests, as the socket can be connected to.
    • This error is fixed by either using dynamic port assignment, or using port 8121.
  • Timeout test set the timeout to a random small number. However, when testing proxy timeout, this should also be set on the ProxyConnectionInfo, which is a new object defined in this PR to allow connecting through multiple proxies.
    • This error is fixed by setting the timeout of proxyconnections to the same small value assigned to the connection info objects.
  • The new interface requires a service factory object to be passed to the constructor of connector objects, allowing these objects to instantiate proxyconnector objects. In testing, these objects are replaced by Mock<> objects.
    • This error is fixed by implementing Mock<> objects for ServiceFactory where required.

@jophippe
Copy link

Hi, I'm testing this PR to merge it into the Visual Studio fork of SSH.NET, and I'm running into a hanging issue. I'm using this code:

var connectionInfo = new ConnectionInfo(Destination, 22, "root", ProxyTypes.Ssh,
    new ConnectionInfo(Proxy, "admin", new PasswordAuthenticationMethod("admin", Password)),
    new PasswordAuthenticationMethod("root", Password));

using (var client = new SftpClient(connectionInfo))
{
    client.Connect();
    var dirs = client.ListDirectory("/");
    foreach (var dir in dirs)
    {
        Console.WriteLine(dir.FullName);
    }
    client.Disconnect();
}

and the program hangs on the client.Connect() line. When I debug, it looks like the Session creates an SshConnector, then the SshConnector creates a Session, then that Session creates an SshConnector, and that SshConnector creates a Session, and so on until the AuthenticationConnection check causes the program to hang. Is there something I'm doing wrong?

@JoostJM
Copy link
Author

JoostJM commented May 27, 2022

@jophippe, My apologies. The updates I implemented to make it fit better with the more recent changes to SSH net introduced a bug. Before, I instantiated the connector by also passing the proxy connection info to the constructor (and used it there to instantiate the proxy session required for the jump). In the update, I moved that part to the Connect function, but forgot to use the ProxyConnectionInfo, using the ConnectionInfo instead (pointing to the ultimate destination). This caused an infinite recursion, as each instantiation of the Session tries to make a new SSH Connector. I fixed the error now.

The SSH connector erroneously started a connection to the final destination host, instead of the intended proxy (or jump) host. Fix this error to prevent an infinite recursion.
@benrobot
Copy link

Any progress on this pull request? I would like to start using this feature.

@JoostJM
Copy link
Author

JoostJM commented Jul 31, 2023

This PR is/was ready for merging, but pending a review by @drieseng. I use it myself by checking out this branch. All checks passed.

@WojciechNagorski
Copy link
Collaborator

@JoostJM Amazing job! As soon as we release the .NET Standard version I'll want to test this PR. However, the branch needs to merge with the master because there are many conflicts.

* Assets/logos (sshnet#782)

* Added logo assets

* Added PNG 1260x640 with white border

Co-authored-by: 103filgualan <f.gualandi@crif.com>

* OPENSSH KeyReader for more keys (sshnet#614)

* OPENSSH KeyReader for more keys

Add support to parse OpenSSH Keys with ECDSA 256/384/521 and RSA.

https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key

Change-Id: Iaa9cce0f2522e5fee377a82cb252f81f0b7cc563

* Fix ED25519Key KeyLength

* Fix ED25519 PubKey-auth

LeadingZeros of BigInteger-Conversion have to be removed
before sending the Key.

* Add interface to SftpFile sshnet#120 (sshnet#812)

* Create ISftpFile interface. SftpFile sealed. Return ISftpFile from SftpClient instead of SftpFile. Make ISftpClient interface disposable.

Co-authored-by: Wojciech Swieboda <wswieboda@chathamfinancial.com>

* Start MessageListener with ThreadAbstraction.ExecuteThreadLongRunning (sshnet#902)

* Fix Thread pool exhaustion due to MessageListener running on ThreadPool
* Mark long running thread as background

* Add async support to SftpClient and SftpFileStream (sshnet#819)

* Add FEATURE_TAP and net472 target
* Add TAP async support to SftpClient and SftpFileStream
* Add async support to DnsAbstraction and SocketAbstraction
* Add async support to *Connector and refactor the hierarchy
* Add ConnectAsync to BaseClient

* Add CODEOWNERS file.

* Fix virus false-positive by Defender on Renci.SSHNet.Tests.dll (sshnet#867)

Co-authored-by: Pedro Fonseca <pfonseca@qti.qualcomm.com>

* Add unit tests for task-based asynchronous API (sshnet#906)

* Fix runtime and culture dependant tests.
* Set C# 7.3 in Tests.csproj to limit intellisense's suggestions under different targets
* Add SftpClientTest.*Async
* Add SftpFileStreamTest_OpenAsync_*
* Add SftpFileStreamTest_WriteAsync_*
* Add SftpFileStreamTest_ReadAsync_*
* Align AppVeyor script with Test project target frameworks

* correct 'Documenation' to 'Documentation' (sshnet#838)

in the documentation's window title

* Agent auth and Keygen (sshnet#794)

* Allow to set PrivateKeyFile Key directly
   So you can add your own Key-Classes to SSH.NET
* Add ED25519 ctor for just pub key part.
* Make ECDSA Key Bits accessible
   You cant export imported CngKeys. To be able to export them to agent or Key-Files make the private bits also accessible.
* Better NETFRAMEWORK vs NETSTANDARD handling
* Add Comment Property to Key
* Add IPrivateKeySource
  So Extension can add own PrivateKeyFiles, e.g. PuttyKeyFile.

* Use cryptographically secure random number generator.
Fixes CVE-2022-29245.

* Remove unused import.

* Add IBaseClient for BaseClient and ISftpClient to inherit from (sshnet#975)

Add IBaseClient for BaseClient and ISftpClient to inherit from

* fix typo (sshnet#999)

* Fix Seek Operations in SftpFileStream (sshnet#910)

* Fix offset operations in SftpFileStream.Seek
* Fix seek exception message and add default case for invalid seek origin
* Use named params when throwing ArgumentException
* Add tests for seeking from end of file

* Add back copyright to license. (sshnet#1060)

Fixes sshnet#1059.

* Removing old target frameworks (sshnet#1109)

Remove support for legacy / deprecated target frameworks while adding support for .NET 6.0 (and higher).
The supported target frameworks are now:
* .NETFramework 4.6.2 (and higher)
* .NET Standard 2.0
* .NET 6.0 (and higher)

* Remove old features [Part 1] (sshnet#1117)

Remove obsolete feature switches (now that we've remove support for legacy target frameworks) and remove corresponding conditional code.

* Remove FEATURE_DIRECTORYINFO_ENUMERATEFILES (sshnet#1119)

* Remove FEATURE_DIRECTORYINFO_ENUMERATEFILES
* Add exception documentation

* Fix some (lots of) issues reported by analyzers. (sshnet#1125)

Fix some (lots of) issues reported by analyzers.

* Round 2 of analyzer fixes and general cleanup. (sshnet#1132)

* Analyzer fixes round 3. (sshnet#1135)

* Replace Array<T>.Empty with Array.Empty<T>() (sshnet#1137)

* Replace IsNullOrWhiteSpace extension (sshnet#1142)

* Use License Expression for NuGet Package

licenseUrl is deprecated, see NuGet/Announcements#32

* Integration tests

* Remove todos

* Update CODEOWNERS

* Use correct SSH.NET

* ListDirectoryAsync return IAsyncEnumerable (sshnet#1126)

* ListDirectoryAsync return IAsyncEnumerable

* Fix documentation

* Update README.md

* Fix

* Add Sftp ListDirectoryAsync test

* Revert

* Integration tests for ListDirectoryAsync with IAsyncEnumerable

* Fix the assembly resolution build warning (sshnet#1165)

* Delete performance/longrunning tests (sshnet#1143)

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Move Integration tests (sshnet#1173)

* Renci.SshNet.IntegrationTests

* Renci.SshNet.TestTools.OpenSSH

* Move integration tests to main repo

* Move old tests to new integration tests

* Move old integration tests to new integration tests

* Move more tests

* Move authentication tests

* Move SshClientTests

* Fix some tests

* Remove duplicated test

* Poc of ProcessDisruptor

* Rename

* Some fixes

* Remove performance tests

* Small improvements

* Add a benchmarks project (sshnet#1151)

* Add a benchmarks project

* Small improvements

---------

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Use ExceptionDispatchInfo to retain call stack in Session.WaitOnHandle() (sshnet#936)

* Use ExceptionDispatchInfo to retain call stack in Session.WaitOnHandle()

* merge

* Update src/Renci.SshNet/Session.cs

Co-authored-by: Rob Hague <rob.hague00@gmail.com>

---------

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>
Co-authored-by: Rob Hague <rob.hague00@gmail.com>

* Support SHA256 fingerprints for host key validation (sshnet#1098)

* Add tests for HostKeyEventArgs

* Add SHA256 fingerprint support

* Add support for RSA SHA-2 public key algorithms (sshnet#1177)

* Abstract out the hash algorithm from RsaDigitalSignature

* Add integration tests

* Add DigitalSignature property to KeyHostAlgorithm

* Add IHostAlgorithmsProvider interface

* Verify the host signature

* Fix HostKeyEventArgsTest after merge

* Remove PubkeyAcceptedAlgorithms ssh-rsa

* Add test coverage for RSA keys in PrivateKeyFile

* Obsolete IPrivateKeySource

---------

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Improvements after sshnet#1177 (sshnet#1180)

* Use ExceptionDispatchInfo in more places (sshnet#1182)

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Try to "fix" the flaky test (sshnet#1185)

* Enable DSA tests (sshnet#1181)

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* FingerPrints (sshnet#1186)

* Use OS-agnostic socket error codes to allow tests run on different OSes (sshnet#1179)

SocketErrorCode is OS agnostic, ErrorCode is OS specific.

On Windows ErrorCode = (int) SocketErrorCode, but on Mac and Unix it is not.

For example ExitCode for HostNotFound (11001) on Windows is 11001, on Mac & Unix is -131073. So testing for ExitCode == 11001 fails on Mac & Unix.

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Fix for channel session semaphore from thread blocking (sshnet#1071)

* Merging fix from @clivetong into our own SSH.NET fork
- The following article describes some of the issues with the double check lock that we have seen issues with: https://www.sudhanshutheone.com/posts/double-check-lock-csharp

* Merging fix from @clivetong into our own SSH.NET fork
- The following article describes some of the issues with the double check lock that we have seen issues with: https://www.sudhanshutheone.com/posts/double-check-lock-csharp

* Update Channel to fix AppVeyor failure (field should be readonly)

* Update ISftpClient for sshnet#120 (sshnet#1193)

* Implement set last write and access time (sshnet#1194)

* Add/migrate hmac+cipher integration tests (sshnet#1189)

* Add/migrate hmac+cipher integration tests

* fix integration tests

---------

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Update tests for SetLastAccessTime(Utc) to also verify the time component and the Kind of the DateTime value returned by GetLastAccessTime(Utc). (sshnet#1198)

---------

Co-authored-by: Filippo Gualandi <filippo.gualandi@gmail.com>
Co-authored-by: 103filgualan <f.gualandi@crif.com>
Co-authored-by: Stefan Rinkes <darinkes@users.noreply.github.com>
Co-authored-by: wxtsxt <wojciech.swieboda@gmail.com>
Co-authored-by: Wojciech Swieboda <wswieboda@chathamfinancial.com>
Co-authored-by: Igor Milavec <igor.milavec@gmail.com>
Co-authored-by: drieseng <gert.driesen@telenet.be>
Co-authored-by: Pedro Fonseca <pbfonseca@gmail.com>
Co-authored-by: Pedro Fonseca <pfonseca@qti.qualcomm.com>
Co-authored-by: Maximiliano Jabase <maxijabase@gmail.com>
Co-authored-by: Owen Krueger <37021716+Owen-Krueger@users.noreply.github.com>
Co-authored-by: Masuri <psh0258@gmail.com>
Co-authored-by: LemonPi314 <49930425+LemonPi314@users.noreply.github.com>
Co-authored-by: Gert Driesen <gertdriesen@msn.com>
Co-authored-by: Rob Hague <rob.hague00@gmail.com>
Co-authored-by: Rob Hague <rh@johnstreetcapital.com>
Co-authored-by: Marius Thesing <marius.thesing@gmail.com>
Co-authored-by: Dāvis Mošenkovs <davikovs@gmail.com>
Co-authored-by: Dmitry Tsarevich <dimhotepus@users.noreply.github.com>
Co-authored-by: Patrick Yates <114094360+patrick-yates-redgate@users.noreply.github.com>
@WojciechNagorski
Copy link
Collaborator

I will review this PR. This feature would be great for next version. But you should merge this PR to develop branch, not the master. The best option for you will be to create a new PR from the develop branch.

@JoostJM
Copy link
Author

JoostJM commented Jul 5, 2024

I'll merge with the develop branch as well. First I still need to finish up reviewing the merges of the tests (source code merge with master is done).

Specifying a port of 0 should be handled correctly (i.e. the BoundPort property should reflect the actual port that is bound).
@JoostJM
Copy link
Author

JoostJM commented Jul 8, 2024

@WojciechNagorski, the merge with develop is complete. Besides the new features, this also contains some adjustments to the tests, removing the use of port 8122 as much as possible (using dynamic assignment of ports), and using 8121 elsewhere.
I implemented this as in the past, I ran into issues with tests in appveyor (it was already using port 8122 for something else).

@Rob-Hague
Copy link
Collaborator

Thanks for working on this @JoostJM

This branch is currently 140 files and 1600+ lines changed, with breaking changes on top. It is hard to really tell but my impression is that many of the changes are unrelated to this feature. I think the review process is going to be much easier for everyone if this PR is made as small as possible, so that it just contains changes which are necessary. For example, an improvement like the dynamic port assignment can go into a separate PR; a change like the port 8122 is not something we experience in appveyor right now, so is it related to this PR or is it now unnecessary?

Doing it this way will mean a reviewer does not have to find such a large block of free time to dedicate to the review, which is honestly hard to be motivated for.

I appreciate the branch could be a small tree at this age, so you may not be motivated yourself to do that. And maybe @WojciechNagorski has some different thoughts, that's just my 2c

Comment on lines +28 to +31
if (proxyConnection.GetType() != typeof(ConnectionInfo))
{
throw new ArgumentException("Expecting connectionInfo to be of type ConnectionInfo");
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's going on here? It's a bit unnerving

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the structure in SSH.NET, it would currently be OK to specify ProxyTypes.Ssh, but provinding a ProxyConnectionInfo instance (intended for HTTP and SOCKS proxy connections) as the proxyConnection parameter in the ConnectionInfo constructor. Therefore, I add a check here to ensure the provided proxyConnection can be used to start an SSH session (to the jump server). Still, it may be a better idea to move this check inside the constructor of ConnectionInfo.

Comment on lines -129 to -144
public string ProxyHost { get; private set; }

/// <summary>
/// Gets proxy connection port.
/// </summary>
public int ProxyPort { get; private set; }

/// <summary>
/// Gets proxy connection username.
/// </summary>
public string ProxyUsername { get; private set; }

/// <summary>
/// Gets proxy connection password.
/// </summary>
public string ProxyPassword { get; private set; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should all stay (no need to break anyone here), they can forward to the other ConnectionInfo if it exists

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can return these if you want. I removed them to prevent confusion. Now, both SSH and HTTP/SOCKS connections may now be used as a proxy connection. However, SSH connection authentication is handled differently from HTTP/SOCKS proxies, with the latter keeping the username and password in the ConnectionInfo instance, but SSH inside one or more child AuthenticationMethod. Having the ProxyUsername and ProxyPassword may suggest these would also represent the respective fields inside a password authentication for SSH, but this is then not the case.

@Rob-Hague
Copy link
Collaborator

On a more practical note

  1. Why do we need all the extra ConnectionInfo types and changes? Could we not just pass an existing ConnectionInfo to the constructor of another one?
// ssh -J user1@host1 user2@host2

var conn1 = new ConnectionInfo("host1", "user1", new PrivateKeyFile(...));
var conn2 = new ConnectionInfo("host2", "user2", new PrivateKeyFile(...), proxyConnection: conn1)

new SftpClient(conn2) // etc...
  1. Why is JumpChannel necessary? It looks very much like ForwardedPortLocal, which is how I understand jump host works. So can we just use ForwardedPortLocal?

@JoostJM
Copy link
Author

JoostJM commented Aug 13, 2024

On a more practical note

  1. Why do we need all the extra ConnectionInfo types and changes? Could we not just pass an existing ConnectionInfo to the constructor of another one?
// ssh -J user1@host1 user2@host2

var conn1 = new ConnectionInfo("host1", "user1", new PrivateKeyFile(...));
var conn2 = new ConnectionInfo("host2", "user2", new PrivateKeyFile(...), proxyConnection: conn1)

new SftpClient(conn2) // etc...

Do you mean why I now have distinct connection info classes for proxy HTTP/SOCKS connections and SSH connections?
For this I have 2 reasons:

  1. The topmost ConnectionInfo should always be a SSH-specific connection info, as the final connection should yield a SSH connection.
  2. HTTPS/SOCKS connections require an user and password to be set in the connection info object, but these are not required for SSH connections, so my thought was to keep them out of the ConnectionInfo class.
  1. Why is JumpChannel necessary? It looks very much like ForwardedPortLocal, which is how I understand jump host works. So can we just use ForwardedPortLocal?

It's true that you could use ForwardedPortLocal to set up a tunnel to be used as a jump channel, allowing you to achieve similar functionality. IMO, this PR represents an improvement for 2 reasons:

  1. Configuring and using it is now more intuitive, as only connection info needs to be provide, without having to worry about instantiating (and ultimatly disposing) the required tunnels.
  2. Using JumpChannel is more secure, as the ForwardedPortLocal listener is only listening until the first socket is connected. That way, no other sockets (potentially from different programs running on the host machine) may subvert the tunnel existing to the jump host.

Finally, changing the configuration of proxies from properties in the ConnectionInfo class to child ConnectionInfo instances allows defining multiple jump hosts to hop through.

@JoostJM
Copy link
Author

JoostJM commented Aug 13, 2024

Thanks for working on this @JoostJM

This branch is currently 140 files and 1600+ lines changed, with breaking changes on top. It is hard to really tell but my impression is that many of the changes are unrelated to this feature. I think the review process is going to be much easier for everyone if this PR is made as small as possible, so that it just contains changes which are necessary. For example, an improvement like the dynamic port assignment can go into a separate PR; a change like the port 8122 is not something we experience in appveyor right now, so is it related to this PR or is it now unnecessary?

Doing it this way will mean a reviewer does not have to find such a large block of free time to dedicate to the review, which is honestly hard to be motivated for.

I appreciate the branch could be a small tree at this age, so you may not be motivated yourself to do that. And maybe @WojciechNagorski has some different thoughts, that's just my 2c

It's true that the functional changes in this PR are not as extensive as the number of files and lines changed would suggest. This is mainly due to the fact that for any SSH proxy, it needed to refactor how proxies are defined.
This in turn resulted in internal breaking changes, though externally the breaking changes would be limited (just the removal of the proxy(...) properties in ConnectionInfo class. E.g. the current constructors for ConnectionInfo are preserved, and the new functionality is exposed through a new constructor.
These internal breaking changes required significant rewriting of the tests. Some of these rewrites may now be redundant (such as the use of dynamically assigned ports where possible and switching to 8121 instead of 8122 everywhere else). I can remove these latter changes if you want. For now, I let them in as I considered them to be more robust than the hardcoded ports and I already did the work.

I agree that it makes this PR quite daunting to review in one go. If it helps, I can split it up into 3 (successive?) PRs:

  1. The change to ConnectionInfo, moving proxy specification to child instances of ConnectionInfo. This also adds the option of defining multiple proxyconnection to connect through successively.
  2. The addition of the SshConnector proxy connector and the associated JumpChannel.
  3. Dynamic assignment of ports in the tests (this can potentially be a first PR, or removed entirely)

@JoostJM
Copy link
Author

JoostJM commented Sep 9, 2024

@Rob-Hague, @WojciechNagorski, what do you think about my suggestions? I'd rather not wait too long, or I'll need to spend again a lot of time getting everything up-to-date with the master branch...

@Rob-Hague
Copy link
Collaborator

I am sorry, I have not yet found a large enough chunk of time to consider this problem properly. In general I would favour simplicity unless there is proven demand otherwise.

I have a couple of other things to get through beforehand. As you know, the library does not move very fast so there are unlikely to be lots of conflicts any time soon :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants