SSH: Difference between revisions

From miki
Jump to navigation Jump to search
(→‎Install: ssh-keygen)
 
(61 intermediate revisions by 2 users not shown)
Line 1: Line 1:
== Install ==
== Reference ==
On this Wiki:
After installing ssh (client & server), you have to create an ssh-key:
* [[SSH Tools]]

== Tips ==

On '''ssh''' command:
<source lang="bash">
<source lang="bash">
ssh -F hostname # Find hostname in ~/.ssh/known_hosts (useful if HashKnowHosts enabled)
ssh-keygen
ssh -l -f ~/.ssh/known_hosts # Print fingerprint of known host keys
ssh -Lport:host:hostport hostname sleep 60 # Forward port, and exit after 60s if no connection is made
ssh -f -N -n -q -L ${BINDADDR}:port:host:hostport hostname # Idem, with 'go backg', 'no remote cmd', 'redir stdin from /dev/nul', 'quiet'
</source>
</source>


If a script is to be called as a ssh command, use '''1>&2''' instead of '''>/dev/stderr''' to redirect a message to ''stderr'' (if not, you'll get an error <tt>undefined /dev/stderr</tt>):
== SSH GUI ==
<source lang="bash">
#! /bin/bash
# This script is called as an ssh command with
# ssh hostname thisscript.sh
echo "This will go to stdin"
echo "This will go to stderr" 1>&2
</source>


=== Gnome/Nautilus ===
=== Escape sequences ===
* Type <code>~?</code> after a '''newline''' to get a list of escape sequence


{| class=wikitable
* Under Gnome, one can uses menu ''Places'' &rarr; ''Connect to Server...'' to connect to a remote server in ''Nautilus''. The connection can be bookmarked for future use.
|-
* The syntax for address bar in ''Nautilus'' is '''<tt>sftp://''username''@''server''/''folder''</tt>'''.
! Sequence !! Effect
|-
| {{kbkey|Return}} <code>~?</code> || List available sequence
|-
| {{kbkey|Return}} <code>~.</code> || Terminate SSH session
|-
| {{kbkey|Return}} <code>~~</code> || Emit <code>~</code>. Also useful to send the escape sequence to a 2nd (tunneled) SSH connection
|}


=== KDE/Konqueror ===
== Install ==
After installing ssh (client & server), you have to create an ssh-key:
<source lang="bash">
ssh-keygen
</source>


=== Create a key ===
* Use [[KDE#KIO|KIO]] '''fish''' or '''sftp''' to establish a SSH or SFTP connection in ''Konqueror''.
<source lang="bash">
ssh-keygen -t ed25519 -f id_ed25519
</source>


=== gftp ===
=== Regenerate SSH host key ===
The easiest is to delete all keys and reconfigure openssh package:


<source lang=bash>
* [http://gftp.seul.org/ gftp] is a free multithreaded file transfer client for *NIX based machines. It supports the FTP, FTPS (control connection only), HTTP, HTTPS, SSH and FSP protocols.
rm /etc/ssh/ssh_host_*
/usr/sbin/dpkg-reconfigure openssh-server
</source>


Or manually:
== SSH Console ==
<source lang=bash>
ssh-keygen -t dsa -N "" -f /etc/ssh/ssh_host_dsa_key
ssh-keygen -t rsa -N "" -f /etc/ssh/ssh_host_rsa_key
ssh-keygen -t ecdsa -N "" -f /etc/ssh/ssh_host_ecdsa_key
</source>


=== SSH-Tunnel ===
* See official page on [http://wiki.yobi.be/wiki/Bypass_Proxy Yobi].
* To install
<div style="padding-left:2em;"><source lang="bash">
# Install ssh-tunnel
tar -xvzf ssh-tunnel-x.yy.tgz
make install


Afterwards, you must delete the old host key from {{file|known_hosts}} file of any client connecting to that server:
# Create empty ssh banner (will be updated at the first connection)
<source lang=bash>
touch ~/.ssh/clbanner.txt
ssh-keygen -R server_hostname

# Create ssh symlink ( need to have ~/bin in path !)
mkdir ~/bin
ln -s /usr/local/bin/ssh.pl ~/bin/ssh

# Edit ~/.ssh/config and ~/.ssh/proxy.conf
vi ~/.ssh/config
vi ~/.ssh/proxy.conf
</source></div>
* Install required packages (openssl and dev libraries) and required PERL packages (see [[Perl]]:
<div style="padding-left:2em;"><source lang="bash">
$ sudo apt-get install openssl libssl-dev
$ sudo cpan
# 2 following lines only needed if first time cpan is run
cpan> o conf init urllist
cpan> o conf commit
cpan> install Getopt::Long MIME::Base64 Net::SSLeay IO::Socket::SSL Authen::NTLM
</source></div>

* Here a patch on ssh-tunnel-v2.26 to prevent double expansion in command arguments.
<source lang="diff">
--- ssh-tunnel-2.26/ssh.pl 2007-04-15 20:15:36.000000000 +0200
+++ ssh-tunnel-2.26-patched/ssh.pl 2008-09-09 15:54:59.125000000 +0200
@@ -15,5 +15,5 @@
# Parse ssh-options
while ($#ARGV>=0 && $ARGV[0] ne '--') {
- push @SSHARGV, shift @ARGV;
+ push @SSHARGV, "\'" . shift(@ARGV) . "\'";
}
shift @ARGV if $ARGV[0] eq '--';
</source>
</source>


== Configuration ==
=== Remote Command Execution ===

* SSH allows to execute any command on remote SSH host. The syntax is
% ssh -t ''SSH_HOST'' ''COMMAND''
* To execute a remote command on remote host and stay connected afterwards, use <tt>ssh -t</tt>, along with bash ''rcfile'', like:
% ssh -t ''SSH_HOST'' "bash --rcfile ''PATH_TO_RC_FILE''"
Don't miss the quotes around the command. Bash will execute the commands in the rc file, and will open a session. Connection remains open because stdin/stdout is not closed. Option -t allows for connecting with current terminal. Without this option, there will be no terminal connection, so bash would run in batch mode (no prompt), and terminal features like tab completion or color would be missing.
* Another solution is to force bash ''interactive'' mode:
% ssh ''SSH_HOST'' "bash --rcfile ''PATH_TO_RC_FILE'' -i"
Since there is no terminal, bash goes by default in non-interactive mode. Interactive mode is forced with option <tt>-i</tt>, and so prompt will be printed, etc. But this is only a partial solution because there is still no terminal, ie. no color, no TAB auto-completion.

==== Troubleshooting ====
* <tt>~/bin/ssh.pl</tt> from <font color="red">''ssh-tunnel''</font> package currently interferes with the command. This is due to double argument processing and expansion. See patch above on v2.26.

== SSH Config ==
SSH can be configured through file <tt>~/.ssh/config</tt>. See <tt>[http://linux.die.net/man/5/ssh_config man ssh_config]</tt> for more information. The format is as follows:
SSH can be configured through file <tt>~/.ssh/config</tt>. See <tt>[http://linux.die.net/man/5/ssh_config man ssh_config]</tt> for more information. The format is as follows:


Line 96: Line 86:
The value to use for each option is given by the first section that matches the host specification and that provides a value for that option. So section <tt>Host *</tt> should always be at the end of the file, since any subsequent section will be ignored.
The value to use for each option is given by the first section that matches the host specification and that provides a value for that option. So section <tt>Host *</tt> should always be at the end of the file, since any subsequent section will be ignored.


=== ProxyCommand ===
== Usage ==
Specify the proxy command used by ssh to deal with proxies. If a default command is specified in <tt>host *</tt>, it can be overridden in a specific host section (use '''ProxyCommand none''' to tell ssh that there is no proxies).


=== Remote Command Execution ===
SSH allows to execute any command on remote SSH host. The syntax is
<source lang="bash">
ssh USER@HOST COMMAND ...
</source>

If COMMAND starts children processes, ssh only exits when all the children terminated. This is becaus these children connects to FD stdin/stdout/stderr and ssh has to keep them open.
To avoid that uses <code>-t</code> and <code>-q</code> to avoid the "closed connection" message:
<source lang="bash">
ssh -qt USER@HOST COMMAND ...
</source>

In addition, to avoid ssh to kill the children on exits, the job control must be enabled in the remote shell [https://stackoverflow.com/questions/14679178/why-does-ssh-wait-for-my-subshells-without-t-and-kill-them-with-t/14866774#14866774] (this is disabled when bash is started in non-interactive mode, which means that process parent and children are all in the same process group, which then receives the SIGHUP signal on exit). This is done with
<source lang="bash>
set -m
</source>
in the remote script.

In case of compound commands, the semi-colon may be escaped with <code>\</code>:
<source lang="bash">
ssh -qt USER@HOST COMMAND1\; COMMAND2...
</source>

Alternatively, use quotes:
<source lang="bash">
ssh -qt USER@HOST "COMMAND1; COMMAND2..."
</source>

if ssh is used within a function / script, and you want to pass along some positional parameters to the remote script that might include spaces or funny characters, use <code>${*@Q}</code> or <code>printf " %q" "$@"</code> to escape them:
<source lang="bash">
#! /bin/bash

if [ -n "$SSH_HOST" ];
exec ssh -qt $SSH_HOST "$0" ${*@Q}
fi
</source>

To execute a remote command on remote host and stay connected afterwards, use <tt>ssh -t</tt>, along with bash ''rcfile'', like:
<source lang="bash">
ssh -t SSH_HOST "bash --rcfile PATH_TO_RC_FILE"
</source>

Don't miss the quotes around the command. Bash will execute the commands in the rc file, and will open a session. Connection remains open because stdin/stdout is not closed. Option -t allows for connecting with current terminal. Without this option, there will be no terminal connection, so bash would run in batch mode (no prompt), and terminal features like tab completion or color would be missing.


Another solution is to force bash ''interactive'' mode:

<source lang="bash">
ssh SSH_HOST "bash --rcfile PATH_TO_RC_FILE -i"
</source>

Since there is no terminal, bash goes by default in non-interactive mode. Interactive mode is forced with option <tt>-i</tt>, and so prompt will be printed, etc. But this is only a partial solution because there is still no terminal, ie. no color, no TAB auto-completion.

=== SSH Escape Sequence ===
Source: https://lonesysadmin.net/2011/11/08/ssh-escape-sequences-aka-kill-dead-ssh-sessions/

Press {{kb|<return> ~}}, the following appears:
Supported escape sequences:
~. - terminate connection (and any multiplexed sessions)
~B - send a BREAK to the remote system
~C - open a command line
~R - request rekey
~V/v - decrease/increase verbosity (LogLevel)
~^Z - suspend ssh
~# - list forwarded connections
~& - background ssh (when waiting for connections to terminate)
~? - this message
~~ - send the escape character by typing it twice
(Note that escapes are only recognized immediately after newline.)

Useful ones:
* {{kb|<enter> ~.}} to exit a broken connection.
* {{kb|<enter> ~~}} to send tilde as first char after newline.

== Port Forwarding with SSH ==

Here two drawings that illustrate very clearly the mechanisms of port forwarding in SSH.

First the case of direct port forwarding, where a port is opened for listening on the (local) SSH Client, and forwarded to the given host and port on the remote side (i.e. accessible from SSH server).

[[file:ssh_port_forwarding_direct.png]]

Then the case of reverse port forwarding, where a port is opened for listening on the (remote) SSH Server, and forwarded to the given host and port locally (i.e. accessible from SSH client).

[[file:ssh_port_forwarding_reverse.png]]

== Applications ==
=== SSHFS ===
SSH FileSystem using FUSE.

'''References:'''
* [http://fuse.sourceforge.net/sshfs.html SSH Filesystem - homepage]
* [https://help.ubuntu.com/community/SSHFS SSHFS - Community Ubuntu Documentation]

'''Install'''
<source lang=bash>
<source lang=bash>
sudo apt-get install sshfs
Host myhost
sudo gpasswd -a $USER fuse
ProxyCommand none
</source>

'''Mount / Unmount'''
<source lang=bash>
mkdir ~/far_projects
sshfs -o idmap=user $USER@far:/projects ~/far_projects
fusermount -u ~/far_projects
</source>
The <code>idmap=user</code> option ensures that files owned by the remote user are owned by the local user. This is to deal with situations where these users have different UID. This option does not translate UIDs for other users.

'''/etc/fstab'''

Line to add to <tt>/etc/fstab</tt>:
<source lang=text>
sshfs#$USER@far:/projects /home/$USER/far_projects fuse defaults,idmap=user 0 0
</source>

== Tips and How-To ==
=== Controlling access ===
By editing {{file|.ssh/authorized_keys}} (see <code>man authorized_keys</code>) one can restrict the access a client has on the remote server.

;port-forwarding connection only
* Edit {{file|authorized_keys}} as follows (from Stack-Overflow [http://stackoverflow.com/questions/8021/allow-user-to-set-up-an-ssh-tunnel-but-nothing-else]):
no-pty,no-X11-forwarding,permitopen="localhost:6379",command="/bin/echo do-not-send-commands" ssh-rsa rsa-public-key-code-goes-here keyuser@keyhost
* Create the reverse port-forwarding with:
autossh -M 0 -f -T -N -n -q -R $PORT:localhost:22 noekeon

=== Reuse connection to boost performance ===
From http://www.debian-administration.org/article/290/Reusing_existing_OpenSSH_v4_connections:

Add the following to {{file|~/.ssh/config}}:
Host *
ControlPath /tmp/%r@%h:%p
Now we can connect as normal, so long as we make the first connection to any host with -M (for "Master") all subsequent connections will be much faster.

Or, force the -M for the first connection automatically with
Host *
ControlMaster auto
ControlPath /tmp/%r@%h:%p

This trick can be used to boost the performance of file transfer using '''scp''' (to make it similar to tar+ssh, see [http://lwn.net/Articles/401118/])

=== Connect through a Proxy ===
SSH can be said to establish the connection to the server through a proxy by using the option <code>ProxyCommand</code>. Example of '''ProxyCommand''' in the SSH config file:
<source lang=bash>
# none
ProxyCommand none # No proxy
# using nc
ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p # Example in ssh_config manpage but DOES NOT WORK
# using connect (aka connect-proxy)
ProxyCommand /usr/bin/connect %h %p # No proxy as well
ProxyCommand /usr/bin/connect -H proxyserver:8080 %h %p # Using HTTP proxy proxyserver:8080
ProxyCommand /usr/bin/connect -h %h %p # Using HTTP proxy defined in env. var HTTP_PROXY
ProxyCommand /usr/bin/connect -S socks5server:1080 %h %p # Using SOCKS5 proxy server socks5server:1080
ProxyCommand /usr/bin/connect -s %h %p # Using SOCKS5 proxy defined in env. var SOCKS5_SERVER
ProxyCommand /usr/bin/connect -S socks4server:1080 -4 %h %p # Using SOCKS4 proxy server socks4server:1080
# Using socat
ProxyCommand socat -ly - PROXY:proxy:%h:%p,proxyport=8080,proxyauth=user:pass
# HTTP Proxy
ProxyCommand socat -ly - SOCKS4A:socksserver:%h:%p,socksport=1080,socksuser=user
# SOCKS4A proxy (%h is resolved by proxy)
# Using ssh-tunnel
ProxyCommand /usr/local/bin/ssh-tunnel.pl -f - - %h %p
</source>
Note that ''connect-proxy'' also supports NTLM authentication (see [[Proxy#connect-proxy|Proxy]])

If a ''hostname'' matches several sections, first match found is used. Use '''ProxyCommand none''' to override a default proxy configuration:
<source lang=bash>
Host 192.*
ProxyCommand none # Otherwise setting in Host * would be taken
Host *
Host *
ProxyCommand /usr/local/bin/ssh-tunnel.pl -f - - %h %p
ProxyCommand /usr/local/bin/ssh-tunnel.pl -f - - %h %p # Default proxy settings
</source>
</source>


=== Dealing with Proxy Time-Out using ssh-tunnel ===
== SSH-Agent ==
In some case, the proxy might wait for the client (ie. local pc) to send an authentication string as it is the case in the SSL protocol. A solution for this is described in [http://wiki.yobi.be/wiki/Bypass_Proxy#Client_side:_using_socat Yobi]. It consists in sending immediately the client SSH banner, and strip it when it is sent by the client. The solution described uses a custom Perl script as <code>ProxyCommand</code>: '''ssh-tunnel.pl'''.
'''<tt>ssh-agent</tt>''' is a program that holds private keys used for public key authentication (RSA, DSA). Using this program, users only have to enter once the passphrase of their ssh key, and not at each <tt>ssh</tt> invokation.


;ssh-tunnel
On Ubuntu/Debian, first install the dependencies:
<source lang=bash>
sudo apt-get install ssh libssl-dev libgetopt-long-descriptive-perl libmime-base64-urlsafe-perl libnet-ssleay-perl libio-socket-ssl-perl libauthen-ntlm-perl
</source>

Then install <tt>ssh-tunnel</tt> (if needed, create <tt>~/bin</tt> first and don't forget to add <tt>~/bin</tt> in the path in <tt>.bashrc</tt> before <tt>/usr/bin</tt> and <tt>/usr/local/bin</tt>):
<source lang="bash">
<source lang="bash">
% eval `ssh-agent -s`
tar -xvzf ssh-tunnel-2.26.tgz
make install
% ssh-add # Here ssh-add asks for user's passphrase
#Create an empty ssh banner (will be updated at the first connection)
% ssh # Here no passphrase requested
touch ~/.ssh/clbanner.txt
#Create the links
ln -s /usr/local/bin/ssh.pl ~/bin/ssh
</source>
</source>


Edit <tt>~/.ssh/config</tt> and <tt>~/.ssh/proxy.conf</tt> as needed.
<tt>ssh-agent</tt> defines the environment variable <tt>SSH_AUTH_SOCK</tt>, which points to a unix socket that is used by '''ssh'' to communicate with the agent.


;Manual Perl package installation
=== Linux ===
If the Perl dependency packages are not available, they can be built manually:
On Linux, '''ssh-agent''' should be launched before starting the X session, so that all child processes have this variable defined. Also, be sure to kill all instances of '''ssh-agent''' when the session ends.


First install the packages:
=== Cygwin ===
* <tt>ssh</tt>, <tt>libssl-dev</tt> (cygwin: <tt>openssh</tt>, <tt>openssl</tt>, <tt>openssl-devel</tt>)
The situation is trickier on Cygwin / Windows because it is not possible to launch the '''ssh-agent''' before the Windows GUI.


Then fetch and build the Perl packages via CPAN (or consider using cpanminus):
I use the script below to overcome this situation (to install in <tt>/usr/local/bin/ssh-agent-refresh.sh</tt>). The script also works in multi-user environment, but only accept one ssh-agent instance per user.
<source lang="perl">
sudo cpan
# on first run, autoconfig starts - see wiki for more details
# Also if proxy must be set, run:
# o conf init urllist
# o conf commit
reload cpan
install Getopt::Long
install MIME::Base64
install Net::SSLeay
install IO::Socket::SSL
install Authen::NTLM
</source>

;Troubleshooting
* Run in verbose debug mode:
ssh +v -v noekeon -- -d -d -d -d
:If there are no debug message, make sure that ssh-tunnel is not started in ''quiet'' mode in {{file|.ssh/config}}:
ProxyCommand /usr/local/bin/ssh-tunnel.pl -q -f - - %h %p # BAD - quiet mode
ProxyCommand /usr/local/bin/ssh-tunnel.pl -f - - %h %p # OK
* Check that the gateway address, first field, is correct in {{file|./.ssh/proxy.conf}}. Get the gateway address with <code>netstat -rn</code>.

=== Creating a VPN over SSH ===
SSH allows to setup a full VPN connection over SSH, although it requires some manipulations, and is slower than regular VPN solutions using UDP packets (SSH uses TCP packets, which are slower).

;References
* References &mdash; with ''PermitRootLogin='''no''''':
:* [http://nick.zoic.org/art/etc/ssh_tricks/ More Trickiness With SSH
:* [http://nick.zoic.org/art/etc/ssh_tricks/comments.html] More Trickiness With SSH &mdash; Comments]
* References &mdash; with ''PermitRootLogin='''yes''''':
:* [https://help.ubuntu.com/community/SSH_VPN SSH_VPN] (rules to make the process automatic)
* More references
:* [http://ubuntuforums.org/showthread.php?t=12088 tun/tap on ubuntu] (creating tun device using OpenVPN)
:* [http://manpages.ubuntu.com/manpages/precise/man8/tunctl.8.html tunctl manual pages]
:* [http://manpages.ubuntu.com/manpages/precise/man1/ssh.1.html ssh manual pages]
:* [http://vtun.sourceforge.net/tun/faq.html tun/tap FAQ]

'''Preliminary Setup''':
* First on the server side, ssh daemon must allow creating of tunnels. For this setup however you '''don't''' need to enable ''PermitRootLogin''. Edit <tt>/etc/ssh/sshd_config</tt>:
<source lang=diff>
+PermitTunnel yes
</source>
* Check if '''ip''' supports creation of tun/tap interfaces:
<source lang=bash>
ip tuntap help
</source>
* If not, install '''openVPN''' (or a recent version of '''tunctl'''):
<source lang=bash>
sudo apt-get install openvpn
</source>
* Unlike what is proposed in ssh manual, we are not going to use private addresses (10.*.*.*) but instead we are going to pick 1 address in the remote network that we hope is not used by any computer on the remote network. This is because it seems that packets with a private address range as origin are lost (no reply). Say we pick address 123.123.123.1 and 123.123.123.2
* Also check that kernel on the client supports forwarding of packets, and that the firewall authorizes it as well ([http://serverfault.com/questions/109343/how-do-i-debug-routing-problems-from-a-virtual-tun0-device]):
<source lang=bash>
sudo su
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -P FORWARD ACCEPT
iptables -F FORWARD
</source>

'''Setting up the VPN''':
* Log on the server, and create the '''tun0''' interface with ''ip'' or ''openvpn'', and configure the interface:
<source lang=bash>
sudo ip tuntap add dev tun0 mode tun user $USER # or 'sudo openvpn --mktun --dev tun0 --user $USER'
sudo ifconfig tun0 123.123.123.1 123.123.123.2 netmask 255.255.255.252 # Assign 123.123.123.2 as local IP addr on tun0 (and .1 as peer)
sudo route add -net 123.123.120.0/22 dev tun0 # This will route all packets to 123.123.120.0/24 network through
# ... our tun0 interface directly to the client.
# ... these packets will have 123.123.123.2 as source IP
</source>

* On the client, create the '''tun0''' interface, and setup the tunnel and route table:
<source lang=bash>
sudo ip tuntap add dev tun0 mode tun user $USER # or 'sudo openvpn --mktun --dev tun0 --user $USER'
sudo ifconfig tun0 123.123.123.2 123.123.123.1 netmask 255.255.255.252
sudo route add -host 123.123.123.2 dev tun0 # Route all packets for 123.123.123.2 through tun0 itf
sudo arp -Ds 123.123.123.2 eth0 pub # Tell all computers on eth0 that we'll take care of packets for IP 123.123.123.2
ssh -f -w 0:0 $SSHSERVER true # SSH in background, tunnel from tun0:tun0
</source>

'''Troubleshooting'''
* Setup ''tcpdump'' on 'tun0' on the client and server
<source lang=bash>
sudo tcpdump -i tun0 -n icmp # On the client and server
</source>
* Then on the server, ping the client and verify that ICMP packets are sent back and forth:
<source lang=bash>
ping 123.123.123.1 # peer on tun0 itf
</source>
* Second, we will ping a computer on the remote netwrok from the server. On the client, setup ''tcpdump'' to now monitors packet on the 'eth0' interface:
<source lang=bash>
sudo tcpdump -i etho0 -n icmp # On the client
sudo tcpdump -i tun0 -n icmp # On the server
</source>
* Ping the remote computer, and observes that packet now also are forwarded by the client over the 'eth0' interface.<br/>If packets are sent, but none are received, likely it is because the ARP proxy is not setup correctly or is not ''published'' (<tt>pub</tt> keyword with <code>arp</code>). If no packets are sent or received, but packets are seen on the client ''tun0'' interface, then probably packet forwarding is not enabled on the client kernel, or firewall forbids it.
<source lang=bash>
ping 123.123.120.1 # On the server
</source>

Remarks:
* SSH manpage creates tun device with different ID, but this is only needed if ssh'ing to localhost.
* Use <code>-w any:any</code> to let ssh uses any available tun devices
* Using <code>-o Tunnel=Ethernet</code> is mandatory for ''tap'' interfaces, or get a <tt>tunnel device open failed</tt> error.
* Enabling <code>PermitRootLogin</code> offers more flexibility. Security impact can be greatly reduced by restricting root ssh access to execute a forced command (see [http://bodhizazen.net/Tutorials/VPN-Over-SSH] as an example)

=== Multiplexing SSH connection for faster connect ===
SSH may multiplex several connections on a single one to accelerate new connections.
This uses the config parameters <code>ControlMaster</code>, <code>ControlPath</code> and <code>ControlPersist</code> in {{file|~/.ssh/config}}.

See [https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing Multiplexing] for details ([https://dev.to/cpu/ssh-config-tips-and-tricks-54hk], [https://serverfault.com/questions/26422/dynamically-generate-ssh-host-entries-in-ssh-config]).

=== Dynamic configuration using <code>Match</code> ===
Instead of using <code>Host</code> keyword to group ssh configuration in {{file|~/.ssh/config}}, we can use <code>Match</code> to build more complex configuration, and among others configurations that depend on runtime settings.

For instance, assume we have a script {{file|on-acme-network}} that returns true if we are on some ACME network, detecting by testing the current network gateway:


<source lang="bash">
<source lang="bash">
#!/bin/bash
#! /bin/bash
#
# This script will detect any running ssh-agent and restore the environment
# variable that would normally be created with the command
#
# % ssh-agent -s
#
# By default, this script looks for an existing ssh-agent process already running with
# same UID as the current shell. If none is found, a new ssh-agent process is launched.
# If the SSH_AUTH_SOCK is not specified, the script will try to find back the correct
# socket name. For this it looks for a socket named /tmp/ssh-*/agent.*, with same UID
# as current script.
#
# If the environment variable SSH_AUTH_SOCK is set, ssh-agent will use that socket name
# instead of generating a new one (on first invocation).
#
# Example of use:
# ssh-agent-refresh.sh
# if ( ssh-add -L | grep -q $USER ); then ssh-add -t 3600; fi
#
# Example with predefined SSH_AUTH_SOCK
# export SSH_AUTH_SOCK=/tmp/.ssh-agent-$USER
# ssh-agent-refresh.sh
# if ( ssh-add -L | grep -q $USER ); then ssh-add -t 3600; fi
#
#
# Script on-acme-network
# Example of output of ssh-agent -s:
#
# SSH_AUTH_SOCK=/tmp/ssh-VAjpOtefMI/agent.2112; export SSH_AUTH_SOCK;
# SSH_AGENT_PID=2568; export SSH_AGENT_PID;
# echo Agent pid 2568;


function get_gateway()
SSH_AGENT_PROCESS_NAME=ssh-agent
{
if [ -x /sbin/ip ]; then
/sbin/ip route | awk '/^default/ { print $3 }' | head -n 1
else
/bin/netstat -rn | perl -lne "print for /^0\.0\.0\.0 +([\.0-9]+) /g" | head -n 1
fi
}


[ "10.10.10.10" = "$(get_gateway)" ]
# Shell must be a login shell - for USER variable
</source>
if [ -z "$USER" ]; then
echo "ERROR! Environment variable USER not defined - you probably don't run a login shell"
exit 4
fi


Then we can create an SSH config that will connect through a proxy if we are in ACME network directly to our hosts, and connect directly otherwise:
# First see check that at most one instance of ssh-agent is running.
<source lang="bash">
SSH_AGENT_COUNT=`ps -su $USER | grep -c "$SSH_AGENT_PROCESS_NAME"`
# ~/.ssh/config
if [ $SSH_AGENT_COUNT -gt 1 ]; then
echo "ERROR! Several ssh-agent processes are running">/dev/stderr
exit 3
fi


Host my_ssh_server
# Second launch a new ssh-agent if none is running. We use variable SSH_AUTH_SOCK if defined
User some_user
if [ $SSH_AGENT_COUNT -eq 0 ]; then
HostName my_ssh_server.com
if [ $SSH_AUTH_SOCK ]; then
ssh-agent -a "$SSH_AUTH_SOCK" -s
else
ssh-agent -s
fi
exit 1
fi


Host my_other_ssh
# Third, find back ssh-agent-pid We use the blob below because pidof doesn't filter based on process UID
User some_user
SSH_AGENT_PID=`ps -su $USER | grep "$SSH_AGENT_PROCESS_NAME" | sed -r 's/^ *([0-9]*) .*$/\1/'`
HostName my_other_ssh.com


Host *
# Next find the socket that the running ssh-agent is attached to. We reuse variable SSH_AUTH_SOCK if it is defined.
ForwardX11 no
if [ ! $SSH_AUTH_SOCK ]; then
ForwardX11Trusted no
SSH_AUTH_SOCK=`find /tmp -type s -user $USER -path "/tmp/ssh-*/agent.*"`
ServerAliveInterval 15
else
if [ -x "$SSH_AUTH_SOCK" ]; then
echo "ssh-agent process found (pid $SSH_AGENT_PID), but given socket does not exist ($SSH_AUTH_SOCK)!">/dev/stderr
exit 2
fi
fi


Match exec on-acme-network
echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK; export SSH_AUTH_SOCK;"
ProxyCommand connect-proxy -H proxy.acme.com:8080 %h %p
echo "SSH_AGENT_PID=$SSH_AGENT_PID; export SSH_AGENT_PID;"
</source>
echo "echo Agent pid $SSH_AGENT_PID;"


Note: another solution for dynamic configuration is to create {{file|config}} as a named pipe and make sure there is always a process writing to it, or use ''scriptfs'' [https://unix.stackexchange.com/questions/208786/determine-the-target-of-an-ssh-connection-at-runtime].
exit 0

=== Restrict ssh connection to a given command ===
This is done in {{file|authorized_keys}}. For instance, for borg:

command="/usr/local/bin/borg serve --restrict-to-path /home/borg/repo/",restrict ssh-ed25519 AA...

To restrict to port forwarding only:
<source lang="bash">
# On the client
autossh -M 0 -f -N -n -q -L 9001:localhost:9001 -L 9002:localhost:9002 server
ssh -f -N -n -q -L 9001:localhost:9001 -L 9002:localhost:9002 server
# In authorized_keys
no-pty,no-X11-forwarding,permitopen="localhost:9002",permitopen="localhost:9002",command="/bin/echo do-not-send-commands" ssh-ed25519 AAAA...
</source>
</source>


=== Deal with unreliable connnection ===
Then add the following lines in your file <tt>~/.bash_profile</tt> (not in the <tt>~/.bashrc</tt> because we use variable <tt>USER</tt> which is only defined in a login shell):
sshd may not detect rapidly that remote client is no longer connected. As a result, whatever process launched on the server may still consume resources needlessly [https://borgbackup.readthedocs.io/en/stable/usage/serve.html].

Edit client configuration {{file|~/.ssh/config}} (or {{file|/etc/ssh/ssh_config}}):

Host backupserver
ServerAliveInterval 10
ServerAliveCountMax 30

Here the client will close the connection if it 30 keepalive packets were not ack'ed (ie. 300s total).


Edit server configuation, {{file|/etc/ssh/sshd_config}}

ClientAliveInterval 10
ClientAliveCountMax 30

Here the server will close the connection if it 30 keepalive packets were not ack'ed (ie. 300s total).

With this configuration, any dead connection will close on either side after 300s, and corresponding locked resource would be freed. For instance, one could give a client process a 600s timeout to try to connect to remote, and acquire that remote resource.

=== Read password from environment with ssh-agent ===
* https://stackoverflow.com/questions/38354773/how-to-pass-an-ssh-key-passphrase-via-environment-variable

Say the password is in variable <code>SSH_PWD</code>. First we make an script that will echo that variable, {{file|echo-ssh-pwd}}:

<source lang="bash">
<source lang="bash">
#! /bin/bash
eval `ssh-agent-refresh.sh` >/dev/null

if ( ! ( ssh-add -L | grep -q $USER ) ); then ssh-add -t 3600; fi
echo "$SSH_PWD"
</source>
</source>


Then make it executable
Some security tip:
<source lang="bash">
* Define a maximum life time using option '''-t time'''.
chmod a+x echo-ssh-pwd
* Lock the agent with a password using option '''ssh-add -x'''.
</source>


Then in our ssh connection script:
Note that to overcome the one instance per user limitation, one would need to save the environment generated by <tt>ssh-agent</tt> in some file in home directory, and then source the proper file at next invocation.
<source lang="bash">
# DISPLAY setting mandatory, /dev/null redirect mandatory, setsid optional?
DISPLAY=":0.0" SSH_ASKPASS="echo-ssh-pwd" setsid ssh-add .ssh/id_ed25519 </dev/null
# ssh ...
</source>


== Troubleshooting ==
Other ideas found on internet:

* [http://www.ganaware.jp/archives/2006/04/winsshaskpass_1.html win-ssh-askpass] A GUI tool to do exactly the same as in Linux. Also provides <tt>win-ssh-askpass.exe</tt> that can be defined as executable for <tt>SSH_ASKPASS</tt> (see '''ssh-add''' man pages).
=== Slow SSH connection setup ===
* [http://www.webweavertech.com/ovidiu/weblog/archives/000326.html] Proposes to use a predefined <tt>SSH_AUTH_SOCK</tt> (defined in Windows environment), and saves the ssh-agent environment into a file, which can be sourced later on.
* '''DNS time-out on the server'''<br/>Whenever a new client connects, the server does a reverse-DNS look-up. If the client IP can't be reversed, the SSH connection is suspended until DNS request times out.<br/>{{green|'''FIX'''}}: Adds client IP and name in <tt>/etc/hosts</tt>
* Some more ideas [http://mah.everybody.org/docs/ssh here]

=== Missing Locale in Perl ===
When logging in from a client with different locale from the server, ''perl'' returns a warning about missing locale:
<source lang=text>
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_PAPER = "fr_BE.UTF-8",
...
LC_NAME = "fr_BE.UTF-8",
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
</source>
* This is fixed by telling ''sshd'' not to accept locale env var. from the client. Edit '''<tt>/etc/ssh/sshd_config</tt>''':
<source lang=diff>
- AcceptEnv LANG LC_*
+ AcceptEnv LANG
</source>

=== Weird fingerprint format (md5 vs sha256) ===
Since OpenSSH 6.8, the format of fingerprint switched from md5 to sha256 (in Base64).
From [http://superuser.com/questions/929566/sha256-ssh-fingerprint-given-by-the-client-but-only-md5-fingerprint-known-for-se superuser.com]:

;Force display of md5 fingerprint:
<source lang=bash>
ssh -o FingerprintHash=md5 example.org
</source>

;Get md5 or sha256 fingerprint
<source lang=bash>
ssh-keyscan example.org > key.pub # or find the keys on the server in /etc/ssh

# Get default ssh, depending on OpenSSH version
ssh-keygen -l -f key.pub (default hash, depending on OpenSSH version)
# Get md5 format
ssh-keygen -l -f key.pub -E md5
# Get sha256/base64 format
awk '{print $2}' ssh_host_rsa_key.pub | base64 -d | sha256sum -b | awk '{print $1}' | xxd -r -p | base64

</source>

=== ssh does not close connection when remote command exits ===
This is likely due to the remote command creating new processes that attach to stdin/stdout/stderr [https://askubuntu.com/questions/498539/ssh-remote-command-execute-stays-without-disconnect-when-finished-execute].

To fix:
* Redirect stdin/stdout/stderr
<source lang="bash">
ssh user@host "/script/to/run < /dev/null > /tmp/mylogfile 2>&1 &"
</source>
* Use <code>-t</code>
<source lang="bash">
ssh -t user@host "/script/to/run"
</source>

=== ssh saying <code>Connection to xxx closed</code> after remote command execution ===
<source lang="bash">
ssh -t user@host somecmd
# ...
# Connection to xxx closed.
</source>

The solution is simply to use <code>-q</code>
<source lang="bash">
ssh -qt user@host somecmd
# ...
</source>

Latest revision as of 21:05, 9 April 2023

Reference

On this Wiki:

Tips

On ssh command:

ssh -F hostname                             # Find hostname in ~/.ssh/known_hosts (useful if HashKnowHosts enabled)
ssh -l -f ~/.ssh/known_hosts                # Print fingerprint of known host keys
ssh -Lport:host:hostport hostname sleep 60  # Forward port, and exit after 60s if no connection is made
ssh -f -N -n -q -L ${BINDADDR}:port:host:hostport hostname # Idem, with 'go backg', 'no remote cmd', 'redir stdin from /dev/nul', 'quiet'

If a script is to be called as a ssh command, use 1>&2 instead of >/dev/stderr to redirect a message to stderr (if not, you'll get an error undefined /dev/stderr):

#! /bin/bash
# This script is called as an ssh command with
#   ssh hostname thisscript.sh
echo "This will go to stdin"
echo "This will go to stderr" 1>&2

Escape sequences

  • Type ~? after a newline to get a list of escape sequence
Sequence Effect
Return ~? List available sequence
Return ~. Terminate SSH session
Return ~~ Emit ~. Also useful to send the escape sequence to a 2nd (tunneled) SSH connection

Install

After installing ssh (client & server), you have to create an ssh-key:

ssh-keygen

Create a key

ssh-keygen -t ed25519 -f id_ed25519

Regenerate SSH host key

The easiest is to delete all keys and reconfigure openssh package:

rm /etc/ssh/ssh_host_*
/usr/sbin/dpkg-reconfigure openssh-server

Or manually:

ssh-keygen -t dsa -N "" -f /etc/ssh/ssh_host_dsa_key
ssh-keygen -t rsa -N "" -f /etc/ssh/ssh_host_rsa_key
ssh-keygen -t ecdsa -N "" -f /etc/ssh/ssh_host_ecdsa_key


Afterwards, you must delete the old host key from known_hosts file of any client connecting to that server:

ssh-keygen -R server_hostname

Configuration

SSH can be configured through file ~/.ssh/config. See man ssh_config for more information. The format is as follows:

# Specific configuration options for host host1
Host host1
  Option1     parameter
  Option2     parameter

# General configuration options for all hosts. 
# Options in this section applies if same option was *not already specified* in a relevant host section above.
Host *
  Option1     parameter
  Option2     parameter

The value to use for each option is given by the first section that matches the host specification and that provides a value for that option. So section Host * should always be at the end of the file, since any subsequent section will be ignored.

Usage

Remote Command Execution

SSH allows to execute any command on remote SSH host. The syntax is

ssh USER@HOST COMMAND ...

If COMMAND starts children processes, ssh only exits when all the children terminated. This is becaus these children connects to FD stdin/stdout/stderr and ssh has to keep them open. To avoid that uses -t and -q to avoid the "closed connection" message:

ssh -qt USER@HOST COMMAND ...

In addition, to avoid ssh to kill the children on exits, the job control must be enabled in the remote shell [1] (this is disabled when bash is started in non-interactive mode, which means that process parent and children are all in the same process group, which then receives the SIGHUP signal on exit). This is done with

set -m

in the remote script.

In case of compound commands, the semi-colon may be escaped with \:

ssh -qt USER@HOST COMMAND1\; COMMAND2...

Alternatively, use quotes:

ssh -qt USER@HOST "COMMAND1; COMMAND2..."

if ssh is used within a function / script, and you want to pass along some positional parameters to the remote script that might include spaces or funny characters, use ${*@Q} or printf " %q" "$@" to escape them:

#! /bin/bash

if [ -n "$SSH_HOST" ];
    exec ssh -qt $SSH_HOST "$0" ${*@Q}
fi

To execute a remote command on remote host and stay connected afterwards, use ssh -t, along with bash rcfile, like:

ssh -t SSH_HOST "bash --rcfile PATH_TO_RC_FILE"

Don't miss the quotes around the command. Bash will execute the commands in the rc file, and will open a session. Connection remains open because stdin/stdout is not closed. Option -t allows for connecting with current terminal. Without this option, there will be no terminal connection, so bash would run in batch mode (no prompt), and terminal features like tab completion or color would be missing.


Another solution is to force bash interactive mode:

ssh SSH_HOST "bash --rcfile PATH_TO_RC_FILE -i"

Since there is no terminal, bash goes by default in non-interactive mode. Interactive mode is forced with option -i, and so prompt will be printed, etc. But this is only a partial solution because there is still no terminal, ie. no color, no TAB auto-completion.

SSH Escape Sequence

Source: https://lonesysadmin.net/2011/11/08/ssh-escape-sequences-aka-kill-dead-ssh-sessions/

Press <return> ~, the following appears:

   Supported escape sequences:
    ~.   - terminate connection (and any multiplexed sessions)
    ~B   - send a BREAK to the remote system
    ~C   - open a command line
    ~R   - request rekey
    ~V/v - decrease/increase verbosity (LogLevel)
    ~^Z  - suspend ssh
    ~#   - list forwarded connections
    ~&   - background ssh (when waiting for connections to terminate)
    ~?   - this message
    ~~   - send the escape character by typing it twice
   (Note that escapes are only recognized immediately after newline.)

Useful ones:

  • <enter> ~. to exit a broken connection.
  • <enter> ~~ to send tilde as first char after newline.

Port Forwarding with SSH

Here two drawings that illustrate very clearly the mechanisms of port forwarding in SSH.

First the case of direct port forwarding, where a port is opened for listening on the (local) SSH Client, and forwarded to the given host and port on the remote side (i.e. accessible from SSH server).

Ssh port forwarding direct.png

Then the case of reverse port forwarding, where a port is opened for listening on the (remote) SSH Server, and forwarded to the given host and port locally (i.e. accessible from SSH client).

Ssh port forwarding reverse.png

Applications

SSHFS

SSH FileSystem using FUSE.

References:

Install

sudo apt-get install sshfs
sudo gpasswd -a $USER fuse

Mount / Unmount

mkdir ~/far_projects
sshfs -o idmap=user $USER@far:/projects ~/far_projects   
fusermount -u ~/far_projects

The idmap=user option ensures that files owned by the remote user are owned by the local user. This is to deal with situations where these users have different UID. This option does not translate UIDs for other users.

/etc/fstab

Line to add to /etc/fstab:

sshfs#$USER@far:/projects /home/$USER/far_projects fuse defaults,idmap=user 0 0

Tips and How-To

Controlling access

By editing .ssh/authorized_keys (see man authorized_keys) one can restrict the access a client has on the remote server.

port-forwarding connection only
  • Edit authorized_keys as follows (from Stack-Overflow [2]):
no-pty,no-X11-forwarding,permitopen="localhost:6379",command="/bin/echo do-not-send-commands" ssh-rsa rsa-public-key-code-goes-here keyuser@keyhost
  • Create the reverse port-forwarding with:
autossh -M 0 -f -T -N -n -q -R $PORT:localhost:22 noekeon

Reuse connection to boost performance

From http://www.debian-administration.org/article/290/Reusing_existing_OpenSSH_v4_connections:

Add the following to ~/.ssh/config:

Host *
  ControlPath /tmp/%r@%h:%p

Now we can connect as normal, so long as we make the first connection to any host with -M (for "Master") all subsequent connections will be much faster.

Or, force the -M for the first connection automatically with

Host *
  ControlMaster auto
  ControlPath /tmp/%r@%h:%p

This trick can be used to boost the performance of file transfer using scp (to make it similar to tar+ssh, see [3])

Connect through a Proxy

SSH can be said to establish the connection to the server through a proxy by using the option ProxyCommand. Example of ProxyCommand in the SSH config file:

# none
  ProxyCommand   none                                            # No proxy
# using nc
  ProxyCommand   /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p  # Example in ssh_config manpage but DOES NOT WORK
# using connect (aka connect-proxy)
  ProxyCommand   /usr/bin/connect %h %p                          # No proxy as well
  ProxyCommand   /usr/bin/connect -H proxyserver:8080 %h %p      # Using HTTP proxy proxyserver:8080
  ProxyCommand   /usr/bin/connect -h %h %p                       # Using HTTP proxy defined in env. var HTTP_PROXY
  ProxyCommand   /usr/bin/connect -S socks5server:1080 %h %p     # Using SOCKS5 proxy server socks5server:1080
  ProxyCommand   /usr/bin/connect -s %h %p                       # Using SOCKS5 proxy defined in env. var SOCKS5_SERVER
  ProxyCommand   /usr/bin/connect -S socks4server:1080 -4 %h %p  # Using SOCKS4 proxy server socks4server:1080
# Using socat
  ProxyCommand   socat -ly - PROXY:proxy:%h:%p,proxyport=8080,proxyauth=user:pass
                                                                 # HTTP Proxy
  ProxyCommand   socat -ly - SOCKS4A:socksserver:%h:%p,socksport=1080,socksuser=user
                                                                 # SOCKS4A proxy (%h is resolved by proxy)
# Using ssh-tunnel
  ProxyCommand   /usr/local/bin/ssh-tunnel.pl -f - - %h %p

Note that connect-proxy also supports NTLM authentication (see Proxy)

If a hostname matches several sections, first match found is used. Use ProxyCommand none to override a default proxy configuration:

Host 192.*
  ProxyCommand none                                              # Otherwise setting in Host * would be taken
Host *
  ProxyCommand /usr/local/bin/ssh-tunnel.pl -f - - %h %p         # Default proxy settings

Dealing with Proxy Time-Out using ssh-tunnel

In some case, the proxy might wait for the client (ie. local pc) to send an authentication string as it is the case in the SSL protocol. A solution for this is described in Yobi. It consists in sending immediately the client SSH banner, and strip it when it is sent by the client. The solution described uses a custom Perl script as ProxyCommand: ssh-tunnel.pl.

ssh-tunnel

On Ubuntu/Debian, first install the dependencies:

sudo apt-get install ssh libssl-dev libgetopt-long-descriptive-perl libmime-base64-urlsafe-perl libnet-ssleay-perl libio-socket-ssl-perl libauthen-ntlm-perl

Then install ssh-tunnel (if needed, create ~/bin first and don't forget to add ~/bin in the path in .bashrc before /usr/bin and /usr/local/bin):

tar -xvzf ssh-tunnel-2.26.tgz
make install
#Create an empty ssh banner (will be updated at the first connection)
touch ~/.ssh/clbanner.txt
#Create the links
ln -s /usr/local/bin/ssh.pl ~/bin/ssh

Edit ~/.ssh/config and ~/.ssh/proxy.conf as needed.

Manual Perl package installation

If the Perl dependency packages are not available, they can be built manually:

First install the packages:

  • ssh, libssl-dev (cygwin: openssh, openssl, openssl-devel)

Then fetch and build the Perl packages via CPAN (or consider using cpanminus):

sudo cpan
# on first run, autoconfig starts - see wiki for more details
# Also if proxy must be set, run:
#  o conf init urllist
#  o conf commit
reload cpan
install Getopt::Long
install MIME::Base64
install Net::SSLeay
install IO::Socket::SSL
install Authen::NTLM
Troubleshooting
  • Run in verbose debug mode:
ssh +v -v noekeon -- -d -d -d -d
If there are no debug message, make sure that ssh-tunnel is not started in quiet mode in .ssh/config:
   ProxyCommand        /usr/local/bin/ssh-tunnel.pl -q -f - - %h %p           # BAD - quiet mode
   ProxyCommand        /usr/local/bin/ssh-tunnel.pl -f - - %h %p              # OK
  • Check that the gateway address, first field, is correct in ./.ssh/proxy.conf. Get the gateway address with netstat -rn.

Creating a VPN over SSH

SSH allows to setup a full VPN connection over SSH, although it requires some manipulations, and is slower than regular VPN solutions using UDP packets (SSH uses TCP packets, which are slower).

References
  • References — with PermitRootLogin=no:
  • References — with PermitRootLogin=yes:
  • SSH_VPN (rules to make the process automatic)
  • More references

Preliminary Setup:

  • First on the server side, ssh daemon must allow creating of tunnels. For this setup however you don't need to enable PermitRootLogin. Edit /etc/ssh/sshd_config:
+PermitTunnel yes
  • Check if ip supports creation of tun/tap interfaces:
ip tuntap help
  • If not, install openVPN (or a recent version of tunctl):
sudo apt-get install openvpn
  • Unlike what is proposed in ssh manual, we are not going to use private addresses (10.*.*.*) but instead we are going to pick 1 address in the remote network that we hope is not used by any computer on the remote network. This is because it seems that packets with a private address range as origin are lost (no reply). Say we pick address 123.123.123.1 and 123.123.123.2
  • Also check that kernel on the client supports forwarding of packets, and that the firewall authorizes it as well ([5]):
sudo su
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -P FORWARD ACCEPT
iptables -F FORWARD

Setting up the VPN:

  • Log on the server, and create the tun0 interface with ip or openvpn, and configure the interface:
sudo ip tuntap add dev tun0 mode tun user $USER               # or 'sudo openvpn --mktun --dev tun0 --user $USER'
sudo ifconfig tun0 123.123.123.1 123.123.123.2 netmask 255.255.255.252  # Assign 123.123.123.2 as local IP addr on tun0 (and .1 as peer)
sudo route add -net 123.123.120.0/22 dev tun0                 # This will route all packets to 123.123.120.0/24 network through 
                                                              # ... our tun0 interface directly to the client.
                                                              # ... these packets will have 123.123.123.2 as source IP
  • On the client, create the tun0 interface, and setup the tunnel and route table:
sudo ip tuntap add dev tun0 mode tun user $USER               # or 'sudo openvpn --mktun --dev tun0 --user $USER'
sudo ifconfig tun0 123.123.123.2 123.123.123.1 netmask 255.255.255.252
sudo route add -host 123.123.123.2 dev tun0                   # Route all packets for 123.123.123.2 through tun0 itf
sudo arp  -Ds 123.123.123.2 eth0 pub                          # Tell all computers on eth0 that we'll take care of packets for IP 123.123.123.2
ssh -f -w 0:0 $SSHSERVER true                                 # SSH in background, tunnel from tun0:tun0

Troubleshooting

  • Setup tcpdump on 'tun0' on the client and server
sudo tcpdump -i tun0 -n icmp    # On the client and server
  • Then on the server, ping the client and verify that ICMP packets are sent back and forth:
ping 123.123.123.1            # peer on tun0 itf
  • Second, we will ping a computer on the remote netwrok from the server. On the client, setup tcpdump to now monitors packet on the 'eth0' interface:
sudo tcpdump -i etho0 -n icmp    # On the client
sudo tcpdump -i tun0 -n icmp    # On the server
  • Ping the remote computer, and observes that packet now also are forwarded by the client over the 'eth0' interface.
    If packets are sent, but none are received, likely it is because the ARP proxy is not setup correctly or is not published (pub keyword with arp). If no packets are sent or received, but packets are seen on the client tun0 interface, then probably packet forwarding is not enabled on the client kernel, or firewall forbids it.
ping 123.123.120.1            # On the server

Remarks:

  • SSH manpage creates tun device with different ID, but this is only needed if ssh'ing to localhost.
  • Use -w any:any to let ssh uses any available tun devices
  • Using -o Tunnel=Ethernet is mandatory for tap interfaces, or get a tunnel device open failed error.
  • Enabling PermitRootLogin offers more flexibility. Security impact can be greatly reduced by restricting root ssh access to execute a forced command (see [6] as an example)

Multiplexing SSH connection for faster connect

SSH may multiplex several connections on a single one to accelerate new connections. This uses the config parameters ControlMaster, ControlPath and ControlPersist in ~/.ssh/config.

See Multiplexing for details ([7], [8]).

Dynamic configuration using Match

Instead of using Host keyword to group ssh configuration in ~/.ssh/config, we can use Match to build more complex configuration, and among others configurations that depend on runtime settings.

For instance, assume we have a script on-acme-network that returns true if we are on some ACME network, detecting by testing the current network gateway:

#! /bin/bash
#
# Script on-acme-network

function get_gateway()
{
	if [ -x /sbin/ip ]; then
		/sbin/ip route | awk '/^default/ { print $3 }' | head -n 1
	else
		/bin/netstat -rn | perl -lne "print for /^0\.0\.0\.0 +([\.0-9]+) /g" | head -n 1
	fi
}

[ "10.10.10.10" = "$(get_gateway)" ]

Then we can create an SSH config that will connect through a proxy if we are in ACME network directly to our hosts, and connect directly otherwise:

# ~/.ssh/config

Host my_ssh_server
    User                some_user
    HostName            my_ssh_server.com

Host my_other_ssh
    User                some_user
    HostName            my_other_ssh.com

Host *
    ForwardX11          no
    ForwardX11Trusted   no
    ServerAliveInterval 15

Match exec on-acme-network
    ProxyCommand        connect-proxy -H proxy.acme.com:8080 %h %p

Note: another solution for dynamic configuration is to create config as a named pipe and make sure there is always a process writing to it, or use scriptfs [9].

Restrict ssh connection to a given command

This is done in authorized_keys. For instance, for borg:

command="/usr/local/bin/borg serve --restrict-to-path /home/borg/repo/",restrict ssh-ed25519 AA...

To restrict to port forwarding only:

# On the client
autossh -M 0 -f -N -n -q -L 9001:localhost:9001 -L 9002:localhost:9002 server
ssh -f -N -n -q -L 9001:localhost:9001 -L 9002:localhost:9002 server
# In authorized_keys
no-pty,no-X11-forwarding,permitopen="localhost:9002",permitopen="localhost:9002",command="/bin/echo do-not-send-commands" ssh-ed25519 AAAA...

Deal with unreliable connnection

sshd may not detect rapidly that remote client is no longer connected. As a result, whatever process launched on the server may still consume resources needlessly [10].

Edit client configuration ~/.ssh/config (or /etc/ssh/ssh_config):

   Host backupserver
           ServerAliveInterval 10
           ServerAliveCountMax 30

Here the client will close the connection if it 30 keepalive packets were not ack'ed (ie. 300s total).


Edit server configuation, /etc/ssh/sshd_config

   ClientAliveInterval 10
   ClientAliveCountMax 30

Here the server will close the connection if it 30 keepalive packets were not ack'ed (ie. 300s total).

With this configuration, any dead connection will close on either side after 300s, and corresponding locked resource would be freed. For instance, one could give a client process a 600s timeout to try to connect to remote, and acquire that remote resource.

Read password from environment with ssh-agent

Say the password is in variable SSH_PWD. First we make an script that will echo that variable, echo-ssh-pwd:

#! /bin/bash

echo "$SSH_PWD"

Then make it executable

chmod a+x echo-ssh-pwd

Then in our ssh connection script:

# DISPLAY setting mandatory, /dev/null redirect mandatory, setsid optional?
DISPLAY=":0.0" SSH_ASKPASS="echo-ssh-pwd" setsid ssh-add .ssh/id_ed25519 </dev/null
# ssh ...

Troubleshooting

Slow SSH connection setup

  • DNS time-out on the server
    Whenever a new client connects, the server does a reverse-DNS look-up. If the client IP can't be reversed, the SSH connection is suspended until DNS request times out.
    FIX: Adds client IP and name in /etc/hosts

Missing Locale in Perl

When logging in from a client with different locale from the server, perl returns a warning about missing locale:

perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
	LANGUAGE = (unset),
	LC_ALL = (unset),
	LC_PAPER = "fr_BE.UTF-8",
	...
	LC_NAME = "fr_BE.UTF-8",
	LANG = "en_US.UTF-8"
    are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
  • This is fixed by telling sshd not to accept locale env var. from the client. Edit /etc/ssh/sshd_config:
- AcceptEnv LANG LC_*
+ AcceptEnv LANG

Weird fingerprint format (md5 vs sha256)

Since OpenSSH 6.8, the format of fingerprint switched from md5 to sha256 (in Base64). From superuser.com:

Force display of md5 fingerprint
ssh -o FingerprintHash=md5 example.org
Get md5 or sha256 fingerprint
ssh-keyscan example.org > key.pub       # or find the keys on the server in /etc/ssh

# Get default ssh, depending on OpenSSH version
ssh-keygen -l -f key.pub (default hash, depending on OpenSSH version)
# Get md5 format
    ssh-keygen -l -f key.pub -E md5
# Get sha256/base64 format
    awk '{print $2}' ssh_host_rsa_key.pub | base64 -d | sha256sum -b | awk '{print $1}' | xxd -r -p | base64

ssh does not close connection when remote command exits

This is likely due to the remote command creating new processes that attach to stdin/stdout/stderr [11].

To fix:

  • Redirect stdin/stdout/stderr
ssh user@host "/script/to/run < /dev/null > /tmp/mylogfile 2>&1 &"
  • Use -t
ssh -t user@host "/script/to/run"

ssh saying Connection to xxx closed after remote command execution

ssh -t user@host somecmd
# ...
# Connection to xxx closed.

The solution is simply to use -q

ssh -qt user@host somecmd
# ...