apple / swift-nio-ssh Goto Github PK
View Code? Open in Web Editor NEWSwiftNIO SSH is a programmatic implementation of SSH using SwiftNIO
License: Apache License 2.0
SwiftNIO SSH is a programmatic implementation of SSH using SwiftNIO
License: Apache License 2.0
NIO SSH Commit Hash
36a4f6f4179ef5ebbeca501fc2125734b9f46290
Swift version
swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)
Target: arm64-apple-macosx14.0
uname -a
Darwin MacBook-Pro.local 23.2.0 Darwin Kernel Version 23.2.0: Wed Nov 15 21:55:06 PST 2023; root:xnu-10002.61.3~2/RELEASE_ARM64_T6020 arm64
Running Xcode 15.3 with macOS Sonoma 14.2.1 (23C71)
When using nio-ssh to execute ssh commands in a daemonized context (built executable launched using launchctl with a config in /Library/LaunchDaemons
) a ChannelError (operationUnsupported
) is thrown.
I'm unsure if this is a problem just with nio-ssh or nio in general. Could it be that certain network operations aren't permitted from within a daemon?
Any information/help on this matter is greatly appreciated!
Forum post: https://forums.developer.apple.com/forums/thread/749910
Reproduction can be found here: https://github.com/eliaSchenker/nio-ssh-daemon-issue/tree/main
To run the reproduction follow these steps:
/Library/PrivilegedHelperTools
/Library/LaunchDaemons/nio-ssh-daemon.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>nio-ssh-daemon</string>
<key>ProgramArguments</key>
<array>
<string>/Library/PrivilegedHelperTools/nio-ssh-daemon</string>
<string>username:password@host</string>
<string>ls -la</string>
</array>
<key>KeepAlive</key>
<true/>
<key>ProcessType</key>
<string>Interactive</string>
<key>StandardOutPath</key>
<string>/Library/Logs/nio-ssh-daemon.out.log</string>
<key>StandardErrorPath</key>
<string>/Library/Logs/nio-ssh-daemon.err.log</string>
</dict>
</plist>
making sure to adjust the program arguments to include an host with username and password.
sudo launchctl load nio-ssh-daemon.plist
Console.app
, navigating to Log Reports
and opening nio-ssh-daemon.out.log
the logged error will be shown:Creating bootstrap
Connecting channel
Creating child channel
Waiting for connection to close
Error in pipeline: operationUnsupported
An error occurred: commandExecFailed
If the executable is run manually without a daemon it will work correctly:
./nio.ssh-daemon username:password@host
The reproduction is a copy of the example in the repository (https://github.com/apple/swift-nio-ssh/tree/main/Sources/NIOSSHClient) with slight modifications to log errors instead of using try!
.
As described in #17, there are a large variety of (possible) extensions and use cases aside from opening ports on a remote machine.
The RequestSuccessMessage
type needs to be modified accordingly, so that other successful replies can be successfully received and processed.
There seem to be SSH-like servers on the internet that behave maliciously, with the intent of repelling malicious connections. They work by sending out an infinite version length or banner. I checked the recent code, but cannot find any length checks or tests. Although I doubt it's a common occurrence, I think it's best to add these checks with tests. I haven't determined a good maximum size yet, however. With some recommendations on that, I'll happily make a PR. I do think separate limits should exist for the pre/post KEX.
Hello
We are trying to connect our Mac and aws servers using the private key. but every time we got authentication failed.
Also, we have checked both keys on another application and it's working fine.so I think the problem is in the library. can you please help?
using this method
.byPublicKeyFromFile(username: self.username, password: "", publicKey: "", privateKey: pvtfilePath?.relativePath ?? "")
but not working can you please help us
We currently don't handle SSH_MSG_IGNORE and SSH_MSG_DEBUG gracefully. We can just ignore both of them for now, so we should add code to do so.
Hey guys, is it possible to implement remote/reverse port forwarding from the client side? Something similar as -R
switch used in the ssh command: ssh -R 9090:localhost:8080 some.remoteserver.com
I'm currently using swift-nio to run a local web server + websocket in the iOS application, and I would like to expose it using the remote ssh server without any additional port forwarding configured on the router. Is it something like this even possible using swift-nio? Thanks!
SwiftNIO SSH commit hash: 5d95eba
Context:
I'm trying to implement a NIOSSHClientServerAuthenticationDelegate
that actually verifies the host key.
The problem:
Except for debug printing, I have not found any way to access any information about the supplied NIOSSHPublicKey
.
Is there currently a way to access information about a NIOSSHPublicKey
? (Apologies if I missed something obvious, I'm fairly new to swift).
$ swift --version
Apple Swift version 5.3.2 (swiftlang-1200.0.45 clang-1200.0.32.28)
Target: x86_64-apple-darwin19.6.0
$ uname -a
Darwin tim-mac.local 19.6.0 Darwin Kernel Version 19.6.0: Mon Aug 31 22:12:52 PDT 2020; root:xnu-6153.141.2~1/RELEASE_X86_64 x86_64 i386 MacBookPro12,1 Darwin
Hello,
I was playing around with the library, and while the client example is good and shows how to establish a simple SSH connection, I can't quite figure out how to configure a direct TCP connection. I was able to change the example to open a channel of directTCPIP
type, but it seems to be ignoring the originatorAddress
, which I am assuming would be a port on my computer (just like when I call ssh -NL
) but I am probbaly wrong.
I probably am not being able to figure it out as I haven't tried Swift NIO before, but an example on those kinds of connections would be helpful to me and probably to many others. :)
Thanks in advance!
We can receive SSH_MSG_UNIMPLEMENTED whenever we send a message the remote peer doesn't like. In almost all cases this is going to be an unrecoverable error for us, but we should validate we report the error sensibly and clearly.
If one calls NIOSSHHandler.createChannel
when the parent channel is inactive (for example due to auth failure), it will never complete.
Today I found this project, but hit the issue that I have no clue how to use the project. I believe it would help a lot to project, if there would be some "Get started" documentation, which would just describe basic scenarios, starting with:
A server may care about which user has connected. I'm not seeing a path to connect the SSH user name to any of my handler code. I'm not even seeing it saved and available in SSHChildChannel
or anything hanging off of it.
Unless I've missed something this will take some doing to add. Options include:
Adding a SSHChannelRequestEvent
with a description of the user name which is sent first.
NIOSSHHandler()
could get a new initializer which passes authentication information into the inboundChildChannelInitializer:
equivalent.
Either of these presuppose that the user name is saved somewhere.
The server sends a global request marked as [email protected]
as soon as I open the nested channel. The swift-nio-ssh library then sees this global reqeust which it does not understand, causing an error to be thrown. This cascases into the SSHChildChannel, causing an error NIOSSHError.tcpShutdown
and shutting down the child channel.
There does not appear to be any way for a child channel implementation to get access to the peerMaxMessageSize
value derived from the SSH_MESSAGE_CHANNEL_OPEN
message. This value is needed by some subsystems, such as SFTP, which are required to manually perform splitting of large data packets manually. In SFTP's case, offering an oversized value in a WriteFile request causes some servers to enter a wedged state waiting for data that will never arrive; without access to the channel's desired maximum message size, it must assume the worst (the specification-required minimum supported size of 32768 payload bytes), which is obviously less than ideal.
As I use SwiftNIO SSH, I need to provide the ability for my users to employ their existing private keys to connect to a remote host. As has been well-documented, SwiftCrypto lacks the ability to decrypt such keys when generated by OpenSSH.
Quoth @Lukasa in the Slack:
"If the user’s OpenSSH private key is passphrase protected then we cannot handle them in-tree at all. Because the way those keys are encrypted does not allow us to decrypt them with the APIs Swift Crypto provides. This is a ripe opportunity for someone to write a third-party extension to the library to handle this use-case."
This proposed extension to SwiftNIO SSH should solve two orthogonal problems:
The README says:
Modern cryptographic primitives only: Ed25519 and EDCSA over the major NIST curves (P256, P384, P521) for asymmetric cryptography, AES-GCM for symmetric cryptography, x25519 for key exchange
There's a typo: EDCSA should be ECDSA.
Also please consider support chacha20poly1305 for symmetric crypto as it performs much better for devices without hardware accelerated AES instructions and it is also the default used by modern versions of OpenSSH, the most popular SSH implementations on servers.
Hi
I am looking from a way to upload file from my iOS app to an external ssh server.
I have tried swift-nio-ssh (NIOSSHClient example) from macos (not yet in iOS app) and I can connet to server and execute commands such as ls. Then I tried the put command and got "bash: put: command not found"... I think even though I can solve this on macos probably iOS will not have the put command. Well, my question is:
Is it possibel to use swift-nio-ssh, from an iOS app to upload a file to a ssh server not using bash commands but using class methods, such as childChannel.xx.yy.uploadfile("./abc.txt")?
Thank you very much
Alex
MacOS
I found 10.15 is set as minimum deployment target... is it possible to built it for 10.10 ?
For implementing RSA support, which will not be landing in the main library as discussed, we'll need to support external implementations for at least public-private keys. I'd also recommend looking into the same for the symmetric encryption as seen in #48
I see machinery for sending an SSH protocol "exit-status" SSHMessage
, and it initializes from SSHChannelData
(which I can send), but there doesn't seem to be any machinery to encode an "exit-status" in an SSHChannelData
and get it through into a SSHMessage.channelRequest
.
That looks to be the right way to send an exit-status, unless I'm missing something obvious.
So, is there a way I'm missing? Or maybe I should make a PR?
Once NIO adopts Sendable
conformances we should cleanup the following things:
@preconcurrency
from imports@unchecked
from SSHChildChannel
conformanceHi,
I'm trying to setup a SSH tunnel in order to forward a remote port into my local device. I followed your PR #55 and I successfully implemented it into a brand new iOS project: I am able to forward a port using password authentication.
What I'm trying to do is performing a private key authentication. Due to the lack of the documentation, I'm going to ask you a few questions. Here's the code:
//ssh-keygen -t ecdsa -b 521 -m pem
let sshPrivateText = """
-----BEGIN EC PRIVATE KEY-----
row1
row2
row3
row4
row5
-----END EC PRIVATE KEY-----
"""
let base64EncodedString:String = Data(sshPrivateText.utf8).base64EncodedString()
let ecdsaPrivateKeyData:Data = Data(base64Encoded: base64EncodedString, options: .ignoreUnknownCharacters)!
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let key: NIOSSHPrivateKey?
do {
key = NIOSSHPrivateKey(p521Key: try .init(rawRepresentation: ecdsaPrivateKeyData))
} catch let error {
fatalError(error.localizedDescription)
}
But I can't go further the NIOSSHPrivateKey
constructor since the .init throws an error:
Fatal error: The operation couldn’t be completed. (CryptoKit.CryptoKitError error 0.): file /Users/user/XCode Projects/NioExample/NioExample/ContentView.swift, line 35
2020-12-13 12:57:48.397737+0100 NioExample[2711:69134] Fatal error: The operation couldn’t be completed. (CryptoKit.CryptoKitError error 0.): file /Users/user/XCode Projects/NioExample/NioExample/ContentView.swift, line 35
(lldb)
▿ CryptoKitError
▿ underlyingCoreCryptoError : 1 element
- error : -1
Also, I have another question. Why the minimum SDK level is iOS13? Could it be downgraded or something? It's a huge device cut! Will this project be included in cocoapods? I'm asking this because I'm going to integrate this script into a native Flutter plugin and it seems like Flutter projects are not compatibile with SwiftPM.
Thank you for this amazing project by the way!
Sometimes its better to be sure that main parent channel is established without errors, before child channels are created
When testing NIOSSHClient, I am always getting stucked indefinitely on childChannel wait() method. Could somebody help me understand what is the problem?
While implementing 2-factor login in my app, I noticed that swift-nio-ssh
is missing support for keyboard-interactive
authentication. I can create a pull request to add it to the NIOSSHAvailableUserAuthenticationMethods: OptionSet
and make the client recognize keyboard-interactive messages. However, I am concerned that this change may cause issues on the server-side. So I created this issue to ask if you have any plans to support keyboard-interactive authentication in the future.
I don't know if I am missing something, is there a way to parse a private key file or private key data with a passphrase into a NIOSSHPrivateKey without knowing the type of private key you want to open?
Hi everybody,
first all of all, I want to say a huge thanks for extending SwiftNIO to support SSH connections and removing the need of relying on external libraries for ssh on swift.
I'm currently working on a ssh client that can execute requests on a remote machine. Since the project already relies on SwiftNIO, I was happy to hear about NIOSSH and wanted to use it for handling the connection and requests.
I oriented myself closely on the examples provided in NIOSSHClient and initialised the channel and child channel like this:
let clientBootstrap = ClientBootstrap(group: group)
.channelInitializer { channel in
channel.pipeline.addHandlers([NIOSSHHandler(role: .client(.init(userAuthDelegate: InteractivePasswordPromptDelegate(username: self.username, password: self.password), serverAuthDelegate: AcceptAllKeysDelegate())), allocator: channel.allocator, inboundChildChannelInitializer: nil), ErrorHandler()])
}
.channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
.channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1)
self.channel = try clientBootstrap.connect(host: self.ipAdress, port: self.port).wait()
let childChannel: Channel = try! channel!.pipeline.handler(type: NIOSSHHandler.self).flatMap { sshHandler in
let promise = self.channel!.eventLoop.makePromise(of: Channel.self)
sshHandler.createChannel(promise) { childChannel, channelType in
guard channelType == .session else {
return self.channel!.eventLoop.makeFailedFuture(SSHClientError.invalidChannelType)
}
return childChannel.pipeline.addHandlers([ExecutionHandler(), ErrorHandler()])
}
return promise.futureResult
}.wait()
self.childChannel = childChannel
The ExecutionHandler
that is passed to the childchannel's pipeline overrides func triggerUserOutboundEvent
and triggers an internal outbound event that contains a SSHChannelRequestEvent.ExecRequest
:
public func triggerUserOutboundEvent(context: ChannelHandlerContext, event: Any, promise: EventLoopPromise<Void>?) {
guard let (buffer, responseHandler) = event as? (ByteBuffer, ((String, Int32) -> Void)?) else {
promise?.fail(SSHError.invalidData)
return
}
self.responseHandler = responseHandler
let _ = context.triggerUserOutboundEvent(SSHChannelRequestEvent.ExecRequest(command: String(buffer: buffer), wantReply: false))
}
This works fine for one request. But if i try to trigger multiple outbound events, i.e. send multiple SSHChannelRequestEvent.ExecRequest
, it fails by returning inputClosed
and the child channel is closed. This is problematic since it loses the state of the requests. If I wanted to change the directory with one request, the second one would start again in the root dir.
I am fairly new to SwiftNIO and NIOSSH, so I wanted to ask if this is a known problem or am I just using the framework wrong?
The READ.me
states ...A session channel represents an invocation of a command.
Does this mean a session
is supposed to be closed after one command? If so, is there still a possibility to maintain the state of a request? For now I've been concatenating related commands together with command1 && command2
and executing them in one request. But this seems more like a hack to me.
Any help is greatly appreciated!
I am trying to use the codes from main.swift to do Local Port Forwarding but I am getting::
bind(descriptor:ptr:bytes:): Operation not permitted (errno: 1)
Can someone please help to do below SSH Cmd with Swift NIO SSH::
ssh -L 4944:localhost:4944 root@__IP__ -p 5544
I sucessfully made a channel but not very sure what to do next for Port Forwarding
I'm very new to Swift Nio, found it yesterday 😅
Sometimes we already have an established channel, for example,
one returned by establishing a tunneled connection. If this
is the case, then we cannot use it to start key exchange, since
it's only started when channel becomes active. In order to
support more use cases we should handle starting key exchange
when handler is added as well.
SwiftNIO SSH commit hash: c56abab
While writing Xcode tests in one of my project which uses swift-nio-ssh
, I encountered a crash.
I reproduced it using the NIOSSHClient
target.
yes \"long text\" | head -n 1000000\n
docker-compose -f ./docker/ssh-docker-compose.yaml up -d
NIOSSHClient
target.* thread #2, name = 'NIO-ELT-0-#0', stop reason = Fatal error: Sent channel window adjust on channel in invalid state
frame #0: 0x0000000193fe4ae8 libswiftCore.dylib`_swift_runtime_on_report
frame #1: 0x00000001940825d4 libswiftCore.dylib`_swift_stdlib_reportFatalErrorInFile + 208
frame #2: 0x0000000193c614b4 libswiftCore.dylib`closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in Swift._assertionFailure(_: Swift.StaticString, _: Swift.String, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 196
frame #3: 0x0000000193c60210 libswiftCore.dylib`Swift._assertionFailure(_: Swift.StaticString, _: Swift.String, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 308
* frame #4: 0x00000001002cae7c NIOSSHClient`ChildChannelStateMachine.sendChannelWindowAdjust(message=(recipientChannel = 0, bytesToAdd = 8811376), self=NIOSSH.ChildChannelStateMachine @ 0x0000000101e040a8) at ChildChannelStateMachine.swift:432:13
...
$ swift --version
swift-driver version: 1.75.2 Apple Swift version 5.8 (swiftlang-5.8.0.124.2 clang-1403.0.22.11.100)
Target: arm64-apple-macosx13.0
Operating system: macOS Ventura 13.2
Xcode version: 14.2
$ uname -a
Darwin MBP-de-Gaetan-Zanella 22.3.0 Darwin Kernel Version 22.3.0: Thu Jan 5 20:48:54 PST 2023; root:xnu-8792.81.2~2/RELEASE_ARM64_T6000 arm64
I'd suggest the channel to keep track of the channel being explicitly closed by the user or third party. When it is explicitly closed, I'd send the "cancel-tcpip-forward" global request as described on RFC 4254 page 17. This way, the user does not need to concern itself with protocol specific knowledge. This allows libraries to wrap a channel without requiring awareness of the SSH protocol.
PR #29 adds a foundation for parsing single keys, which is useful for parsing public key files. It would be helpful for NIOSSH servers to implement an authorized_keys parser.
It seems that there are some issues in autoRead
implementation, they don't fire down the pipeline
I am trying to figure out how to write a simple Swift code for running an ssh client port forwarding command. I am aware of [swift-nio-ssh] but am not sure how to use that in Xcode. Any suggestion?
From https://tools.ietf.org/html/rfc4253#section-4.2:
The server MAY send other lines of data before sending the version
string. Each line SHOULD be terminated by a Carriage Return and Line
Feed. Such lines MUST NOT begin with "SSH-", and SHOULD be encoded
in ISO-10646 UTF-8 [RFC3629] (language is not specified).
The NIOSSHClientUserAuthenticationDelegate
provides a single method, nextAuthenticationRequest
, for providing offers for authenticating with a remote host. In the golden path, an auth request is successful and everyone is happy. However, in situations where the request fails, either because of incorrect credentials or an unexpected failure on the remote end, it would be useful for the delegate to receive a notification to that effect. As things stand now, the only way to know an offer fails is if the method is called again. That type of inference is something I'd rather not rely upon.
In my research of this repository, I came across NIOSSHUserAuthenticationOutcome
; I wonder if using that here would be helpful?
Hello, I am trying to SSH to a HPC server using this library, but trying to authenticate when connecting always gives me the above error. I have tried both password authentication and p521 key authentication to no avail.
Since I have been able to successfully use SwiftNIO SSH to execute a test command on another known server, I am sure my code runs correctly (it is also adapted from the NIOSSHClient example code from this repository). I am also able to SSH with a regular computer to the HPC server.
Running ssh -Q key on the HPC server gives the following:
ssh-ed25519
[email protected]
ssh-rsa
ssh-dss
ecdsa-sha2-nistp256
ecdsa-sha2-nistp384
ecdsa-sha2-nistp521
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
which is exactly the same as on the test server I have been able to connect to, and includes ed25519. Any ideas what the problem could be?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.