Skip to content

Instantly share code, notes, and snippets.

@seanw2020
Last active February 19, 2024 03:15
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save seanw2020/924c50e4c8428ad2d030db99cc819e20 to your computer and use it in GitHub Desktop.
Save seanw2020/924c50e4c8428ad2d030db99cc819e20 to your computer and use it in GitHub Desktop.
How to configure and visualize an SSH CA

How to configure an SSH CA, with visuals

ssh certificates

SSH CA

For details, see "Mastering SSH", Second Edition, Chapter 14: Certificate Authorities. Also see 'man ssh-keygen': "ssh-keygen supports signing of keys to produce certificates that may be used for user or host authentication. Certificates consist of a public key, some identity information, zero or more principal (user or host) names and a set of options that are signed by a Certification Authority (CA) key. Clients or servers may then trust only the CA key and verify its signature on a certificate rather than trusting many user/host keys. Note that OpenSSH certificates are a different, and much simpler, format to the X.509 certificates used in ssl(8).

Create two CAs

Choose a computer to act as the CA. We'll call this the CA computer (not CA host, since "host" is overloaded here). In this example, only root can sign keys, thus sudo.

# Log into the CA computer.
ssh you@ca

# Prepare
sudo mkdir -p /usr/local/sshca/{users,hosts}
sudo chmod 1771 /usr/local/sshca        # sticky bit (first 1) prevents non-owners from renaming files. # 11 allows cat of .pub files
sudo chmod 774 /usr/local/sshca/{users,hosts} 
sudo chgrp -R $(id -g -n) /usr/local/sshca # recursively change group to your group

# Create two CAs: 1 to sign hosts and 1 to sign users. These are openssh, not openssl, CAs
sudo ssh-keygen -t rsa -b 4096 -C "SSH CA for hosts created September 25, 2019 by John Smith" -f /usr/local/sshca/hosts-ca
sudo ssh-keygen -t rsa -b 4096 -C "SSH CA for users created September 25, 2019 by John Smith" -f /usr/local/sshca/users-ca

# Protect them
sudo chmod 400 /usr/local/sshca/{hosts-ca,users-ca}         # only allow the owner to read these
sudo chmod 444 /usr/local/sshca/{users-ca.pub,hosts-ca.pub}

# Upload the users CA to the server1. This overwrites the destination!
scp /usr/local/sshca/users-ca.pub root@server1:/etc/ssh/

# Copy the hosts CA's pub key. This overwrites the destination!
scp /usr/local/sshca/hosts-ca.pub root@client1:/etc/ssh/

SSH Server (sshd)

Configure the ssh server to trust the users CA, and thus any certs it signs. We'll use root to modify /etc/ssh.

# Log into the CA computer.
ssh you@ca

# Update server1's sshd_config
ssh root@server1 bash <<\EOF
comment="# Added by configuration-manager@foo.com"
keyword_pair="TrustedUserCAKeys /etc/ssh/users-ca.pub" 
path="/etc/ssh/sshd_config"

# If not present in sshd_config, append the keyword pair
grep -q -F "${keyword_pair}" "${path}" || printf "\n%s\n%s" "${comment}" "${keyword_pair}" >> "${path}"

# Make readable
chmod 444 "${path}"

# Restart sshd. This does not disconnect existing connections.
sudo /etc/init.d/ssh restart  # if you have upstart or system v
sudo systemd restart sshd     # if you have systemd
EOF

SSH Client (ssh)

Configure the ssh client to trust the hosts CA, and thus any certs it signs. We'll use root to modify /etc/ssh.

# Log into the CA computer.
ssh you@ca

# Create or update client1's /etc/ssh/ssh_known_hosts. By default, it doesn't exist.
ssh root@client1 bash <<\OUTER
# set permissions
chmod 774 /etc/ssh/ssh_known_hosts
chmod 774 /etc/ssh/hosts-ca.pub

# Allow client1 to connect to servers from these domains
tee -a /etc/ssh/ssh_known_hosts <<INNER
# Added by configuration-manager@foo.com
@cert-authority \*.domain1.com,\*.domain2.com $(cat /etc/ssh/hosts-ca.pub)
INNER
OUTER

Sign an ssh server's public keys using hosts-ca

# Log into the CA computer.
ssh you@ca

# For clarity, create a directory to hold the ssh server's public keys
mkdir -p /usr/local/sshca/hosts/server1

# Download the public certs
# Alternatively, ssh-keyscan works but would allow man-in-the-middle.
scp root@server1:/etc/ssh/ssh_host_\{rsa_key.pub,dsa_key.pub,ecdsa_key.pub,ed25519_key.pub\} /usr/local/sshca/hosts/server1/

