capistrano / sshkit

A toolkit for deploying code and assets to servers in a repeatable, testable, reliable way.
MIT License
1.13k stars 253 forks source link

Support older net-sftp API for Ruby 2.0 #529

Closed mattbrictson closed 6 months ago

mattbrictson commented 6 months ago

Old Rubies such as Ruby 2.0 resolve a version of net-sftp that does not have the File#size convenience method. To support these older versions, use the lower level File#stat, which works for versions of net-sftp all the way back to 2.1.2.

This fixes a NoMethodError when SFTP is used via sshkit on Ruby 2.0.

I was able to confirm this with this successful CI job: https://github.com/capistrano/sshkit/actions/runs/7373383846/job/20062512939

Fixes #528

mattbrictson commented 6 months ago

@JasonPoll I'd appreciate it if you could also test this branch with your SFTP setup, when you have the chance. 🙏

JasonPoll commented 6 months ago

@JasonPoll I'd appreciate it if you could also test this branch with your SFTP setup, when you have the chance. 🙏

Running into some difficulty. Surprisingly, getting Ruby 2.0.0(p648) installed was not one of them. Did you know that bundler was not a default gem back in Ruby 2.0.0?

My initial set up:

rbenv shell 2.0.0-p648

ruby --version
ruby 2.0.0p648 (2015-12-16 revision 53162) [x86_64-linux]

gem install -v1.17.3 bundler

rm Gemfile.lock
bundle install
bundle show --paths | grep -E "(net|ed255|bcryp|rbn)"
/home/vagrant/.rbenv/versions/2.0.0-p648/lib/ruby/gems/2.0.0/gems/bcrypt_pbkdf-1.1.0
/home/vagrant/.rbenv/versions/2.0.0-p648/lib/ruby/gems/2.0.0/gems/ed25519-1.2.4
/home/vagrant/.rbenv/versions/2.0.0-p648/lib/ruby/gems/2.0.0/gems/net-scp-4.0.0
/home/vagrant/.rbenv/versions/2.0.0-p648/lib/ruby/gems/2.0.0/gems/net-sftp-2.1.2
/home/vagrant/.rbenv/versions/2.0.0-p648/lib/ruby/gems/2.0.0/gems/net-ssh-4.2.0

# verify my (RSA) keys are working, and that SFTP is functional to the remote host:
scp -v junk.dat vagrant@192.168.69.42:~/tmp/junk.dat
... 
debug1: Offering public key: /home/vagrant/.ssh/id_rsa RSA SHA256:R42SqxvKIiLq9WA058xqybHEoq2WJlx6PyuGYB3kqHc explicit
debug1: Server accepts key: /home/vagrant/.ssh/id_rsa RSA SHA256:R42SqxvKIiLq9WA058xqybHEoq2WJlx6PyuGYB3kqHc explicit
...
debug1: Sending subsystem: sftp
junk.dat                                                                      100% 1024KB  69.0MB/s   00:00   
...
Transferred: sent 1054632, received 5376 bytes, in 0.1 seconds
debug1: Exit status 0

# verify that SCP is disabled on the remote host: 
scp -v -O junk.dat vagrant@192.168.69.42:~/tmp/junk.dat
...
debug1: Offering public key: /home/vagrant/.ssh/id_rsa RSA SHA256:R42SqxvKIiLq9WA058xqybHEoq2WJlx6PyuGYB3kqHc explicit
debug1: Server accepts key: /home/vagrant/.ssh/id_rsa RSA SHA256:R42SqxvKIiLq9WA058xqybHEoq2WJlx6PyuGYB3kqHc explicit
...
debug1: Sending command: scp -v -t ~/tmp/junk.dat
SCP protocol is forbidden via /etc/ssh/disable_scp
lost connection
...
debug1: Exit status 255

Same test set up as in #524 - fire up a pry console and get myself set up:

require 'sshkit'
require 'sshkit/dsl'
include SSHKit::DSL

def doit(ip, &block)
  puts "- global transfer method: :#{SSHKit::Backend::Netssh.config.transfer_method}"
  on [ip] do |host|
    yield host if block_given? 

    as :vagrant do 
      within '/home/vagrant/tmp' do 
        upload! 'junk.dat', 'junk.dat'
        download! 'junk.dat', 'junk_downloaded.dat'
      end 
    end 
  end 
end 

My issue is that when I try to use my test method, I get:

