step-ssh: Automatically issue/renew SSH host certs

The `ssh-bootstrap` script, which is run by the *ssh-bootstrap.service*
systemd unit, requests SSH host certificates for each of the existing
SSH host keys.  The certificates are issued by the *POST /sshkeys/sign*
operation of *dch-webhooks* web service.

The *step-ssh-renew* timer/service runs `step ssh renew`, in a
container, on a weekly basis to renew the SSH host certificate.  A host
certificate must already exist, and its private key is used to
authenticate to the CA server.

Since `step ssh renew` can only operate on one certificate/key file at a
time, the `step-ssh-renew@.container` defines a template unit.  The
template instance specifies the key type (i.e. `rsa`, `ecdsa`, or
`ed25519`), which in turn defines which certificate and private key file
to use.  The timer unit activates a target unit, which depends on the
concrete service units.  Note that the target unit must have
`StopWhenUnneeded=yes` so that it can be restarted again the next time
the timer fires.
master
Dustin 2023-10-03 19:17:29 -05:00
parent 4048e5cc0a
commit 88f165363d
7 changed files with 117 additions and 0 deletions

13
ssh-bootstrap.service Normal file
View File

@ -0,0 +1,13 @@
# vim: set ft=systemd :
[Service]
Description=Bootstrap SSH host certificates
ConditionPathExistsGlob=!/etc/ssh/ssh_host_*_key-cert.pub
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/bin/sh /etc/ssh/bootstrap.sh
[Install]
WantedBy=multi-user.target

35
ssh-bootstrap.sh Normal file
View File

@ -0,0 +1,35 @@
#!/bin/sh
# vim: set sw=4 ts=4 sts=4 et :
gen_sshd_config() {
{
for x in ssh_host_*_key-cert.pub; do
printf 'HostCertificate /etc/ssh/%s\n' "${x}"
done
} > sshd_config.d/10-hostcertificate.conf
}
parse_response() {
jq -r '.certificates | to_entries | .[] | .key + " " + .value' \
| while read filename contents; do
[ -n "${filename}" ] || continue
echo "${contents}" > "${filename}" || return
done
}
request_sign() {
set -- \
https://bootstrap.pyrocufflink.blue/sshkeys/sign \
-H 'Accept: application/json' \
-F hostname=$(hostname -f)
for f in /etc/ssh/ssh_host_*_key.pub; do
set -- "$@" -F keys=@"${f}"
done
curl -fsSL "$@"
}
cd /etc/ssh || exit
request_sign | parse_response
gen_sshd_config
systemctl reload sshd

3
step-ssh-renew.env Normal file
View File

@ -0,0 +1,3 @@
STEP_CA_URL=https://ca.pyrocufflink.blue:32599
STEP_ROOT=/etc/pki/ca-trust/source/anchors/dch-root-ca.crt
STEP_PROVISIONER=sshpop

6
step-ssh-renew.target Normal file
View File

@ -0,0 +1,6 @@
[Unit]
Description=Renew SSH host certificates
StopWhenUnneeded=yes
Wants=step-ssh-renew@ed25519.service
Wants=step-ssh-renew@ecdsa.service
Wants=step-ssh-renew@rsa.service

11
step-ssh-renew.timer Normal file
View File

@ -0,0 +1,11 @@
[Unit]
Description=Periodically renew SSH host certificates
[Timer]
Unit=%N.target
OnCalendar=Tue *-*-* 00:00:00
RandomizedDelaySec=48h
Persistent=yes
[Install]
WantedBy=timers.target

20
step-ssh-renew@.container Normal file
View File

@ -0,0 +1,20 @@
[Unit]
Description=Renew SSH host %I certificate
After=network-online.target
Wants=network-online.target
ConditionPathExists=/etc/ssh/ssh_host_%I_key-cert.pub
[Container]
ContainerName=step-ssh-renew-%I
Image=docker.io/smallstep/step-cli:0.25.0
EnvironmentFile=/etc/sysconfig/step-ssh-renew
Exec=step ssh renew -f /etc/ssh/ssh_host_%I_key-cert.pub /etc/ssh/ssh_host_%I_key
Volume=/etc/ssh:/etc/ssh:rw
Volume=/etc/pki:/etc/pki:ro
# Required in order to be able to write to /etc/ssh
SecurityLabelDisable=true
User=0
Group=0
[Service]
Type=oneshot

29
step-ssh.yaml Normal file
View File

@ -0,0 +1,29 @@
variant: fcos
version: 1.4.0
storage:
files:
- path: /etc/ssh/bootstrap.sh
mode: 0755
contents:
local: ssh-bootstrap.sh
- path: /etc/containers/systemd/step-ssh-renew@.container
mode: 0644
contents:
local: step-ssh-renew@.container
- path: /etc/sysconfig/step-ssh-renew
mode: 0600
contents:
local: step-ssh-renew.env
- path: /etc/systemd/system/ssh-bootstrap.service
mode: 0644
contents:
local: ssh-bootstrap.service
- path: /etc/systemd/system/step-ssh-renew.target
mode: 0644
contents:
local: step-ssh-renew.target
- path: /etc/systemd/system/step-ssh-renew.timer
mode: 0644
contents:
local: step-ssh-renew.timer