Problem
`bootroot-remote bootstrap` unconditionally requires non-empty EAB `kid`/`hmac` in OpenBao KV (src/bin/bootroot-remote/io.rs:197-198) and fails with `Missing required string key: kid` if either is absent. This was originally framed as a security policy — "remote-bootstrap crosses trust boundaries, so EAB is required as a second-factor authorization." On closer inspection that framing does not hold against the bundled topology.
The enforcement has no backing in the bundled OSS step-ca
Per Smallstep's own documentation, EAB is not supported in open-source step-ca — it is a commercial-only feature. bootroot bundles vanilla OSS step-ca (Dockerfile). So:
- The default ACME provisioner in `ca.json.ctmpl` has no `requireEAB: true`, and even if it did, the OSS server would not enforce it.
- bootroot-remote's EAB check is a bootroot-side-only gate. Whatever EAB credentials it validates (non-empty `kid`/`hmac`) are never presented to a CA that validates them.
- In my testing I worked around this by writing dummy values (`kid="dummy-kid", hmac="ZHVtbXk="`) directly into OpenBao KV. The subsequent ACME flow succeeded because step-ca never looked at the EAB. The "2nd-factor authorization" was never exercised.
A policy that can be satisfied by dummy values is not actually a policy. The current check adds friction for operators without providing the security guarantee it implies.
The real residual gap the policy was presumably trying to cover
Even if EAB worked, remote-bootstrap has a different genuine weakness: the HTTP-01 responder HMAC in OpenBao KV (`bootroot/responder/hmac`) is global across all services. Any service with a valid OpenBao AppRole can read the HMAC and then register challenges for any hostname. If one service's `secret_id` is compromised, the attacker can use that HMAC to register challenges for other services' hostnames and obtain certificates outside the compromised service's normal SAN.
EAB (if the CA supported it) would mitigate this by requiring a per-account binding the attacker also would need. But since the CA does not support it, the gap is real and uncovered.
Proposed fix
Remove the mandatory EAB enforcement
- Change `PulledSecrets::eab_kid` and `eab_hmac` in src/bin/bootroot-remote/io.rs from `String` to `Option` and read via a new `read_optional_string` helper.
- Only call `write_eab_file` when both `Some`. Otherwise the apply summary reports `"eab": "skipped"`.
- bootroot-agent already handles the EAB-absent case (omits the field from ACME `newAccount`) — no change needed on that side.
This aligns the remote-bootstrap path with how local-file already works: EAB is optional; if present, forwarded to ACME; if absent, the account is created without it.
Deliver a real per-service boundary via responder HMAC
Separate from EAB, consider giving each service its own HTTP-01 responder HMAC so that compromise of one service's OpenBao secret_id does not expose the ability to register challenges for other services. Sketch:
- `rotate responder-hmac` becomes per-service. OpenBao KV structure: `bootroot/services//responder_hmac` in addition to (or replacing) the global `bootroot/responder/hmac`.
- `bootroot-http01` responder validates the HMAC against the challenge's target FQDN and accepts only when the HMAC matches the service whose SAN the FQDN belongs to.
- bootroot-agent reads its service-specific HMAC from KV via the existing template pipeline.
This closes the actual boundary EAB was supposed to cover, without depending on a CA feature the bundled image does not have. Large-ish change; probably deserves its own design issue if accepted — noting here as the motivating rationale for dropping the fake-EAB gate.
Documentation
- Update the remote-bootstrap guide to state EAB is optional and bootroot does not claim to enforce a remote-specific authorization boundary beyond what OpenBao AppRole + responder HMAC already provide.
- Drop any language suggesting "EAB is required for remote-bootstrap" from `docs/ko/remote-bootstrap.md` / `docs/en/remote-bootstrap.md`.
Related
Environment
- bootroot at `5ff9993` (main tip, 2026-04-19)
- `bootroot-step-ca:0.29.0` (bundled vanilla OSS step-ca)
- ACME provisioner in secrets/templates/ca.json.ctmpl has no `requireEAB` set (and OSS step-ca would not honor it if added)
Problem
`bootroot-remote bootstrap` unconditionally requires non-empty EAB `kid`/`hmac` in OpenBao KV (src/bin/bootroot-remote/io.rs:197-198) and fails with `Missing required string key: kid` if either is absent. This was originally framed as a security policy — "remote-bootstrap crosses trust boundaries, so EAB is required as a second-factor authorization." On closer inspection that framing does not hold against the bundled topology.
The enforcement has no backing in the bundled OSS step-ca
Per Smallstep's own documentation, EAB is not supported in open-source step-ca — it is a commercial-only feature. bootroot bundles vanilla OSS step-ca (Dockerfile). So:
A policy that can be satisfied by dummy values is not actually a policy. The current check adds friction for operators without providing the security guarantee it implies.
The real residual gap the policy was presumably trying to cover
Even if EAB worked, remote-bootstrap has a different genuine weakness: the HTTP-01 responder HMAC in OpenBao KV (`bootroot/responder/hmac`) is global across all services. Any service with a valid OpenBao AppRole can read the HMAC and then register challenges for any hostname. If one service's `secret_id` is compromised, the attacker can use that HMAC to register challenges for other services' hostnames and obtain certificates outside the compromised service's normal SAN.
EAB (if the CA supported it) would mitigate this by requiring a per-account binding the attacker also would need. But since the CA does not support it, the gap is real and uncovered.
Proposed fix
Remove the mandatory EAB enforcement
This aligns the remote-bootstrap path with how local-file already works: EAB is optional; if present, forwarded to ACME; if absent, the account is created without it.
Deliver a real per-service boundary via responder HMAC
Separate from EAB, consider giving each service its own HTTP-01 responder HMAC so that compromise of one service's OpenBao secret_id does not expose the ability to register challenges for other services. Sketch:
This closes the actual boundary EAB was supposed to cover, without depending on a CA feature the bundled image does not have. Large-ish change; probably deserves its own design issue if accepted — noting here as the motivating rationale for dropping the fake-EAB gate.
Documentation
Related
Environment