Difference between revisions of "Secure Shell"

From Christoph's Personal Wiki
Jump to: navigation, search
(Making SSH even more secure)
 
(10 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
'''Secure Shell''' (or '''SSH''') is a set of standards and an associated network protocol that allows establishing a secure channel between a local and a remote computer. It uses public-key cryptography to authenticate the remote computer and (optionally) to allow the remote computer to authenticate the user.
 
'''Secure Shell''' (or '''SSH''') is a set of standards and an associated network protocol that allows establishing a secure channel between a local and a remote computer. It uses public-key cryptography to authenticate the remote computer and (optionally) to allow the remote computer to authenticate the user.
 +
 +
''Note: This article will only consider OpenSSH.''
  
 
== SSH without passwords ==
 
== SSH without passwords ==
 
* Step 1: Generate keys (public and private) and ''leave passphrase blank'' if you want password-less logins:
 
* Step 1: Generate keys (public and private) and ''leave passphrase blank'' if you want password-less logins:
 
  ssh-keygen
 
  ssh-keygen
  # ~Or~
+
  # ~OR~
 
  ssh-keygen -t dsa
 
  ssh-keygen -t dsa
  # ~Or~
+
  # ~OR~
  ssh-keygen -t dsa -b 2048 -f /home/bob/my-key
+
  ssh-keygen -t rsa -b 4096 -f /home/bob/my-key
  
 
* Step 2: Copy '''''public''''' key to remote server (Important: Only the ''public key''!):
 
* Step 2: Copy '''''public''''' key to remote server (Important: Only the ''public key''!):
Line 15: Line 17:
  
 
* Step 3: Set directory/file permissions (if not already set):
 
* Step 3: Set directory/file permissions (if not already set):
  chmod 700 ~/.ssh
+
  chmod 0700 ~/.ssh
  chmod 600 ~/.ssh/authorized_keys
+
  chmod 0600 ~/.ssh/authorized_keys
  
 
* Step 4: Now, SSH into your remote server (password will be required the first time):
 
* Step 4: Now, SSH into your remote server (password will be required the first time):
Line 46: Line 48:
 
* Now use that private key to log into your remote server (assuming, of course, that server has the matching key):
 
* Now use that private key to log into your remote server (assuming, of course, that server has the matching key):
 
  $ ssh -i /path/to/my_private_key.txt -l root <SERVER_IP>
 
  $ ssh -i /path/to/my_private_key.txt -l root <SERVER_IP>
 +
$ #~OR~
 +
$ ssh root@<SERVER_IP> -i /path/to/my_private_key.txt
 +
 +
* Get the private key's "fingerprint":
 +
$ ssh-keygen -lf /path/to/my_private_key.txt
 +
2048 f6:a0:8c:99:ba:c2:31:36:1c:f2:5d:c5:da:37:27:b7  bob@hostname (RSA)
 +
 +
* Create a "signature":
 +
$ echo -n 'this is my signature' |openssl sha1 -binary |\
 +
    openssl pkeyutl -sign -inkey my_private_key.txt -pkeyopt digest:sha1 > signature
 +
 +
==Converting and verifying OpenSSH public keys==
 +
 +
* First, generate a public/private key:
 +
$ ssh-keygen -t rsa -b 2048 -f /home/bob/my-key
 +
 +
* Extract public key from private key:
 +
$ openssl rsa -in my-key -pubout
 +
 +
* Note the difference between the above and the default public key <code>`ssh-keygen`</code> provides (i.e., the "<code>my-key.pub</code>" file):
 +
$ cat /home/bob/my-key.pub
 +
 +
* Or, get your public key in PEM format (only works with OpenSSH v5.6+):
 +
$ ssh-keygen -f my-key.pub -e -m pem
 +
 +
* Check the integrity of your public key:
 +
$ sed -e 's/ssh-rsa //' ~/.ssh/id_rsa.pub|awk '{print substr($1,1,76)}'|openssl base64 -d|hexdump
 +
 +
00000000  00 00 00 07 73 73 68 2d  72 73 61 00 00 00 03 01  |....ssh-rsa.....|
 +
00000010  00 01 00 00 01 01 00 a3  f3 03 a0 8b 08 df 93 ac  |................|
 +
00000020  34 19 6c 19 1b 1a b5 b7  bf 43 0e 41 2f be 33 9a  |4.l......C.A/.3.|
 +
00000030  3f 15 c0 91 8c 27 09 ba  c5                      |?....'...|
 +
00000039
 +
 +
The above reads as such:
 +
 +
00 00 00 07            The length in bytes of the next field
 +
73 73 68 2d 72 73 61    The key type (ASCII encoding of "ssh-rsa")
 +
00 00 00 03            The length in bytes of the public exponent
 +
01 00 01                The public exponent (usually 65537, as here)
 +
00 00 01 01            The length in bytes of the modulus (here, 257)
 +
00 a3 f3...            The modulus
 +
 +
So the key has type RSA, and its modulus has length 257 bytes, except that the first byte has value "00", so the real length is 256 bytes (that first byte was added so that the value is considered positive, because the internal encoding rules call for signed integers, the first bit defining the sign). 256 bytes is 2048 bits.
  
 
==SSH config file==
 
==SSH config file==
Line 86: Line 132:
 
  iptables -A INPUT -i eth0 -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
 
  iptables -A INPUT -i eth0 -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
 
  iptables -A INPUT -i eth0 -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 8 --rttl --name SSH -j DROP
 
  iptables -A INPUT -i eth0 -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 8 --rttl --name SSH -j DROP
 +
 +
===Miscellaneous===
 +
 +
User rights must follow the principle of least privilege. The restriction can be applied to a number of parameters: accessible commands or originating IP addresses. For example, in the authorized_keys file, you can authorize only connections from a specific network (for a given key):
 +
from="192.168.1.*" ssh-ed25519 AAAA...
 +
So, for this key, only connections from the <code>192.168.1.0/24</code> network will be accepted.
  
 
==Supported escape sequences==
 
==Supported escape sequences==
Line 98: Line 150:
 
  ~?  - this message
 
  ~?  - this message
 
  ~~  - send the escape character by typing it twice
 
  ~~  - send the escape character by typing it twice
 +
 +
==Miscellaneous==
 +
 +
* Create a custom SSH prompt:
 +
$ echo 'Defaults passprompt="LAUNCH CODE: "' | sudo tee -a /etc/sudoers.d/launch_code
 +
 +
; Proxy Jump
 +
 +
* SSH into a backend host (using its private IP) via a bastion host:
 +
$ ssh -i ~/.ssh/backend.pem ubuntu@<backend-private-ip> \
 +
      -o ProxyCommand="ssh -i ~/.ssh/bastion.pem -o ForwardAgent=yes -W %h:%p ubuntu@<bastion-public-ip>"
 +
#~OR~
 +
$ ssh -i ~/.ssh/backend.pem ubuntu@<backend-private-ip> \
 +
      -o ProxyCommand="ssh -i ~/.ssh/bastion.pem -o ForwardAgent=yes ubuntu@<bastion-public-ip> 'nc %h %p'"
 +
 +
Another way to do the above is to use the proxy jump (<code>-J</code>) option:
 +
$ ssh-add ~/.ssh/backend.pem
 +
$ ssh-add ~/.ssh/bastion.pem
 +
$ ssh-add -L
 +
$ ssh -v -J ubuntu@<bastion-public-ip> ubuntu@<backend-private-ip>
 +
 +
Here we tell SSH to connect to the target host by first making a ssh connection to the jump host (aka "bastion") described by destination and then establishing a TCP forwarding to the ultimate destination from there.  Multiple jump hops may be specified, separated by comma characters. This is a shortcut to specify a <code>ProxyJump</code> configuration directive.
 +
 +
Finally, you can add something like the following to your <code>~/.ssh/config</code> file:
 +
<pre>
 +
Host bastion
 +
    HostName <bastion-public-ip>
 +
    User ubuntu
 +
    IdentityFile ~/.ssh/bastion.pem
 +
    IdentitiesOnly yes
 +
    ProxyCommand none
 +
    TCPKeepAlive yes
 +
    ServerAliveInterval 5
 +
 +
Host backend
 +
    User ubuntu
 +
    HostName <backend-private-ip>
 +
    IdentityFile ~/.ssh/backend.pem
 +
    ProxyCommand ssh bastion nc %h %p
 +
</pre>
 +
 +
With the above, you can now SSH "directly" to the backend (really proxying via the bastion host) with:
 +
$ ssh backend
 +
 +
* Run a local [[Bash]] script on a list of remote hosts:
 +
<pre>
 +
$ for i in $(seq 102 104); do
 +
    ssh root@128.14.163.${i} "bash -s" < ./install_docker.sh;
 +
  done
 +
</pre>
  
 
==Todo==
 
==Todo==
Line 103: Line 205:
 
  ssh -NfL 3690:127.0.0.1:3690 USER@64.3.10.24 -p6111
 
  ssh -NfL 3690:127.0.0.1:3690 USER@64.3.10.24 -p6111
 
Then you can access the repository via
 
Then you can access the repository via
  svn://127.0.0.1/YOUR-SVN-PATH
+
  <nowiki>svn://127.0.0.1/YOUR-SVN-PATH</nowiki>
  
 
*Secure web traffic when traveling
 
*Secure web traffic when traveling

Latest revision as of 18:24, 6 February 2024

Secure Shell (or SSH) is a set of standards and an associated network protocol that allows establishing a secure channel between a local and a remote computer. It uses public-key cryptography to authenticate the remote computer and (optionally) to allow the remote computer to authenticate the user.

Note: This article will only consider OpenSSH.

SSH without passwords

  • Step 1: Generate keys (public and private) and leave passphrase blank if you want password-less logins:
ssh-keygen
# ~OR~
ssh-keygen -t dsa
# ~OR~
ssh-keygen -t rsa -b 4096 -f /home/bob/my-key
  • Step 2: Copy public key to remote server (Important: Only the public key!):
scp ~/.ssh/id_dsa.pub username@remote-host:.ssh/authorized_keys
# ~OR~
ssh-copy-id -i ~/.ssh/id_rsa.pub username@remote-host
  • Step 3: Set directory/file permissions (if not already set):
chmod 0700 ~/.ssh
chmod 0600 ~/.ssh/authorized_keys
  • Step 4: Now, SSH into your remote server (password will be required the first time):
ssh username@remote-host

That's it! You are now free to log into your remote server without entering a password. This is useful for automating file transfers. However, it must be used with care. If not executed properly, it is a potential security risk.

Using SSH private keys

For illustration purposes, I will generate a pseudo-key by generating 512 random characters to give you an idea of what a RSA private key should look like (note: You should never really create a private key less than 2048 bits):

$ echo "-----BEGIN RSA PRIVATE KEY-----" && openssl rand -base64 512 && echo -e "-----END RSA PRIVATE KEY-----\n"
-----BEGIN RSA PRIVATE KEY-----
AgaLRL9vUvHb736UVEavYIgpDJywdAvy+Y8/PGnS2aXbr1JzRXsvmoufcYpdJev+
9E2XigSgoEuP3eDH4lRCtYRVuSqN7jUVJT26KBQbC34qw72mrfcVoW5H442l2oGF
oOcWTcRz0F4R0LKbCecx7tGgzAW/XOVocmcC4CsEIrA+hmUkk9sXO/VD7eV6dP5D
d3k3bqoDI4VEkhpavKSTRnoDBrl33tiz43vyiQUegPjZVkg+jOI7fyZL2hElQea2
o+KjEFfr4a1ZJs/58XitoCcHb7vaFX4PGNDuveBchFKmeWROuMxHalBVbV/sZVr4
bJYfNHTHHr4rNjQdf5cO9wnzIhC1hsutxZWPEj9JF3X+BVtAgKVS9Zbkh9BxSJJG
cLWrmyqM7gRhE96ibHF6hGJ7jj0cf/pK8e8NVIVzD1jwvXAT7FeJHkKltoAKQ7LQ
bC4d0b27jOccLpR6C4SU6zhSyWBnsoawiMfYR7HsEmLlOZW6fycrukFzi5wm/zpK
r4YVIrzWHJzJbP+CIVvLUp8hv13OO3ozQo3tCNofpESV2/vYOGStDQtF9GVq53rS
DWn2NAzT6X1IFtJlxQxG0CNsnNBAAZoOA3lgEPQqPzdoqKA/deS64oBH8j8CUSUp
DQgaIxzVF1/2bKO3JoHKLaeui4vFIH7KT8ITS/FKoD8=
-----END RSA PRIVATE KEY-----
  • Save your private key to a file (let's call it "my_private_key.txt") and:
$ chmod 600 my_private_key.txt
  • Now use that private key to log into your remote server (assuming, of course, that server has the matching key):
$ ssh -i /path/to/my_private_key.txt -l root <SERVER_IP>
$ #~OR~
$ ssh root@<SERVER_IP> -i /path/to/my_private_key.txt
  • Get the private key's "fingerprint":
$ ssh-keygen -lf /path/to/my_private_key.txt
2048 f6:a0:8c:99:ba:c2:31:36:1c:f2:5d:c5:da:37:27:b7  bob@hostname (RSA)
  • Create a "signature":
$ echo -n 'this is my signature' |openssl sha1 -binary |\
    openssl pkeyutl -sign -inkey my_private_key.txt -pkeyopt digest:sha1 > signature

Converting and verifying OpenSSH public keys

  • First, generate a public/private key:
$ ssh-keygen -t rsa -b 2048 -f /home/bob/my-key
  • Extract public key from private key:
$ openssl rsa -in my-key -pubout
  • Note the difference between the above and the default public key `ssh-keygen` provides (i.e., the "my-key.pub" file):
$ cat /home/bob/my-key.pub
  • Or, get your public key in PEM format (only works with OpenSSH v5.6+):
$ ssh-keygen -f my-key.pub -e -m pem
  • Check the integrity of your public key:
$ sed -e 's/ssh-rsa //' ~/.ssh/id_rsa.pub|awk '{print substr($1,1,76)}'|openssl base64 -d|hexdump

00000000  00 00 00 07 73 73 68 2d  72 73 61 00 00 00 03 01  |....ssh-rsa.....|
00000010  00 01 00 00 01 01 00 a3  f3 03 a0 8b 08 df 93 ac  |................|
00000020  34 19 6c 19 1b 1a b5 b7  bf 43 0e 41 2f be 33 9a  |4.l......C.A/.3.|
00000030  3f 15 c0 91 8c 27 09 ba  c5                       |?....'...|
00000039

The above reads as such:

00 00 00 07             The length in bytes of the next field
73 73 68 2d 72 73 61    The key type (ASCII encoding of "ssh-rsa")
00 00 00 03             The length in bytes of the public exponent
01 00 01                The public exponent (usually 65537, as here)
00 00 01 01             The length in bytes of the modulus (here, 257)
00 a3 f3...             The modulus

So the key has type RSA, and its modulus has length 257 bytes, except that the first byte has value "00", so the real length is 256 bytes (that first byte was added so that the value is considered positive, because the internal encoding rules call for signed integers, the first bit defining the sign). 256 bytes is 2048 bits.

SSH config file

Note: See the ssh_config (5) man page for details.

  • Edit your SSH config file (~/.ssh/config) and add the following (example) lines:
# contents of $HOME/.ssh/config
Host dev
    HostName dev.example.com
    Port 22321
    User bob

Host github
    IdentityFile ~/.ssh/github.key

Now you can simply type:

ssh dev

to SSH into that dev.example.com remote host.

See: for more examples.

Making SSH even more secure

Note: All of the following settings will be implemented in your /etc/ssh/sshd_config file.

  • Disable SSH protocol 1. Make sure no lines reads Protocol 1. If so, change it to:
Protocol 2
  • Enable key-based logins (see above for how to do this):
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
  • Disable password-based logins (Only do this if you first enable key-based logins!):
PasswordAuthentication no
  • Run on ports other than 22
Port 1717  # any free port above 1024

You will then need to point to this port when SSHing into your remote machine

ssh -p 1717 remote.machine
  • Disable root logins (Very important!):
PermitRootLogin no

Disable / deny brute force attacks

The following iptables rules should deny almost all brute force attacks on your firewall's port 22 (SSH port):

iptables -A INPUT -i eth0 -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
iptables -A INPUT -i eth0 -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 8 --rttl --name SSH -j DROP

Miscellaneous

User rights must follow the principle of least privilege. The restriction can be applied to a number of parameters: accessible commands or originating IP addresses. For example, in the authorized_keys file, you can authorize only connections from a specific network (for a given key):

from="192.168.1.*" ssh-ed25519 AAAA...

So, for this key, only connections from the 192.168.1.0/24 network will be accepted.

Supported escape sequences

Note: The following escapes are only recognized immediately after newline.

~.  - terminate connection (and any multiplexed sessions)
~B  - send a BREAK to the remote system
~C  - open a command line
~R  - Request rekey (SSH protocol 2 only)
~^Z - suspend ssh
~#  - list forwarded connections
~&  - background ssh (when waiting for connections to terminate)
~?  - this message
~~  - send the escape character by typing it twice

Miscellaneous

  • Create a custom SSH prompt:
$ echo 'Defaults passprompt="LAUNCH CODE: "' | sudo tee -a /etc/sudoers.d/launch_code
Proxy Jump
  • SSH into a backend host (using its private IP) via a bastion host:
$ ssh -i ~/.ssh/backend.pem ubuntu@<backend-private-ip> \
      -o ProxyCommand="ssh -i ~/.ssh/bastion.pem -o ForwardAgent=yes -W %h:%p ubuntu@<bastion-public-ip>"
#~OR~
$ ssh -i ~/.ssh/backend.pem ubuntu@<backend-private-ip> \
      -o ProxyCommand="ssh -i ~/.ssh/bastion.pem -o ForwardAgent=yes ubuntu@<bastion-public-ip> 'nc %h %p'"

Another way to do the above is to use the proxy jump (-J) option:

$ ssh-add ~/.ssh/backend.pem
$ ssh-add ~/.ssh/bastion.pem
$ ssh-add -L
$ ssh -v -J ubuntu@<bastion-public-ip> ubuntu@<backend-private-ip>

Here we tell SSH to connect to the target host by first making a ssh connection to the jump host (aka "bastion") described by destination and then establishing a TCP forwarding to the ultimate destination from there. Multiple jump hops may be specified, separated by comma characters. This is a shortcut to specify a ProxyJump configuration directive.

Finally, you can add something like the following to your ~/.ssh/config file:

Host bastion
    HostName <bastion-public-ip>
    User ubuntu
    IdentityFile ~/.ssh/bastion.pem
    IdentitiesOnly yes
    ProxyCommand none
    TCPKeepAlive yes
    ServerAliveInterval 5

Host backend
    User ubuntu
    HostName <backend-private-ip>
    IdentityFile ~/.ssh/backend.pem
    ProxyCommand ssh bastion nc %h %p

With the above, you can now SSH "directly" to the backend (really proxying via the bastion host) with:

$ ssh backend
  • Run a local Bash script on a list of remote hosts:
$ for i in $(seq 102 104); do
    ssh root@128.14.163.${i} "bash -s" < ./install_docker.sh;
  done

Todo

  • Access your local subversion repository from the road
ssh -NfL 3690:127.0.0.1:3690 USER@64.3.10.24 -p6111

Then you can access the repository via

svn://127.0.0.1/YOUR-SVN-PATH
  • Secure web traffic when traveling
ssh -D 9999 -p6111 USER@64.3.10.24

then go to Firefox's Preferences->Advanced->Network->Settings->Manual proxy settings with:

SOCKS Host: 127.0.0.1  Port: 9999
No proxy for: localhost, 127.0.0.1

See also

External links