doit("192.168.69.42")
- global transfer method: :scp
/home/vagrant/.rbenv/versions/2.0.0-p648/lib/ruby/gems/2.0.0/gems/mutex_m-0.1.2/lib/mutex_m.rb:110: warning: toplevel constant Mutex referenced by Thread::Mutex
/home/vagrant/.rbenv/versions/2.0.0-p648/lib/ruby/gems/2.0.0/gems/mutex_m-0.1.2/lib/mutex_m.rb:110: warning: toplevel constant Mutex referenced by Thread::Mutex
NotImplementedError: OpenSSH keys only supported if ED25519 is available
net-ssh requires the following gems for ed25519 support:
 * rbnacl (>= 3.2, < 5.0)
 * rbnacl-libsodium, if your system doesn't have libsodium installed.
 * bcrypt_pbkdf (>= 1.0, < 2.0)
See https://github.com/net-ssh/net-ssh/issues/478 for more information
Gem::LoadError : "rbnacl is not part of the bundle. Add it to your Gemfile."

from /home/vagrant/.rbenv/versions/2.0.0-p648/lib/ruby/gems/2.0.0/gems/net-ssh-4.2.0/lib/net/ssh/authentication/ed25519_loader.rb:19:in `raiseUnlessLoaded'

The thing is, I'm not using ED25519 keys (as my earlier scp CLI debug output demonstrates.) The referenced net-ssh issue 478 does mention that the rbnacl and rbnacl-libsodium gems are require for net-ssh <5.0, which appears to be what bundler 1.17.3 resolved on my test system -- the same as your referenced CI-run, neither of which include rbnacl or rbnacl-libsodium.

Am I doing something wrong, or am I missing something here? I'm not entirely sure why I'm getting the ED25519 error -- I only have RSA keys on this VM.

🤔 sshkit.gemspec has bcrypt_pbkdf and ed25519 as development dependencies, implying any consumer of SSHKit would need to add bcrypt_pbkdf and ed25519 to their own bundle if they intend to use ED25519 keys with SSHKit. Likewise, if someone is using ruby 2.0 (and resolves net-ssh to <5.0,) would they need rbnacl and rbnacl-libsodium in their bundle?

Should I temporarily add rbnacl and rbnacl-libsodium as development dependencies and try again?

mattbrictson commented 6 months ago

@JasonPoll thanks for all your investigation on this. I have a couple thoughts.

I'm not entirely sure why I'm getting the ED25519 error -- I only have RSA keys on this VM.

Even though the VM only uses RSA keys, net-ssh might be iterating through all of your SSH keys on the host machine, i.e. those in your ~/.ssh directory. If you have an ED25519 key in there, that might be enough to trigger the error.

In that case, yes, I think temporarily adding rbnacl and rbnacl-libsodium as development dependencies should hopefully do the trick. Otherwise you could try moving your ED25519 keys out of your ~/.ssh directory and/or remove them from your ssh-agent.

All that being said, I am mostly concerned that my changes in this PR didn't not cause a regression in the SFTP behavior that was previously working. For the purposes of merging this PR I would be satisfied if your tests work with a modern version of Ruby. So if getting Ruby 2.0 running is causing a problem, then feel free to test against whatever version is convenient.

Thanks again for your help!

mattbrictson commented 6 months ago

@JasonPoll thank you again for the thorough testing! I'll merge this.

JasonPoll commented 6 months ago

All that being said, I am mostly concerned that my changes in this PR didn't not cause a regression in the SFTP behavior that was previously working.

@mattbrictson - oops, sorry, I missed this last bit of your message. Rest assured that I just re-ran my ad-hoc tests with Ruby 3.2.2 -- same setup as in #524. I did not experience any regression in SCP enabled/disabled on remote host vs SSHKit SCP-vs-SFTP (global or per-host configured,) behaviors. All functioned the same with modern tooling.

My only remaining question would be when do you think we might see a new version of SSHKit cut, and see it incorporated into a fresh version of Capistrano? 😁

mattbrictson commented 6 months ago

when do you think we might see a new version of SSHKit cut, and see it incorporated into a fresh version of Capistrano?

I'll probably cut a new release of sshkit next Tuesday. It should then automatically be incorporated into capistrano the next time you bundle update your projects (or gem update sshkit if you aren't using Bundler).

mattbrictson commented 6 months ago

This has been released in sshkit 1.22.0. https://github.com/capistrano/sshkit/releases/tag/v1.22.0