Search code examples
sshlibgit2rugged

How To Invoke Rugged Check_connection Via ssh?


Below is a Ruby program that is supposed to verify a connection with my GitHub repos, private and public.

The ssh key is the correct one, and it has no passphrase. However, ssh is apparently not used, as shown near the end of this question.

require 'pathname'
require 'rugged'

base = ARGV[0] || '.'
base_fq = Pathname.new(base).realpath.to_s
repo = Rugged::Repository.new base_fq
puts repo.inspect
remote = repo.remotes['origin']
puts "remote.name=#{remote.name}, remote.url=#{remote.url}, remote.fetch_refspecs=#{remote.fetch_refspecs}"
credentials = Rugged::Credentials::SshKey.new(
  username:   'git',
  passphrase: nil,
  privatekey: File.expand_path('~/.ssh/id_rsa'),
  publickey:  File.expand_path('~/.ssh/id_rsa.pub')
)
puts credentials.inspect

success = remote.check_connection(:fetch, credentials: credentials)
puts "remote.check_connection(:fetch, credentials: credentials) returned #{success}"

success = remote.fetch(credentials: credentials)
puts "remote.fetch(credentials: credentials) returned #{success}"

Output is:

#<Rugged::Repository:60 {path: "/mnt/c/work/ruby/update/.git/"}>
remote.name=origin, [email protected]:mslinn/update.git, remote.fetch_refspecs=["+refs/heads/*:refs/remotes/origin/*"]#<Rugged::Credentials::SshKey:0x00007fee350db648 @username="git", @publickey="/home/mslinn/.ssh/id_rsa.pub", @privatekey="/home/mslinn/.ssh/id_rsa", @passphrase=nil>
remote.check_connection(:fetch, credentials: credentials) returned false
lib/check_connection.rb:21:in `fetch': unsupported URL protocol (Rugged::NetworkError)
        from lib/check_connection.rb:21:in `<main>'```

Why does check_connection always return false? Presumably that is also the reason the fetch raises an exception.

Connecting Via ssh

I can connect via ssh:

$ ssh github.com
Warning: No xauth data; using fake authentication data for X11 forwarding.
X11 forwarding request failed on channel 0
PTY allocation request failed on channel 0
Hi mslinn! You've successfully authenticated, but GitHub does not provide shell access.
Connection to github.com closed.

My ~/.ssh/config file contains:

Compression yes
ForwardX11 yes
ForwardX11Trusted yes
XAuthLocation /usr/bin/xauth

Host github.com
  User git
  PreferredAuthentications publickey

I discovered that libgit2 uses libssh2 for ssh support. However, libssh2 does not read config settings from ~/.ssh/config, so libgit2 does not support that either.

libssh2 Limitation

I found this on the GitHub blog:

If you’re using libgit2 or another piece of code using libssh2, we recommend you use libssh2 1.9.0 or newer and an ECDSA key, since it does not yet support RSA with SHA-2. Similarly, the Go SSH client also doesn’t yet support RSA with SHA-2, so we recommend using an Ed25519 key there.

I am using an RSA with SHA-2 key, which might be the problem:

$ ssh-keygen -l -f ~/.ssh/id_rsa
1024 SHA256:Xdv1AE4QTd0NfrwOGVTamF/wxnvFufCtsOIoOXtX5Mw Administrator@CHLOE (RSA)

Missing libgit2 ssh Feature

Here is how to discover the features that libgit2 was built with:

$ irb
irb(main):001:0> require 'rugged'
=> true

irb(main):002:0> Rugged.libgit2_version
=> [1, 6, 3]

irb(main):003:0> Rugged.features
=> [:threads, :https]

The above output shows the library is up-to-date (version 1.6.3), but it was not built with :ssh, so that is definitely a problem. I'll have to build the library to include that feature.

$ gem install rugged -- --with-ssh
Building native extensions with: '--with-ssh'
This could take a while...
Successfully installed rugged-1.6.3

$ irb
irb(main):001:0> require 'rugged'
=> true

irb(main):002:0> Rugged.features
=> [:threads, :https, :ssh]

ssh Not Used

I configured the git ssh command to increase verbosity:

$ git config core.sshCommand "ssh -vi ~/.ssh/id_rsa"

Now when I type git pull, the expected ssh debug information is displayed. However, this does not affect the output of my test program. Two possibilities:

  1. Perhaps libgit2 (and hence rugged) does not use the system ssh? After digging around on the Interwebs I believe this is true.

  2. Perhaps libgit2 (and hence rugged) does not support the core.sshCommand setting?

ssh-agent

No agent is required for git, it is optional, and I was not using one:

$ ssh-add -L
Could not open a connection to your authentication agent.

I set up ssh-agent and tried again:

$ eval $(ssh-agent) > /dev/null

$ ssh-add
Identity added: /home/mslinn/.ssh/id_rsa (/home/mslinn/.ssh/id_rsa)

$ ssh-add -l
1024 SHA256:Xdv1AE4QTd0NfrwOGVTamF/wxnvFufCtsOIoOXtX5Mw 
/home/mslinn/.ssh/id_rsa (RSA)

$ echo $SSH_AGENT_PID
2196

Then I changed the Ruby program to compute credentials using ssh-agent:

credentials = Rugged::Credentials::SshKeyFromAgent.new(username: 'git')

However, the Ruby program failed as before. Here is the output:

#<Rugged::Repository:60 {path: "/var/work/ruby/update/.git/"}>
remote.name=origin, [email protected]:mslinn/update.git, remote.fetch_refspecs=["+refs/heads/*:refs/remotes/origin/*"]
#<Rugged::Credentials::SshKeyFromAgent:0x00007fa290d169d0 @username="git">
remote.check_connection(:fetch, credentials: credentials) returned false

Solution

  • After clambering over hill and dale, the problem was that libgit2 as provided by the rugged gem does not include ssh support. I showed how to do that above by using the gem command.

    Bundler can also be configured to build libgit2 with ssh support:

    $ bundle config set --global build.rugged --with-ssh
    

    After typing the above, the next time you run bundle install on a Ruby project that specifies rugged as a dependency, if the resolved version of rugged is not installed, it will be built with ssh support.

    Once the above was done, the problem went away:

    $ ruby lib/check_connection.rb
    #<Rugged::Repository:60 {path: "/var/work/ruby/update/.git/"}>
    remote.name=origin, [email protected]:mslinn/update.git, remote.fetch_refspecs=["+refs/heads/*:refs/remotes/origin/*"]
    #<Rugged::Credentials::SshKey:0x00007f13c7adecd8 @username="git", @publickey="/home/mslinn/.ssh/id_rsa.pub", @privatekey="/home/mslinn/.ssh/id_rsa", @passphrase=nil>
    remote.check_connection(:fetch, credentials: credentials) returned true
    

    It is probably a good idea to add this to every rugged project:

    abort "Error: Rugged was not built with ssh support" \
      unless Rugged.features.include? :ssh
    

    For more information, see Working With Git Repos Using Ruby's Rugged Gem.