SSH, keys and agent

As you are certainly already aware, SSH is encrypted connection. But of course, it doesn't mean that this is secure by itself. There are some parameters to think about.
Also, via SSH and the key authentication, you can create a password-less connection to a remote server at no security cost, to ease the work of your batch processes.

In this tutorial, we are going to create a secure connection without being prompted by a password to let our scripts run from without anymore human intervention.
You want to connect to server from a machine called client.
This tutorial is based on Ubuntu 15.04 (Vivid Vervet).

Create the user(s) and their SSH key

On your system to be remotely managed, you will have to create the users used to connect to :
server:~$ sudo useradd -s /bin/bash -m -d /home/user1 -G sudo user1
server:~$ sudo passwd user1

This command will create the user user1 with bash as the default shell. The home directory of this user will be /home/user1 and it will be created if not already existing (switch -m). Also, this user will be made member of the group sudo, which on Ubuntu, by default, gives the right to use the sudo command to perform action as root with all commands of the system.
Finally we assign a password to this user. This password will be asked when running sudo.

Well, this is done using the first admin user created when installing Ubuntu. If you are doing the initial configuration at the console, you may have to manually install the SSH server :

server:~$ sudo apt-get install openssh-server

Now, create a SSH key-pair that we'll be used to connect to this server using SSH. The creation of the key-pair is done on the system from where you intent to log on. On client, we assume that you are using the user called ubuntu:

ubuntu@client:~$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/ubuntu/.ssh/id_rsa):
Created directory '/home/ubuntu/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/ubuntu/.ssh/id_rsa.
Your public key has been saved in /home/ubuntu/.ssh/id_rsa.pub.
The key fingerprint is:
97:5a:ca:74:ec:de:4a:df:da:ab:2a:44:c6:e3:08:d9 ubuntu@client
The key's randomart image is:
+---[RSA 2048]----+
|                 |
|                 |
|     o .         |
|    o E =. .     |
|     . =S.*      |
|      .ooB       |
|       .+ o      |
|        .o o o   |
|         .+o=o+. |
+-----------------+
ubuntu@client:~$

The above created the private key (id_rsa) and public key counterpart (id_rsa.pub) inside /home/ubuntu/.ssh directory.
You can change the name of the file that will hold the private and public key by using the -f switch followed by the name of the file for the private key. By default public key will be named by adding a suffix .pub to the filename given for the private key.
At the password prompt, if you simply hit ENTER, you will be able to use the key without being prompted by a password.
We will see later how to use ssh-agent to use keys with passwords in scripts.

Authorize your user on the target systems

Now, we need to authorize access to the remote system using the newly created SSH key.
For this, we will use the ssh-copy-id utility. This will copy the public key to the remote system inside the $HOME/.ssh/authorized_keys file. And set the correct permissions on this file.
ubuntu@client:~$ ssh-copy-id user1@server
The authenticity of host 'server (10.1.1.111)' can't be established.
ECDSA key fingerprint is 66:87:5b:8f:90:8e:24:9d:af:4c:16:8b:8e:78:50:5c.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s),
to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you 
are prompted now it is to install the new keys
user1@server's password:
 
Number of key(s) added: 1
 
Now try logging into the machine, with:   "ssh 'user1@server'"
and check to make sure that only the key(s) you wanted were added.
 
ubuntu@client:~$

At this stage we don't have yet any SSH key on the remote system so you will be prompted to enter the password of user1 to proceed (SSH must still be enabled to allow password login - see later the tweaking of the SSH server).
The action performed by ssh-copy-id is simply to put the content of the public key file inside a file called authorized_keys. The SSH server expects to find this file under the .ssh directory of any Linux user created and allowed to perform SSH connections.
If you don't use ssh-copy-id, here are the manual steps:

  1. You need to get the whole content of the public key file, usually, it is something of the form : ssh-rsa EAAAADAQABAAABAQDYiy6bv3n75MaKJPeZ5WI0wk2Pel9Z0h7YxCc8rtWjeipwDJ4VPb1qgXKmq8HMG5YTMvsxEvI ubuntu@client
  2. Log to your remote server as your target account (here user1 on server)
  3. Inside the home directory, create (if not yet existing) a directory called .ssh: mkdir .ssh
  4. Give it restrictive permissions, only accessible & writable by the owner: chmod 700 .ssh
  5. Edit the file authorized_keys inside .ssh and type the content you get from step 1. on one single line.
  6. Give the file restrictive permissions: chmod 600 authorized_keys

Now you can connect to this system by simply typing:

ubuntu@client:~$ ssh user1@server

And you won't be prompted anymore to enter a password.

Tweaking the SSH server configuration

