How to Configure and Use Passwordless SSH for Infrastructure Automation on Ubuntu

In the previous post, we revisited SSH public key authentication protocol that brought passwordless SSH to life. We perused and summarized the RFCs. To visualize the authentication process, we drew the arrows that highlight the sequences of messages exchanged between the client and the server.

Not to get overwhelmed with theories, we set up a mini lab to demonstrate how a client should authenticate itself against a remote server with passwordless SSH. We ran the experiment in a cloud environment provided by Digital Ocean.

And probably you may ask this question, “Why running the experiment in the cloud?”

The very simple answer to this is “to emulate the situations when passwordless SSH shines”.

Let’s go into more details by reviewing the use cases.

Passwordless SSH Basic Use: One Client, One Server / Target Host

You have a laptop. The laptop may run on Windows or MacOS. You read a tutorial article about developing and deploying a simple API server with Node JS and ExpressJS on Ubuntu. You decided to give it a try.

After writing the NodeJS code, you are ready to deploy the code to an Ubuntu server, which you do not have but can easily rent from a cloud provider. You can choose from developer friendly provider like Digital Ocean, versatile yet affordable Alibaba Cloud, or more established names like AWS or Azure.

So you sign up for a cloud service and spin up a new Ubuntu machine to serve the API code.

Let’s draw the network diagram after the server is provisioned and running.

Figure 1: Network diagram for one client and one target host

As you can see in the diagram, the laptop is connected to the home network. The newly spun Ubuntu machine is located inside the data center managed by the cloud provider. We enable private network when creating the machine so practically the machine is accessible from the internet with its public IP address and from other nodes in the same data center through its private IP address.

The default authentication method for accessing a remote machine via SSH is password authentication. Each cloud service provider, however, may apply different policy for authentication. For example, default SSH authentication on AWS is public key authentication, which is the superset of passwordless SSH authentication. In contrast, if you are using Digital Ocean, you can configure to login as root user using password.

For the rest of the article, the commands are executed on servers provisioned at Digital Ocean. You can sign up through the link and get $100 credit so that you can also try by yourself.

You may need to do some adjustment to the steps mentioned in this article if you run the commands in different environment.

Server Provisioning for Basic Use

We launch an Ubuntu machine from Digital Ocean dashboard and name it as “master”. We create a user named “devops” that will be used for passwordless SSH authentication.

Step 1: Create the a new server instance

After logging into the dashboard, create a new server instance ( or droplet in Digital Ocean terminology) by clicking the “Create” button at the top and choose “Droplets”.

Figure 2: Create droplet button

Step 2: Configure the parameters for server launch

– From “Choose an image” section, choose Ubuntu as the image type.

Figure 3: Select Ubuntu OS from OS distro list

– From “Select additional options” section, check the “Private networking” option

Figure 4: Enable private networking

– From “Authentication” section, choose “One-time password” option

Figure 5: Select one time password

– From “Finalize and create” section, create one droplet and assign name for the host. We use “master” as the host name in this article.

Figure 6: Assign host name to the new droplet

Step 3: Create a user for passwordless SSH authentication

It is recommended to access the server as a non root user and disable root login afterwards. We achieve this by configuring the newly launched server as follows.

– Login as root to the client machine

$ ssh root@SERVER-PUBLIC-IP

– Create “devops” user for passwordless SSH

# adduser devops

– Add the user used into sudo group so that it can later execute commands with root privileges

# usermod -aG sudo devops

– Edit SSH server configuration file to disable remote root login and also to set up connection timeout (e.g. one minute)


# vi /etc/ssh/sshd_config
...
PermitRootLogin no

MaxAuthTries 5
...

– Restart the SSH daemon

# systemctl restart sshd

After restarting the SSH daemon, we can proceed with the public key setup on the client side.

Passwordless SSH basic setup

