Introduction
This guide covers how I set up and manage ssh keys on my home network. It’s not meant as an explainer on what ssh is or why you should use it, more a recipe for how I do things.
- Edit October 16 2020 - Added scripts to automate this setup
Who this is for
Mostly me so I remember how to do this if I have to rebuild my network, or want to add new clients. I’m not an expert in security, so definitely do your own research before implementing any of this, and I would certainly say that this is a hobbyist implementation in general and not suitable for an organization.
I got the inspiration for this from How to SSH properly. It’s a nice and thorough guide, but it’s a bit enterprise oriented for my purposes. The idea here will be to make something that’s more maintainable for a local setup.
The actual process
The idea is to have my certificate authority keys live on a USB drive. That way when I set up a new machine I can plug in the drive and sign the keys, and then the rest of the time the keys are completely removed from network access. As long as I’m smart and don’t lose/destroy the USB drive it seems like a great idea. The main advantage from just using regular keys rather than signing them is that I don’t have to add a line to authorized_keys
on every host every time I add a client, and I can keep a nice clean known_hosts
on each of my clients. For this example my host machine will be named mars
and the client machine will be named luna
. I’ll do the preliminary setup from luna
, but any machine should be fine.
Get the drive ready
I have a 64GB USB key that I’m going to store the CAs on. That’s way too big for what I need, and I wouldn’t mind having it handy to shuttle other files around (I’m not going to be taking it anywhere, on account of it has CAs for my network on it, but still). So I created a big exfat partition that took up most of the disk for files and such, along with a 200MB ntfs partition right at the end for storing keys. I went with exfat for the storage partition because it’s readable in both Windows and Linux (once you install exfat-utils
and exfat-fuse
), is designed for flash media, and can handle large files. I went with NTFS for the secrets partition because I have to have file permissions on the private keys or it won’t work, and windows machines won’t read EXT4. It does mean I’ll have to install NTFS support on any Linux boxes I want to run it from, which is a hassle, but I can live with that.
Generate CA keys
From the usb drive:
ssh-keygen -t ed25519 -f user_ca -C user_ca
ssh-keygen -t ed25519 -f host_ca -C host_ca
Generate a host and user signing key using ed25519 encryption. Generates public private key pairs named host/user_ca and host/user_ca.pub for the public keys. RSA is the default, but all of my systems support ed25519 and I understand it’s better in terms of security and performance so I might as well take this opportunity to update.
Generate known_hosts file
This will go in the ~/.ssh
folder of clients in order to validate access.
echo -n "@cert-authority * " > known_hosts
cat host_ca.pub >> known_hosts
Generate the host key and sign it
For any machines that I want to be able to ssh into I need to generate and sign a host key. From the same folder as the CA keys were generated:
ssh-keygen -t ed25519 -f mars_host_ed25519_key
ssh-keygen -s host_ca -I mars -h mars_host_ed25519_key.pub
WARNING: DO NOT PUT A PASSPHRASE ON HOST KEYS
The first line operates the same as before. The second line signs the public key. -I mars
is the certificate’s identity, apparently it can be used to revoke a certificate in the future although I’m not totally clear on how at this point. You can use the -n
flag to specify which hostname in particular this key is valid for but I don’t really see the need in as small a setup as I’m doing. -h
identifies this as a host key.
At the end of this, three new files are generated: mars_host_ed25519_key
, mars_host_ed25519_key-cert.pub
, and mars_host_ed25519_key.pub
.
Configure SSH to use host certificates
Move the three generated files from the last section and copy user_ca.pub
to /etc/ssh
on your host and set the permissions to match the other files there. In this example I’ve physically moved the key over to the host machine and mounted it. If your host currently has ssh enabled with password based authentication you could scp
it over instead:
sudo mv mars_host_ed25519_key* /etc/ssh/
sudo cp user_ca.pub /etc/ssh/
cd /etc/ssh/
sudo chown root:root mars_host*
sudo chown root:root user_ca.pub
sudo chmod 644 mars_host*
sudo chmod 600 mars_host_ed25519_key # stricter private key permissions
sudo chmod 644 user_ca.pub
Next, edit /etc/ssh/sshd_config
to have the lines
HostKey /etc/ssh/mars_host_ed25519_key
HostCertificate /etc/ssh/mars_host_ed25519_key-cert.pub
TrustedUserCAKeys /etc/ssh/user_ca.pub
There was a commented out line in the file for HostKey
so I put it below there. Placement shouldn’t really matter but it will hopefully be easier to find if I have to track this file down later.
sudo systemctl restart sshd
Once you’re sure everything is working you’ll want to disable password authentication. Be aware that if you screw up keys and have the second line set you’ll have to physically connect to the machine to resolve it. For my home network this is no big deal, but just be aware. Go into /etc/ssh/sshd_config
and set the following lines and restart ssh again:
PubkeyAuthentication yes
PasswordAuthentication no
Create user key
Still in the folder with the ca key:
ssh-keygen -f luna_ed25519 -t ed25519
ssh-keygen -s user_ca -I ian@luna -n admin,ansible,ipreston,pi luna_ed25519.pub
mv luna* ~/.ssh/
cp known_hosts ~/.ssh/
The only new flag in this is -n ipreston,admin,pi
which is the comma separated list of users I want this to be valid for. In addition to the username I set up on my server I want this key to be able to connect to my pfsense admin user and my raspberry pi, which I generally just leave with the default pi
user.
Give it a test drive
At this point if everything is configured correctly you should be able to ssh from your client to your host and only be prompted for the passkey you set (or without any prompting if you didn’t set a passkey)
ssh -i ~/.ssh/luna_ed25519 ipreston@mars
Set up an ssh config file
If that all worked the next step will be to set a nice alias to save some typing in the future.
touch ~/.ssh/config
chmod 600 ~/.ssh/config
In config
:
Host mars
User ipreston
IdentityFile ~/.ssh/luna_ed25519
There are lots of other options you can set in that config but that’s all I need for my setup. After that all you should need to type is ssh mars
to be connected to your host with the right user and using the correct authentication.
External servers
There’s no real sense in signing keys for external services. For example, I use GitHub with SSH, but they only allow CA authentication for enterprise (fair enough). There’s also no particular reason to re-use my LAN keys for GitHub, so I created a new key and set ~/.ssh/config
to use the correct identity automatically:
Use the same commands as the user key setup stage, except don’t bother signing the key. Add the public key to GitHub as you normally would (GitHub docs) and then edit ~/.ssh/config
to add something like the following:
Host github.com
IdentityFile ~/.ssh/luna_github_ed25519
HostName github.com
User git
Android
I don’t ssh a ton from Android, but every so often it’s handy to be able to do. My former go-to app was Connectbot, but I never really liked how it managed keys, and I couldn’t get it to work with the CA. I ended up going with termux. To set it up:
- Generate and sign keys as normal.
- copy the key pairs,
config
andknown_hosts
onto your android device - Install termux (it’s in the play store)
- Open termux
- Install openssh with
pkg install openssh
- Allow access to internal storage with
termux-setup-storage
and accepting the request - Navigate to where you copied the files (somewhere in
~/storage/shared
) and copy them to~/.ssh
usingcp
, it’s just regular bash in termux. - Everything should work, try connecting to a host.
Auto config scripts
These have been tested to set up the host and user authentication on Arch Linux. Presumably they’ll work on other distros as well. Android will still need to be done manually probably.
To set them up save them both in a directory, and then in a CA subfolder create user and host cert keys as well as a known_hosts file and config file as described above. In the config where you’d normally have the hostname associated with the key file just put HOST and the script will replace it with whatever your hostname is, which is also how it creates keys.
Set up host authentication
This script when run as root will generate a signed host key, move it to the correct directory, modify SSH configuration to authenticate using it and then restart ssh.
#!/usr/bin/env bash
# Bash "strict" mode, -o pipefail removed
SOURCED=false && [ "${0}" = "${BASH_SOURCE[0]}" ] || SOURCED=true
if ! $SOURCED; then
set -eEu
shopt -s extdebug
trap 's=$?; echo "$0: Error on line "$LINENO": $BASH_COMMAND"; exit $s' ERR
IFS=$'\n\t'
fi
# Text modifiers
Bold="\033[1m"
Reset="\033[0m"
# Colors
Red="\033[31m"
Green="\033[32m"
Yellow="\033[33m"
error_msg() {
T_COLS=$(tput cols)
echo -e "${Red}$1${Reset}\n" | fold -sw $((T_COLS - 1))
exit 1
}
check_root() {
echo "Checking root permissions..."
if [[ "$(id -u)" != "0" ]]; then
error_msg "ERROR! You must execute the script as the 'root' user."
fi
}
generate_and_sign_host_key() {
private_suffix="_host_ed25519_key"
private_key="$HOSTNAME$private_suffix"
public_key="$private_key.pub"
ssh-keygen -t ed25519 -f $private_key -N ""
chown root:root $private_key*
# Have to lock it down before signing
chmod 600 $private_key
ssh-keygen -s CA/host_ca -I $HOSTNAME -h $public_key
cp CA/user_ca.pub /etc/ssh/
chown root:root /etc/ssh/user_ca.pub
chmod 644 /etc/ssh/user_ca.pub
chmod 644 $private_key*
# Set back to stricter private key access
chmod 600 $private_key
mv $private_key* /etc/ssh/
}
update_sshd_config() {
private_suffix="_host_ed25519_key"
private_key="$HOSTNAME$private_suffix"
hostkey="HostKey /etc/ssh/$private_key"
cert="HostCertificate /etc/ssh/$private_key-cert.pub"
ca="TrustedUserCAKeys /etc/ssh/user_ca.pub"
sshd="/etc/ssh/sshd_config"
sed -i "/^#HostKey\s\/etc\/ssh\/ssh_host_ed25519_key/a $ca" $sshd
sed -i "/^#HostKey\s\/etc\/ssh\/ssh_host_ed25519_key/a $cert" $sshd
sed -i "/^#HostKey\s\/etc\/ssh\/ssh_host_ed25519_key/a $hostkey" $sshd
sed -i 's/^#PasswordAuthentication\syes/PasswordAuthentication no/g' $sshd
}
check_root
generate_and_sign_host_key
update_sshd_config
systemctl restart sshd
Setup user authentication
Whatever user your run this as should end up with a configured SSH setup that will allow access into any similarly configured hosts.
#!/usr/bin/env bash
# Bash "strict" mode, -o pipefail removed
SOURCED=false && [ "${0}" = "${BASH_SOURCE[0]}" ] || SOURCED=true
if ! $SOURCED; then
set -eEu
shopt -s extdebug
trap 's=$?; echo "$0: Error on line "$LINENO": $BASH_COMMAND"; exit $s' ERR
IFS=$'\n\t'
fi
# Text modifiers
Bold="\033[1m"
Reset="\033[0m"
# Colors
Red="\033[31m"
Green="\033[32m"
Yellow="\033[33m"
error_msg() {
T_COLS=$(tput cols)
echo -e "${Red}$1${Reset}\n" | fold -sw $((T_COLS - 1))
exit 1
}
check_root() {
echo "Checking root permissions..."
if [[ "$(id -u)" == "0" ]]; then
error_msg "ERROR! Don't run this as root."
fi
}
update_known_hosts() {
cp CA/known_hosts $HOME/.ssh/known_hosts
}
gen_keys() {
cp CA/user_ca .
chown $USER user_ca
chmod 600 user_ca
private_suffix="_ed25519"
private_key=$HOME/.ssh/$HOSTNAME$private_suffix
public_key="$private_key.pub"
ssh-keygen -f $private_key -t ed25519 -N ""
ssh-keygen -s user_ca -I $USER@$HOSTNAME -n admin,ipreston,cornucrapia,pi,ansible $public_key
rm user_ca
}
update_config() {
user_config="$HOME/.ssh/config"
cp CA/config $user_config
chown $USER $user_config
chmod 600 $user_config
sed -i -e "s/HOST/$HOSTNAME/g" $user_config
}
check_root
mkdir -p $HOME/.ssh
update_known_hosts
gen_keys
update_config
Conclusion
That’s basically it, whenever I add a new client or host I can connect the USB key, run setup_host.sh
as root and then setup_user.sh
as any users. All previously configured clients or hosts will automatically have the correct permissions.