Now that the SSH connection using your private key is working, it is time to have a look to the configuration of your SSH server and to do some optional tweaking, if you want to enforce more securty.
First of all, the location of this configuration is inside /etc/ssh/sshd_config.
We are thus editing this file using sudo and your preferred text editor and there we will focus on the parameters I describe after.

user1@server:~$ sudo vi /etc/ssh/sshd_config

The file, like many other configuration files, is structured the following way:

  • # at the begining of the line indicate a comment, a line that won't be interpreted by the SSH daemon
  • each line contain a parameter and its value just separated by space : Parameter Value
  • parameters with boolean value just take yes or no as value.

I want to put attention on the following parameters :

  • Port <value>: on which port does the SSH daemon listen. By default on port 22, but you can change it or you can add other ports too by adding more line Port <value>
  • ListenAddress <value>: in case you have more than one network interface on your system, you can use this parameter to listen only on one given interface by specifying its IP address. Use 0.0.0.0 to listen on all interfaces using IPv4.
  • Protocol <value>: which protocol version to use : use 2 to enforce only the more secure version 2 protocol and ignore connections made using the version 1 protocol
  • UsePrivilegeSeparation yes: by default
  • PermitRootLogin no: no direct login using root account will be allowed
  • X11Forwarding yes: if you want to launch graphical application on the remote system, using X11forwarding will draw the window on your client machine
  • TCPKeepalive yes: to send keepalive message to the clients so the connection is not dropped due to inactivity. Usefull for your scripts if you want to avoid losing connection and testing for its availability or if you let various terminal sessions open, hoping from one system to another one in different windows.
The default value of the other parameters are acceptable.

Tweaking the SSH client configuration

You can configure the way the SSH client behave by editing the central configuration file /etc/ssh/ssh_config or by creating a local configuration file in the .ssh directory in your home.
A common usage of the $HOME/.ssh/config file is to create a connection profile for each host with your preferred parameters so you don't have to type them anymore !
For example, the following config file:

Host nas
Hostname nas.mydomain.org
User admin
Port 9876

WIth this in my config file, I just need to type ssh nas to connect to the SSH daemon at Hostname, using User name and Port.
All the parameters that can be used in a SSH connection can be used there. Check man ssh_config for the complete list of parameters.
If you want a parameter to be applied to all connection you make, you can use wildcard:

Host *
ServerAliveInterval 30

This will apply the ServerAliveInterval parameter to all connection you make using SSH.
Note this parameter, it is very usefull because it causes the SSH client to send some data on the connection if no data was received from the server during the number of seconds specified as value of this parameter. This is usefull if you want to reuse an already opened connection after, for instance, leaving the terminal when it runs unattended for a long time.

Using the SSH agent daemon

If you want to secure your SSH keys with password but need to use them in script without human interaction, you'd better to have a look at the SSH agent.
When this SSH agent is running, you can load in it as many as SSH keys you want. Each time you add a new key, you are prompted for the password of it. When you launch the SSH client, it will fetch identities from the agent too, and try them to log in on the remote destination, without prompting anymore for the password of the private key.
When you kill the SSH agent, the private keys are taken out of the memory of your system.
A good start would be to have the SSH agent (ssh-agent) launch from a user profile script like .bashrc, .profile or .bash_profile.
In the .bashrc file of the user I use to initiate all SSH connection, I have the following line :
eval `ssh-agent`

It must be done like this via the eval command because once successfully launched, the SSH agent print on the standard output the commands to create and define 2 shell variables that will be required by further SSH commands to use the agent.
If you run ssh-agent, you will get on screen something like this :

SSH_AUTH_SOCK=/tmp/ssh-CUC0loYCJPvY/agent.9763; export SSH_AUTH_SOCK;
SSH_AGENT_PID=9764; export SSH_AGENT_PID;
echo Agent pid 9764;

Shell code to define 2 variables and their value.
ssh-agent will try to detect the style of your shell and will output the command in the syntax of your running shell.
You can override the detection mechanism by using the -c (C-Shell syntax-like) or -b (Bash shell syntax-like) command switch.
If the above two variables are not defined, the ssh-add and co. won't be able to use the agent.
To securely stop and remove the keys when you logout, it is best to have also the command to stop the agent in your logout script.
In my .bash_logout, the following line will stop the agent :

ssh-agent -k

Now that you can start and stop the agent, you need to load your key(s) in it. This is done with the ssh-add utility. Without any parameter, it loads the default identity ($HOME/.ssh/id_rsa or $HOME/.ssh/id_dsa). If you have keys with different filename, just pass the filename as parameter, without any switches.

JumpHost(admin):~$ ssh-add .ssh/test-key
Enter passphrase for .ssh/test-key:
Identity added: .ssh/test-key (.ssh/test-key)

You can also use ssh-add to list the identities (keys) present in the agent : ssh-add -l or ssh-add -L (the first shows the key fingerprint, the second the full public key).