adding ssh host signing
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# fleet-ssh-hostcert — SSH host certificate client
|
||||
|
||||
At boot, gets this host's **per-boot SSH host key** signed by the **fleet SSH CA**
|
||||
(step-ca) so administrators trust every fleet host via a single `@cert-authority`
|
||||
line in `known_hosts` — instead of trust-on-first-use (TOFU) on a key that changes
|
||||
every boot. Closes **F-02 / POA&M P-17**. Full design: `docs/ssh-host-certificate-design.md`.
|
||||
|
||||
## Why
|
||||
|
||||
The diskless root is a RAM overlay, so `sshd-keygen@` regenerates the SSH host
|
||||
keys **every boot** (see `scripts/extract-artifacts.sh`). Clients can therefore
|
||||
never pin a host key — every admin SSH connection is TOFU, and a LAN MITM is
|
||||
undetectable client-side. A CA-signed **host certificate** removes TOFU fleet-wide:
|
||||
the key can stay ephemeral; trust comes from the CA, not the key.
|
||||
|
||||
## How (Design A — per-boot key + boot-time cert)
|
||||
|
||||
1. Load the **TPM identity** (persistent handle `0x81000100` + leaf cert in NV
|
||||
`0x01800100`) and build an **mTLS client cert** from it — the same identity used
|
||||
for 802.1X, the NVMe hostnqn and agent mTLS. The private key never leaves the TPM.
|
||||
2. Read the fresh `/etc/ssh/ssh_host_ecdsa_key.pub`.
|
||||
3. `POST` it to the **enrol-broker host plane** (`/ssh/hostcert`) over mTLS. The
|
||||
broker verifies the client cert to the fleet CA (⇒ an enrolled host) and has
|
||||
step-ca sign an SSH **host** cert — the broker, not the host, holds the CA
|
||||
provisioner credential, so step-ca stays behind the single front door.
|
||||
4. Write `/etc/ssh/ssh_host_ecdsa_key-cert.pub` + a `HostCertificate` sshd drop-in
|
||||
(`60-hostcert.conf`), then `systemctl reload sshd`.
|
||||
|
||||
**Fail-open:** any failure (no network, broker/CA down, host not yet enrolled)
|
||||
exits non-zero **without touching sshd** — the host keeps serving its bare per-boot
|
||||
key (today's behaviour). It never installs a missing/broken `HostCertificate` that
|
||||
would stop sshd from starting. `ssh-hostcert.timer` retries (boot + hourly); a
|
||||
signed cert lands once the broker is reachable.
|
||||
|
||||
## Identity model / invariants
|
||||
|
||||
- **No per-host state in the image** (invariant #1): the broker DNS name in
|
||||
`ssh-hostcert.env` is fleet-generic; the host identity comes from the TPM at
|
||||
runtime. Principals default to the runtime hostname (never baked).
|
||||
- **Reuses the one TPM identity** ("one identity, N stages") as the mTLS client
|
||||
auth to the broker — the SSH host key itself is a separate ephemeral key, so this
|
||||
does not add cross-protocol signing exposure to the identity key (cf. F-06).
|
||||
|
||||
## Config
|
||||
|
||||
Fleet-generic knobs in `ssh-hostcert.env` (installed to `/etc/ssh-hostcert/`):
|
||||
`SSH_HOSTCERT_BROKER_URL` (the enrol-broker host-plane endpoint),
|
||||
`SSH_HOSTCERT_CACERT`, `SSH_HOSTCERT_HANDLE`/`_NVINDEX`, `SSH_HOSTCERT_PRINCIPALS`.
|
||||
All are also `-flags` for `fleet-ssh-hostcert`.
|
||||
|
||||
## Build / test
|
||||
|
||||
Pure Go (go-tpm, `CGO_ENABLED=0`), one static reproducible binary like
|
||||
`fleet-enrol`/`fleet-agent`; deps pinned by `go.sum`. Built **out of the VM** by
|
||||
CI and file-provisioned into the rootfs (Packer `2b-ssh-hostcert`, gated on
|
||||
`enable_ssh_hostcert`).
|
||||
|
||||
```
|
||||
cd files/ssh-hostcert
|
||||
CGO_ENABLED=0 go test ./...
|
||||
CGO_ENABLED=0 GOTOOLCHAIN=local go build -trimpath -buildvcs=false -ldflags '-s -w -buildid=' -o bin/fleet-ssh-hostcert .
|
||||
```
|
||||
|
||||
## Serving side (once per environment)
|
||||
|
||||
`serving/dot1x-bench/step-ca/ssh-ca-bootstrap.sh` enables step-ca's SSH CA, allows
|
||||
the `enrol` provisioner to issue SSH host certs, issues the broker host-plane
|
||||
server cert, and prints the admin `@cert-authority` line. The broker host plane is
|
||||
enabled by `BROKER_HOST_ADDR` (wired in the dot1x-bench compose). Several steps are
|
||||
**BENCH-VERIFY** against your step / step-ca version — notably the SSH host
|
||||
principal **authorisation policy**. See `serving/enrol-broker/` (host.go, sshsigner.go).
|
||||
|
||||
## Bench checklist (BENCH-VERIFY)
|
||||
|
||||
- Broker host plane: `step ssh certificate --host --sign …` (sshsigner.go) succeeds
|
||||
against the CA's SSH host CA; the exact flags are the tunable part of the contract.
|
||||
- Host principal policy: requested principals are authorised (tighten the broker to
|
||||
validate `principals` against the peer cert).
|
||||
- SELinux: the (root) service writing `/etc/ssh/*` and running `systemctl reload
|
||||
sshd` under confinement — may need a small local TE module (à la the chrony
|
||||
read module). Watch for AVCs.
|
||||
- Client: `ssh <host>` shows no host-key warning once `@cert-authority` is set.
|
||||
Reference in New Issue
Block a user