Skip to content

Using Step-CA for SSH Certificates

Get Server Fingerprint

This is needed initially to point the step CLI to the running instance of step-ca.

step certificate fingerprint $(step path)/certs/root_ca.crt

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.

Get the CA for user SSH certificates
step ssh config --roots > /etc/ssh/ssh_user_ca.pub

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
Reload sshd
systemctl reload sshd

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
Generate keys if necessary
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""
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'
Reload the step-ca service
sudo kill -HUP "$(pidof step-ca)"
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)"

Client Certificate Issues

Check config files
sudo grep -r TrustedUserCAKeys /etc/ssh/
Check trusted CA for user SSH certs
cat $(sshd -T | grep -i trusteduserca | awk '{print $2}')
Check user SSH certificate valid until date
ssh-keygen -Lf ~/.ssh/id_ed25519-cert.pub | grep -i valid | awk '{print $5}'