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).
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/
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 [email protected]"
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
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 [email protected]
@cert-authority \*.domain1.com,\*.domain2.com $(cat /etc/ssh/hosts-ca.pub)
INNER
OUTER
# 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 [email protected]"
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
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.
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 [email protected]"
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,[email protected]" -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
@seanw2020 nice post! Could you share which tools you used to draw the diagram?