Diataxis category: reference.
Day-2 security guidance for hosts running d2b's daemon / broker control plane.
d2b does not yet ship a general-purpose secret backend. The reserved
broker operations (ReadSecretById, InjectSecretById, RotateSecretById)
exist in the privileges matrix, but they are not the operator-facing secret
store for today's releases.
Treat these paths as the sensitive baseline instead:
| Surface | Why it matters | Access policy |
|---|---|---|
d2b.site.keysDir (default /var/lib/d2b/keys) |
Framework-managed SSH private keys. | Keep root-owned; launcher-group readability exists only so the CLI can copy the key to a caller-owned 0600 tempfile before ssh. |
/var/lib/d2b/vms/<vm>/host-keys/ |
Guest boot-time staging (host.pub, user-authorized-keys). |
Public keys + authorized-keys text only; never put private keys here. |
/var/lib/d2b/vms/<vm>/swtpm/ |
TPM NVRAM + EK seed. | Treat as identity-bearing state. Do not wipe casually; wiping forces guest re-enrollment with any external IdP. |
/etc/d2b/*.json and /var/lib/d2b/current-bundle/ |
Trusted bundle + daemon config. Usually integrity-sensitive rather than secret. | Root-owned, not world-readable, changed only by d2b's own emitters/install flow. |
/var/lib/d2b/audit/broker-<utc-date>.jsonl |
Security evidence for privileged operations. | Root-owned append-only writer; read through d2b audit or direct root access. |
Operational rules:
- keep real secrets out of
vms.json, examples, README snippets, and public issue reports; - keep
launcherUserssmall — that boundary is broad enough to matter; - prefer framework-managed keys unless you have a documented reason to own an
external
ssh.keyPathyourself.
The closed-world privileges matrix lives in privileges.json, but the operator
view is usually this simpler table:
| Actor | Socket / entry point | What they can do |
|---|---|---|
launcherUsers |
/run/d2b/public.sock via the CLI |
Day-to-day public CLI traffic: read-only verbs plus mutating verbs that dispatch through d2bd → broker (v1.0 daemon-only per ADR 0015). Treat membership as privileged host access. |
adminUsers |
/run/d2b/public.sock via the CLI |
Everything launchers can do, plus the admin-only surfaces: the audit export, and the destructive guest-control exec verb (vm exec) that runs arbitrary commands as the VM's workload user (ssh.user, never root) in a PAM login session over the authenticated transport (operators elevate with sudo inside the session). The daemon enforces the admin requirement with SO_PEERCRED at public.sock accept time, before any session work, and denies non-admin callers with an authz-not-admin error. |
d2bd |
/run/d2b/priv.sock |
The only direct broker client. The broker re-resolves paths from the trusted bundle and emits allow/deny audit rows for privileged operations. |
root |
host OS | Break-glass access outside the d2b control plane. |
Two consequences matter operationally:
- the real privilege split is public socket caller -> d2bd -> broker, not "human user talks to broker directly";
- destructive broker ops are audited and tagged as destructive in the private matrix, but the current public-socket human boundary is still the configured launcher/admin user set. Restrict those groups accordingly.
Start with the supported surface:
d2b audit --jsonFor direct on-host inspection, read the daily JSONL files:
sudo tail -n 50 /var/lib/d2b/audit/broker-$(date -u +%F).jsonlFields worth pivoting on during an incident:
operationpublic_operation_idauthz_resultdecisionerror_kindsubject_idscope_idbundle_hashtracing_span_id
Use journalctl for surrounding service context:
sudo journalctl -u d2bd.service --since "-1h"The broker defaults to 14-day retention. The d2b.site.audit.retentionDays
option is already declared, but on today's NixOS path its value may still lag
behind the runtime broker invocation wiring. If you need longer retention now,
ship the JSONL files off-host before prune-on-rotate does it for you.
Use this when a YubiKey or other allowlisted USBIP device is attached to the wrong VM/env, appears stuck after a crash, or needs immediate containment.
Prefer the public CLI first:
sudo d2b usb detach work-entra 1-3 --applyIf the guest still holds the device and you need the low-level
tools, use usbip port / usbip detach --port <N> on the guest
side and sudo usbip unbind -b 1-3 on the host.
In v1.0 (per ADR 0015) the per-env usbipd backend + proxy run as
broker-spawned runners on the per-env DAG under
d2b.slice/sys-<env>/usbipd-*. Stop them via the broker
SignalRunner op dispatched through the daemon — e.g. detach
the device through d2b usb detach <vm> --apply which the
broker translates into a SIGTERM on the per-env usbipd runner.
(The legacy d2b-sys-<env>-usbipd-{backend,proxy}.{service,socket}
systemd units were retired in v1.0; the equivalent operator action
no longer goes through systemctl stop.)
sudo rm -f /run/d2b/locks/usbip/1-3Do this only after the device is detached/unbound and the per-env units above are stopped; otherwise the next attach may race the old owner.
d2b usb probe --json
d2b audit --json
sudo journalctl -u d2bd.service --since "-15m"Confirm that the busid no longer shows an active owner, the lock file is gone, and the audit trail contains the detach/unbind you expected before re-enabling the proxy/backend units.
Pick the narrowest effective move first:
- remove the affected user from
launcherUsers/adminUsersand rebuild; - or stop
d2bdwhile you investigate; - or, on NixOS, stop the affected VMs (
d2b vm stop <vm> --apply) and stop/maskd2bd.serviceto halt reconciliation.
Copy or snapshot at least:
/var/lib/d2b/audit/- the trusted bundle (
/etc/d2b/*.jsonor/var/lib/d2b/current-bundle/) journalctl -u d2bd.service- the relevant VM state directories under
/var/lib/d2b/vms/<vm>/
Do not delete swtpm/ unless you have already decided to rotate the guest's
TPM identity and re-enroll it everywhere.
- Framework-managed SSH identity compromised: run
d2b keys rotate <vm>. - Consumer-supplied
ssh.keyPathcompromised: rotate that key out-of-band; d2b deliberately will not overwrite it. - Guest SSH host key changed or suspected compromised: run
d2b rotate-known-host <vm>and thend2b trust <vm>once the guest is back with its replacement host key. - Bundle/config tampering: re-render and re-land the trusted bundle, then restart the daemon.
- reinstall host artifacts if needed with
d2b host install --apply; - restart or switch affected VMs from a known-good generation;
- if you are fully unwinding the rollout, follow
../how-to/uninstall-d2b.md.
Use at least:
d2b auth status --json
d2b host check --strict
d2b audit --jsonThen confirm the recovered VM trust state (keys show, trust, status) for
any VM touched by the incident.