# Sign all of server1's public keys, thus creating certificates with extension .pub
sudo ssh-keygen -s /usr/local/sshca/hosts-ca -I "i am server1" -h -n server1,server1.foo.com,192.168.0.1 -V +56w5d /usr/local/sshca/hosts/server1/ssh_host_*.pub

# Copy all server1's newly signed certs back to server1
scp /usr/local/sshca/hosts/server1/ssh_host_*-cert.pub root@server1:/etc/ssh/

# Add the signed certs to sshd_config
ssh root@server1 bash <<\EOF
comment="# Added by configuration-manager@foo.com"
path="/etc/ssh/sshd_config"

# Only add the keyword pair if it's not already there
for part in rsa dsa ecdsa ed25519; do
  pubpath="/etc/ssh/ssh_host_${part}_key-cert.pub"
  keyword_pair="HostCertificate ${pubpath}" 
  if [ -f "${pubpath}" ]; then
    grep -q -F "${keyword_pair}" "${path}" || printf "\n%s\n%s" "${comment}" "${keyword_pair}" >> "${path}"
  fi
done

# Restart sshd. This does not disconnect existing connections.
sudo /etc/init.d/ssh restart  # if you have upstart or system v
sudo systemd restart sshd     # if you have systemd
EOF

Sign an ssh client's public key using users-ca

This assumes the user already has a keypair: id_rsa and id_rsa.pub.

# Log into the CA computer.
ssh you@ca

# For clarity, create a directory to hold the ssh user's public keys
mkdir -p /usr/local/sshca/users/user1

# Download the user's public cert
scp user1@client1:/home/user1/.ssh/id_rsa.pub /usr/local/sshca/users/user1/

# Sign the user's public key, thus creating a certificate named id_rsa-cert.pub
# Note: the user mentioned in the -n option must exist on the ssh server and allow ssh connections
sudo ssh-keygen -s /usr/local/sshca/users-ca -I "user1@client1" -n "user1" -V +52w \
  /usr/local/sshca/users/user1/id_rsa.pub

# Optionally investigate the cert's details:
ssh-keygen -Lf /usr/local/sshca/users/user1/id_rsa-cert.pub

# Copy the signed certificate back to the user's .ssh directory
scp /usr/local/sshca/users/user1/id_rsa-cert.pub user1@client1:/home/user1/.ssh/

# Test from the ssh client.
ssh user1@client1
ssh -vvv server1 # You should NOT be prompted. Troubleshoot otherwise. Use ssh-agent for any priv key passwords.

Single-purpose Keys

This allows you to create a certificate whose public key can only perform one task, like running /usr/local/scripts/myscript.sh. The intention here is for user1 to run sudo /usr/local/bin/myscript.sh.

# Log into client1
ssh user1@client1

# Create a new keypair
ssh-keygen -t rsa -b 4096 -C "user1@client1: single purpose key: sudo myscript.sh" -f /home/user1/.ssh/id_rsa-myscript

# Try this key when connecting
tee -a /home/user1/.ssh/config <<\EOF
# Added by configuration-manager@foo.com"
IdentityFile ~/.ssh/id_rsa-myscript
EOF

# Set required permissions. Details in man ssh_config:
chmod 700 /home/user1/.ssh/config

# Log into CA
ssh user1@client1

# Set up a directory
mkdir -p /usr/local/sshca/users/user1

# Download the pub key
scp user1@client1:/home/user1/.ssh/id_rsa-myscript.pub /usr/local/sshca/users/user1/

# Sign it
# NOTES: permit-pty helps troubleshooting (you can see error output) and avoids "PTY allocation request failed on channel 0".
#      : For details, see ForceCommand and permit-pty in man sshd_config
#      : -n means approved (for login) principals. They are expected to exist on server1 (or configure AuthorizedPrincipalsFile)
sudo ssh-keygen -s /usr/local/sshca/users-ca -I "signed: user1@client1: single purpose key: myscript.sh" \
  -n "user1,user1@foo.com" -V +52w -O clear -O permit-pty -O force-command="/usr/local/bin/myscript.sh" \
  /usr/local/sshca/users/user1/id_rsa-myscript.pub

# Optionally inspect it
ssh-keygen -Lf /usr/local/sshca/users/user1/id_rsa-myscript-cert.pub

# Upload to client1
scp /usr/local/sshca/users/user1/id_rsa-myscript-cert.pub user1@client1:/home/user1/.ssh/


# Test it
ssh user1@client1
ssh server1
@imzjy
Copy link

imzjy commented Oct 10, 2022

@seanw2020 nice post! Could you share which tools you used to draw the diagram?

@s1ryx
Copy link

s1ryx commented Nov 28, 2023

out of all the guides that exist, this one (especially cause of the graphic) was the only one actually making me able to implement it. thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment