Using Step-CA for SSH Certificates
- If you’re not using SSH certificates you’re doing SSH wrong
- SSH certificate login advanced example: Run an SSH CA and connect to hosts using SSH certificates
- Notes on automated renewal
Get Server Fingerprint
This is needed initially to point the step CLI to the running instance of step-ca.
Install Step-CA CLI
The step CLI needs to be installed on pretty much everything - CA server, SSH host server, and SSH clients.
Install step-ca
apt-get update && apt-get install -y --no-install-recommends curl vim gpg ca-certificates
curl -fsSL https://packages.smallstep.com/keys/apt/repo-signing-key.gpg -o /etc/apt/trusted.gpg.d/smallstep.asc && \
echo 'deb [signed-by=/etc/apt/trusted.gpg.d/smallstep.asc] https://packages.smallstep.com/stable/debian debs main' \
| tee /etc/apt/sources.list.d/smallstep.list
apt-get update && apt-get -y install step-cli step-ca
After installation, the step CLI needs to be pointed at the step-ca server.
Point local step CLI at the step-ca instance
step ca bootstrap --ca-url janus.john-stream.com --fingerprint $FINGERPRINT
This needs to be done on both the client and the server, which both will need to use the step-ca CLI
Server Side
Get token for signing host cert
step ca token soteria.john-stream.com \
--ssh --host \
--password-file /etc/step-ca/password.txt \
--not-after 30m
Get step-ca to sign the host public key to produce the ssh host certificate.
Sign the host SSH certificate
export HOSTNAME=$(hostname -s) && \
step ssh certificate --host --sign \
--principal "$HOSTNAME" --principal "$HOSTNAME.john-stream.com" \
--provisioner admin \
"$HOSTNAME" /etc/ssh/ssh_host_ed25519_key.pub
The server also needs the public key for the CA that signs the user SSH certificates.
Configure the sshd to use the certs.
Configure sshd
cat <<EOF > /etc/ssh/sshd_config.d/certs.conf
TrustedUserCAKeys /etc/ssh/ssh_user_ca.pub
HostKey /etc/ssh/ssh_host_ed25519_key
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
EOF
Renewal
[email protected]
[Unit]
Description=Certificate renewer for %I
After=network-online.target
Documentation=https://smallstep.com/docs/step-ca/certificate-authority-server-production
StartLimitIntervalSec=0
; PartOf=cert-renewer.target
[Service]
Type=oneshot
User=root
Environment=STEPPATH=/etc/step-ca \
CERT_LOCATION=/etc/step/certs/%i.crt \
KEY_LOCATION=/etc/step/certs/%i.key
; ExecCondition checks if the certificate is ready for renewal,
; based on the exit status of the command.
; (In systemd <242, you can use ExecStartPre= here.)
ExecCondition=/usr/bin/step certificate needs-renewal ${CERT_LOCATION}
; ExecStart renews the certificate, if ExecStartPre was successful.
ExecStart=/usr/bin/step ca renew --force ${CERT_LOCATION} ${KEY_LOCATION}
; Try to reload or restart the systemd service that relies on this cert-renewer
; If the relying service doesn't exist, forge ahead.
; (In systemd <229, use `reload-or-try-restart` instead of `try-reload-or-restart`)
ExecStartPost=/usr/bin/env sh -c "! systemctl --quiet is-active %i.service || systemctl try-reload-or-restart %i"
[Install]
WantedBy=multi-user.target
[email protected]
[Unit]
Description=Timer for certificate renewal of %I
Documentation=https://smallstep.com/docs/step-ca/certificate-authority-server-production
; PartOf=cert-renewer.target
[Timer]
Persistent=true
; Run the timer unit every 15 minutes.
OnCalendar=*:1/15
; Always run the timer on time.
AccuracySec=1us
; Add jitter to prevent a "thundering hurd" of simultaneous certificate renewals.
RandomizedDelaySec=5m
[Install]
WantedBy=timers.target
Enable the timer for the john user
systemctl enable --now [email protected]
Check the status of the timer
systemctl status [email protected]
Client Side
Get token for signing host cert
step ca token john \
--ssh \
--password-file /etc/step-ca/password.txt \
--not-after 30m
Sign the user SSH certificate
step ssh certificate --sign \
--principal root --principal john \
--provisioner admin \
john ~/.ssh/id_ed25519.pub
Make the client trust servers with certs from the host CA
(umask 022; cat <<EOF > ~/.ssh/known_hosts
@cert-authority *.john-stream.com $(step ssh config --host --roots)
EOF
)
SSH client certs can't be renewed automatically...?
Debugging
Provisioner
Check the kid of the provisioner named admin
step ca provisioner list | jq -r '.[] | select(.name=="admin").key.kid'
Rotate the JWK provisioner on the server
export PROVISIONER_NAME=admin && \
step ca provisioner remove $PROVISIONER_NAME && \
step ca provisioner add $PROVISIONER_NAME --type jwk --create && \
sudo kill -HUP "$(pidof step-ca)"