In brief, basic passwordless SSH setup goes through these steps:
1. Client creates key pair (public key and private key) for SSH public key authentication.
2. Client copies the public key to the server.
3. Client authenticates against the server using passwordless SSH.

Three simple steps that do not look too intimidating.

How do we translate those steps into commands in the terminal / shell?

As a gentle reminder, in this basic setup, the client is the laptop and the server is the Ubuntu machine.

Step 1: Create client key pair for SSH public key authentication

If you are on Windows, you can use PuTTYgen, either the standalone binary (puttygen.exe) or the one packaged with PuTTY installer, to generate the SSH key pair. However, the key generation with PuTTY is not covered in this article.

If you are using MacOS, you can simply use the built-in ssh-keygen command to generate the key pair:

$ ssh-keygen -t rsa -f ~/.ssh/id_rsa

When generating the key pair, you will be asked to provide a passphrase. The passphrase is used to encrypt the private key. If you set the passphrase, you will be prompted to type it when using public key authentication against a remote server. If you do not set a passphrase, you will be immediately authenticated and logged into the remote server without seeing the passphrase prompt.

For this kind of setup where the client for passwordless SSH authentication is a laptop, it is recommended to set up a passphrase when generating the key pair. The purpose is to add an extra layer of security especially in case of stolen laptop or undesired breach.

Step 2: Copy the public key to the server

We will copy the public key using inline remote command execution provided by the ssh client program.

From the ssh (client) manual on MacOS:

$ man ssh
SYNOPSIS
     ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface] [-b bind_address] [-c cipher_spec] [-D [bind_address:]port] [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]
         [-i identity_file] [-J destination] [-L address] [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address] [-S ctl_path]
         [-W host:port] [-w local_tun[:remote_tun]] destination [command]

The interesting part of an ssh command is the [command] section where the client can specify the remote command to be executed on the destination, which is the remote server.

Here is how we copy the public key to the server by utilizing the inline remote command execution.

$ PUBKEY=$(cat /home/LAPTOPUSER/.ssh/id_rsa.pub); ssh "devops@SERVER-PUBLIC-IP" "umask 0077; mkdir -p .ssh; echo $PUBKEY >> .ssh/authorized_keys"

The above script consists of two parts:

PUBKEY=$(cat /home/LAPTOPUSER/.ssh/id_rsa.pub)

In this first part we set a variable named PUBKEY that stores the buffered output of the client public key file (id_rsa.pub).

ssh "devops@SERVER-PUBLIC-IP" "umask 0077; mkdir -p .ssh; echo $PUBKEY >> .ssh/authorized_keys"

The second part is how we use SSH client program to execute remote command on the server according to ssh destination [command] syntax. The string devops@SERVER-PUBLIC-IP corresponds to destination and umask 0077; mkdir -p .ssh; echo $PUBKEY >> .ssh/authorized_keys represents [command].

The remote commands executed on the server to copy the client public key is further explained as follows.

umask 0077

We set the file creation mode mask to 0077.

There are four octal digits in umask. The first digit is the mask for special file permission, the second digit is the mask for user permission, the third one for group permission, and the last digit is the permission mask for everybody else.

File permission is represented by three octal digits. The first digit is for user permission, the second digit for group permission, and the last digit is the permission for everybody else.

Each digit in file permission constitutes the aggregate value of three permission types: “read” permission with octal value of 4, “write” permission with octal value of 2, and “execute” permission with octal value of 1. If we sum up the “read”, “write” and “execute” permissions, we will have a value from 0 to 7.

A file with permission 750 (or rwxr-x---) means that the user has read, write, and execute permission (the octal digit 7). Other user in the same group can read and execute (the octal digit 5). Conversely, everybody else does not have any permission to read, write, or execute the file (the octal digit 0).

When we set the mask on a permission digit, we mean to not give the permission for the corresponding class of user. So umask 0077 means that we will not give any “read”,”write”, or “execute” permission to other user in the same group and everybody else.

mkdir -p .ssh

