June 12, 2018 From rOpenSci (https://deploy-preview-488--ropensci.netlify.app/blog/2018/06/12/ssh-02/). Except where otherwise noted, content on this site is licensed under the CC-BY license.
Have you ever needed to connect to a remote server over SSH to transfer files via SCP or to setup a secure tunnel, and wished you could do so from R itself? The new rOpenSci ssh package provides a native ssh client in R allows you to do that and even more, like running a command or script on the host while streaming stdout and stderr directly to the client. The package is based on libssh, a powerful C library implementing the SSH protocol.
install.packages("ssh")
Because the ssh package is based on libssh it does not need to shell out. Therefore it works natively on all platforms without any runtime dependencies. Even on Windows.
The package is still work in progress, but the core functionality should work. Below some examples to get you started from the intro vignette.
First create an ssh session by connecting to an SSH server.
session <- ssh_connect("jeroen@dev.opencpu.org")
print(session)
## <ssh session>
## connected: jeroen@dev.opencpu.org:22
## server: 1e:28:44:af:84:91:e5:88:fe:82:ca:34:d7:c8:cf:a8:0d:2f:ec:af
Once established, a session is closed automatically by the garbage collector when the object goes out of scope or when R quits. You can also manually close it using ssh_disconnect()
but this is not strictly needed.
The client attempts to use the following authentication methods (in this order) until one succeeds:
privkey
argument in ssh_connect()
if specified~/.ssh/config
or any of the default locations: ~/.ssh/id_ed25519
, ~/.ssh/id_ecdsa
, ~/.ssh/id_rsa
, or .ssh/id_dsa
.To debug authentication set verbosity to at least level 2 or 3:
session <- ssh_connect("jeroen@dev.opencpu.org", verbose = 2)
## ssh_socket_connect: Nonblocking connection socket: 7
## ssh_connect: Socket connecting, now waiting for the callbacks to work
## socket_callback_connected: Socket connection callback: 1 (0)
## ssh_client_connection_callback: SSH server banner: SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.4
## ssh_analyze_banner: Analyzing banner: SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.4
## ssh_analyze_banner: We are talking to an OpenSSH client version: 7.2 (70200)
## ssh_packet_dh_reply: Received SSH_KEXDH_REPLY
## ssh_client_curve25519_reply: SSH_MSG_NEWKEYS sent
## ssh_packet_newkeys: Received SSH_MSG_NEWKEYS
## ssh_packet_newkeys: Signature verified and valid
## ssh_packet_userauth_failure: Access denied. Authentication that can continue: publickey
## ssh_packet_userauth_failure: Access denied. Authentication that can continue: publickey
## ssh_agent_get_ident_count: Answer type: 12, expected answer: 12
## ssh_userauth_publickey_auto: Successfully authenticated using /Users/jeroen/.ssh/id_rsa
Run a command or script on the host and block while it runs. By default stdout and stderr are steamed directly back to the client. This function returns the exit status of the remote command (hence it does not automatically error for an unsuccessful exit status).
out <- ssh_exec_wait(session, command = 'whoami')
## jeroen
print(out)
## [1] 0
You can also run a script that consists of multiple commands.
ssh_exec_wait(session, command = c(
'curl -O https://cran.r-project.org/src/contrib/Archive/jsonlite/jsonlite_1.4.tar.gz',
'R CMD check jsonlite_1.4.tar.gz',
'rm -f jsonlite_1.4.tar.gz'
))
## % Total % Received % Xferd Average Speed Time Time Time Current
## Dload Upload Total Spent Left Speed
## 100 1071k 100 1071k 0 0 654k 0 0:00:01 0:00:01 --:--:-- 654k
## * using log directory '/home/jeroen/jsonlite.Rcheck'
## * using R version 3.4.3 (2017-11-30)
## * using platform: x86_64-pc-linux-gnu (64-bit)
## * using session charset: ASCII
## * checking for file 'jsonlite/DESCRIPTION' ... OK
## * this is package 'jsonlite' version '1.4'
## * checking package namespace information ... OK
## * checking package dependencies ...
...
The ssh_exec_internal()
is a convenient wrapper for ssh_exec_wait()
which buffers the output steams and returns them as a raw vector. Also it raises an error by default when the remote command was not successful.
out <- ssh_exec_internal(session, "R -e 'rnorm(10)'")
print(out$status)
## [1] 0
cat(rawToChar(out$stdout))
## R version 3.4.4 (2018-03-15) -- "Someone to Lean On"
## Copyright (C) 2018 The R Foundation for Statistical Computing
## Platform: x86_64-pc-linux-gnu (64-bit)
##
## R is free software and comes with ABSOLUTELY NO WARRANTY.
## You are welcome to redistribute it under certain conditions.
## Type 'license()' or 'licence()' for distribution details.
##
## R is a collaborative project with many contributors.
## Type 'contributors()' for more information and
## 'citation()' on how to cite R or R packages in publications.
##
## Type 'demo()' for some demos, 'help()' for on-line help, or
## 'help.start()' for an HTML browser interface to help.
## Type 'q()' to quit R.
##
## > rnorm(10)
## [1] 0.14301778 -0.26873489 0.83931307 0.22034917 0.87214122 -0.13655736
## [7] -0.08793867 -0.68616146 0.23469591 0.93871035
This function is very useful if you are running a remote command and want to use it’s output as if you had executed it locally.
Note that the exec functions are non interactive so they cannot prompt for a sudo password. A trick is to use -S
which reads the password from stdin:
command <- 'echo "mypassword!" | sudo -s -S apt-get update -y'
out <- ssh_exec_wait(session, command)
Be very careful with hardcoding passwords!
Upload and download files via SCP. Directories are automatically traversed as in scp -r
.
# Upload a file to the server
file_path <- R.home("COPYING")
scp_upload(session, file_path)
## [100%] /Library/Frameworks/R.framework/Versions/3.5/Resources/COPYING
This will upload the file to the home directory on your server. Let’s download it back:
# Download the file back and verify it is the same
scp_download(session, "COPYING", to = tempdir())
## 18011 /var/folders/l8/bhmtp25n2lx0q0dgv1x4gf1w0000gn/T//Rtmpldz4eO/COPYING
We can compare the checksums to verify that the files are identical:
tools::md5sum(file_path)
## "eb723b61539feef013de476e68b5c50a"
tools::md5sum(file.path(tempdir(), "COPYING"))
## "eb723b61539feef013de476e68b5c50a"
Opens a port on your machine and tunnel all traffic to a custom target host via the SSH server.
ssh_tunnel(session, port = 5555, target = "ds043942.mongolab.com:43942")
This function blocks while the tunnel is active. Use the tunnel by connecting to localhost:5555
from a separate process. The tunnel can only be used once and will automatically be closed when the client disconnects.