We create a directory named .ssh for storing the client public key. Because we set the umask to 0077 earlier, this directory will have permission with numeric value of 700 (or rwx------).

echo $PUBKEY >> .ssh/authorized_keys

We create a file named authorized_keys and put the client public key into the file. The newly created file will have permission with numeric value of 600 (or rw-------). This is since newly created file has default permission value of 666 and we previously set umask to 0077.

If the authorized_keys file already exists, the public key will be appended to it and the file permission is left unchanged.

Step 3: Test the passwordless SSH connection

Now we go on with testing and verifying that we can login to the server with passwordless SSH. We execute this command on the laptop.

$ ssh devops@SERVER-PUBLIC-IP

And now we’re in.

Passwordless SSH Powerful Use: One Client, Multiple Target Hosts

You have deployed your first API server and somehow it becomes popular. So you need to provision another server and add a load balancer to split the traffic. After a while, your monolith API also becomes somewhat complex to manage. You decide to decompose the single service into a set of micro services. You deploy each service on a new server instance. What was initially only a simple server has now grown into tens of servers and counting.

Once the server count grows, it is time to rethink about the server infrastructure management. If you have, for example ten servers, performing same task on all ten servers may not be the best use of your time. You may think about automating the process of managing and performing maintenance on the servers.

There are some existing server infrastructure automation solutions, such as Ansible, Chef, or Puppet. Deciding which automation solution to invest in can be more nuanced than how it looks at the surface.

But as always, there is a recourse. Here is where passwordless SSH starts to shine.

You can use passwordless SSH to perform management tasks on all your servers at once.

Let’s draw the network diagram once again.

Figure 7: Network diagram for one client and multiple target hosts

As shown in the diagram, there are now multiple server instances in the data center. Even though we can connect to each server from the Internet, we set it up so that by connecting to one server, which is the master node, we can also manage other servers. We name each of the remaining servers as slave node.

Server management is performed by first enabling passwordless SSH from the master node to all of the slave nodes. After passwordless SSH is set up, practically we can execute remote commands on all slave nodes without seeing the password prompt. This is the first level of automation to the server infrastructure that we can achieve without incorporating additional tools.

Simplifying Public Key Export

We will run another experiment on Digital Ocean to show how we can simplify the process of copying / exporting public key.

This experiment involves four nodes. The first node is the client, which is the master node. The target hosts are similarly named as slave1, slave2, and slave3. Private networking is enabled for all nodes.

Here is the droplet list after all nodes are created.

Figure 8: Droplet list

Let’s go through all the steps to setting up passwordless SSH from master to all slave nodes.

Step 1: Login to each node (master, slave1, slave2, slave3) and create a new user with passwordless SSH

In this example, we name the user as devops.

– Login as root to each machine

$ ssh root@[MASTER|SLAVE1|SLAVE2|SLAVE3]-PUBLIC-IP

– Create devops user for passwordless SSH

# adduser devops

– Add the user used into sudo group so that it can later execute commands with root privileges

# usermod -aG sudo devops

– Edit SSH server configuration file to disable remote root login and also to set up connection timeout (e.g. one minute)

# vi /etc/ssh/sshd_config
...
PermitRootLogin no

MaxAuthTries 5
...

– Restart the SSH daemon

# systemctl restart sshd

Step 2: Obtain the private IPs of all nodes and add the IP info to /etc/hosts

– Get private IP for current node (For Digital Ocean, the private IP uses 10.x.x.x address)

# ifconfig -a | grep -e '10\.'

Sample output:

Figure 9: How to obtain private IP address

– Add private IPs of all nodes into /etc/hosts. Perform this on all nodes

# vi /etc/hosts

Sample output:

Figure 10: Update /etc/hosts with private IPs of all nodes

Step 3: Create SSH key pair on the client

The client in this example is the master node.

– Login to the master node as devops user

$ ssh devops@MASTER-PUBLIC-IP

– Create SSH key pair. Leave the passphrase empty when creating the key pair.

$ ssh-keygen -t rsa -f ~/.ssh/id_rsa

Sample output:

Figure 11: SSH key pair generation on master node

Step 4: Copy the public key to all nodes at once

This is the main part where we simplify the process of exporting / copying the public key. In Step 1 and Step 2, we have to log in to each server to perform the tasks. However, in this current step we execute the command to copy the public key to all nodes just once.

First, we define the target hosts where the public key will be copied. We also include the client itself.

$ TARGETS=("master" "slave1" "slave2" "slave3");

Next, we define the public key to be copied

$ PUBKEY=$(cat /home/devops/.ssh/id_rsa.pub);

Since there are several hosts, we need to loop through each host to copy the public key. We reuse the same command previously used in one-client-one-target-host setup.

$ for target in "${TARGETS[@]}"; do ssh "devops@$target" "umask 0077; mkdir -p .ssh; echo $PUBKEY >> .ssh/authorized_keys && echo 'Public key copied to' $target;"; done;

Finally, we put everything inline and execute the script for copying the public key.

$ TARGETS=("master" "slave1" "slave2" "slave3"); PUBKEY=$(cat /home/devops/.ssh/id_rsa.pub); for target in "${TARGETS[@]}"; do ssh "devops@$target" "umask 0077; mkdir -p .ssh; echo $PUBKEY >> .ssh/authorized_keys && echo 'Public key copied to' $target;"; done;

Sample output:

Figure 12: Inline script to copy public key to all nodes

Nicely done. Now we should be able to log in from master to all slave nodes with passwordless SSH. A quick test should confirm this.

$ ssh slave1

Sample output:

Figure 13: Passwordless SSH authentication from master to slave2

Step 4 (alternative): Use ssh-copy-id to copy the public key to all nodes at once

Because all nodes are running Ubuntu, we can use ssh-copy-id to copy the public key to all nodes. The ssh-copy-id program itself is a shell script that expands on the remote commands we use to copy the public key from the client to the target hosts.

The one line command to copy the public key with ssh-copy-id will be as follows.

$ TARGETS=("master" "slave1" "slave2" "slave3"); for target in "${TARGETS[@]}"; do ssh-copy-id "devops@$target" && echo 'Public key copied to' $target"; done;

The commands for exporting the public key are encapsulated in ssh-copy-id. As a result, the inline script now looks more concise.

Passwordless SSH for Server Infrastructure Automation

Passwordless SSH opens the door for further server infastructure automation. Since password is not needed to perform task remotely on the target host, we can create scripts that perform management or maintenance tasks on the servers without human interaction. The scripts can be scheduled to run periodically or at certain time, or triggered by certain event.

As an example, we can create a script that take the snapshot of user directory every day. Instead of setting cron job on all nodes, we simply create a script on the master node and set a cron job to run the script only on the master node. This simplifies the amount of time and effort needed to perform the task.

The minimal script for the example will look like as follows.

$ vi take_dir_snapshot.sh

#!/bin/bash

TARGETS=("master" "slave1" "slave2" "slave3");

for target in "${TARGETS[@]}"
do
ssh "devops@$target" "{ date; ls -Al; echo "";} >> snapshot.log"
done

If we execute the script manually from the master node, we will notice that the snapshot.log file is created on all nodes.

$ chmod u+x take_dir_snapshot.sh
$ ./take_dir_snapshot.sh

After adding a cronjob entry on the master node, the snapshot task for all nodes is now scheduled to run automatically everyday.

$ crontab -e
...
# Take directory snapshot of all nodes every 2AM in the morning
* 2 * * * /home/devops/take_dir_snapshot.sh
...

Conclusion

Passwordless SSH is more than a way to remove some keystrokes when logging in to the remote server. It can also be very usable to automate server administration. Proper infrastructure planning and understanding about the network layout can ease the passwordless SSH configuration and bring passwordless SSH to more effective use.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.