Replies: 2 comments
Decisions (operator, 2026-11-07)All eleven are approved as recommended:
|
0 replies
Decisions, 2026-11-08: sandbox-lane postureThe operator changed the sandbox-lane posture on 2026-11-08. The plan records these as decisions 12 to 20.
Lanes that need a boot-time unlock keep the Tier 2 design. |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Status: Draft, for the project's RFC discussion category
Related issues: #864, #12, #396
Related RFC: the ESO Kubernetes-auth RFC (#851)
Related decisions: ADR-0009, ADR-0011, ADR-0028, ADR-0029, ADR-0050
Each repository citation reads
dmfdeploy/dmfdeploy@1d47e09:<path>:<lines>,where
1d47e09is the umbrellaorigin/mainrevision I read. Appendix Alists the external sources and what I read in each. Appendix B lists what I
could not confirm.
Summary
OpenBao starts sealed after every restart. A sealed OpenBao serves no
secrets, and nothing that needs them can start. In the current deployment,
unlocking needs the unlock key material to be reachable from the operator's
own machine, which fails when that machine or its route to the cluster is
missing.
This RFC proposes the Tier 2 flow that ADR-0029 accepts but defers. One admin,
who may not be technical, unlocks OpenBao after a cold boot without physical
access to the node. The admin opens an unlock page and confirms on a phone
with the phone's own sign-in check. The unlock key material is not stored on
the node.
This RFC covers the sandbox and open-source lane only. The goal is the most
security that is achievable without adding friction for admins. In this lane,
physical and local root access to the node is a known, accepted risk. Tier 2
does not try to stop an attacker who already has root on the node. What it
does give is that no unlock material rests on the node, so a stolen disk, a
stolen box or a one-time root login cannot unseal OpenBao without the
admin's phone. Once OpenBao is unsealed, root on the node can read secrets
in memory, and that risk stays documented.
The RFC describes three flows as properties that must hold: an online site
with a laptop and a QR code, an online site with a notification link on the
phone, and an offline portable kit. It describes an optional private mesh
layer. It states the properties for adding and removing devices, for audit,
for a periodic admin check-in and for the work after an unseal. Lanes with legal or regulatory obligations
are out of scope. It puts eleven choices to you as
decisions, each with a recommendation.
Background
ADR-0029 defines three tiers and calls Tier 2, "Operator unseal", the
recommended default
(
dmfdeploy/dmfdeploy@1d47e09:docs/decisions/0029-tiered-unseal-posture.md:19-29).It marks the tiers as a direction, not a control. Every tier assumes a
high-availability OpenBao with Raft storage, and that baseline is not built
(same file, line 15). The live posture is the single-replica auto-unseal of
ADR-0011
(
dmfdeploy/dmfdeploy@1d47e09:docs/decisions/0011-auto-unseal-tradeoff.md:16-18).ADR-0009 keeps the five-share recovery path as the disaster layer for every
tier
(
dmfdeploy/dmfdeploy@1d47e09:docs/decisions/0009-shamir-dr-model.md:23).ADR-0029 also fixes four guardrails for any implementation
(
dmfdeploy/dmfdeploy@1d47e09:docs/decisions/0029-tiered-unseal-posture.md:31-36):a one-time token for each cold boot, protection against a silent downgrade
to a lower tier, rate limiting with an audit record for every submission,
and no extra weaker factor on top of a passkey. The design plan adds the
unlock page and the passkey design
(
dmfdeploy/dmfdeploy@1d47e09:docs/plans/dmf-unseal-posture-tiered-framework-plan.md:157-200, 285-353).That plan is marked historical, so this RFC treats it as input.
Goals and non-goals
Goals:
access to the node.
can write the record.
matters in this lane.
Non-goals:
push-notification server. Those belong in the plan.
Threat model
This RFC covers the sandbox and open-source lane. In this lane, physical and
local root access to the node is a known, accepted risk, because stopping it
would add friction for every admin and the lane's data does not justify
that.
What Tier 2 still gives:
unlock material at rest, not even in wrapped form (see property 3).
domain only (Appendix A).
Accepted:
and the code that receives the shares run on the node, so such an attacker
can change them and capture a later unlock. Stopping that needs a trusted
client and an end-to-end boundary that root cannot change. That is a much
larger design with real friction, and it is out of scope.
per-device passphrase mixed into the wrapping key would narrow this
(
dmfdeploy/dmfdeploy@1d47e09:docs/plans/dmf-unseal-posture-tiered-framework-plan.md:246).It adds a step for every unlock, so the RFC does not require it.
Design properties
This section states what must hold. It does not prescribe an implementation.
Properties that hold in every flow
other service. It does not depend on the console, on External Secrets
Operator or on a console sign-in, because those need secrets that are
still sealed.
published on the internet.
wrapped unlock shares or keys are stored on a Tier 2 node. That covers
disks, backups, swap, crash dumps and logs. Unlock material exists on the
node only in memory, for the length of one unlock. Off-node custody is
one wrapped copy on each enrolled phone and the ADR-0009 recovery
package. The WebAuthn PRF extension gives a passkey the output that wraps
the copy (Appendix A).
the unwrapped material or any token. It records whether a token was valid.
can write it. See the audit property below.
tier to be bound in git-tracked config, checked cryptographically at boot,
and changed downward only through a signed, audited change
(
dmfdeploy/dmfdeploy@1d47e09:docs/decisions/0029-tiered-unseal-posture.md:34).This RFC keeps that requirement as written.
ADR-0029 says the automation file is deleted for Tiers 1 and 2
(
dmfdeploy/dmfdeploy@1d47e09:docs/decisions/0029-tiered-unseal-posture.md:56).Declaring Tier 2 while the file still exists would declare a posture that
differs from the real one. The migration also re-checks the ADR-0009
five-share distribution.
relying party ID, which must be the page's domain or a parent of it, and
never an IP address (Appendix A). The page needs a stable host name, and
the passkey is enrolled for it.
Properties of the unlock session
The same session shape serves every flow:
QR code that carries a session URL. In Flows B and C it gets there through
a notification link or a saved page.
material locally, and submits it to the session. No other device sees key
material. In particular the laptop in Flow A only shows the QR code.
short time, it accepts one submission, and it cannot be replayed. Only a
session that completed a passkey assertion for an enrolled credential, and
that holds the wrapped copy, may submit.
assertion and the wrapped copy that only the phone holds.
user-verification result. It does not prove a specific physical device, and
passkeys that sync between devices are allowed. What gates the unlock is
possession of the wrapped copy that lives on the phone.
Device lifecycle: enrollment, removal and transfer
The properties below follow the steady-state design in the #864 discussion.
sign-in. The unlock page unlocks and never enrolls.
material on that device and wraps a copy for the new one. When no enrolled
device is available, the ADR-0009 recovery package approves.
wrapped copy, and any one of them can unlock.
request ID, reason, time and result, like an unlock.
because that device may still hold a working wrapped copy. A rekey makes
every copy wrapped under the old key stop working. The new key is wrapped
only for the retained devices.
disaster-recovery shares are reissued from the new key and their custody
is verified again.
-init(only when none is in progress),-status,-cancel(discardssubmitted keys and configuration) and a nonce that every submitted key must
carry. It does not say how to resume an interrupted rekey, or whether the
old keys stay valid until it completes (Appendix B). The plan must test
both and write the procedure. The same page marks
rekeyas deprecated infavor of
rotate-keys, so the plan also checks which command to use.Flow A: online site, laptop and QR code
carries a single-use session URL.
page on the phone. The phone asks for its sign-in check, unwraps locally and
submits to the session.
between the devices. The phone needs a network route to the unlock page.
Flow B: online site, notification link on the phone
the same session as in Flow A, without the QR code.
Flow C: offline portable kit
The kit is the flypack of ADR-0050. That ADR is Proposed, and its Mode C
constraints bind no implementation today
(
dmfdeploy/dmfdeploy@1d47e09:docs/decisions/0050-flypack-mode-portable-offline-kit.md:6, 61).admin's phone at install.
and opens the unlock page on itself.
context (Appendix A). The certificate question below settles how.
notification question below settles the fallback.
(
dmfdeploy/dmfdeploy@1d47e09:docs/decisions/0050-flypack-mode-portable-offline-kit.md:26).Unlock material sealed to the kit's own hardware is material at rest on the
node, which conflicts with property 3. If the kit uses Tier 2, that
constraint needs an amendment when the ADR is promoted. Nothing here changes
the ADR while it is Proposed.
Optional private mesh layer
A self-hosted mesh VPN control server lets paired devices reach the unlock
page and the notifications from anywhere. It hides them from unpaired
devices.
passkey to unlock.
Audit
ADR-0028 C5 requires actor, role, request id and reason on every
DMF-initiated automated action
(
dmfdeploy/dmfdeploy@1d47e09:docs/decisions/0028-identity-and-authority-chain.md:35).ADR-0029 requires an audit record for every submission, sent to a write-once
target
(
dmfdeploy/dmfdeploy@1d47e09:docs/decisions/0029-tiered-unseal-posture.md:35).The unlock page runs before the write-once target is reachable, so these
properties must hold:
service is up.
spool is cleared only after delivery.
that the credential belongs to), the role, a request ID and a reason. An
automatic reason such as "cold-boot unlock" is enough. An unlock record also
holds the verified time of the assertion, the credential identifier and the
user-verification result. Device details that the phone reports about itself
are labeled as claimed, not verified. The record never says "biometric",
because the user-verification result does not say which method was used
(Appendix A).
address and the time, with the actor marked unknown. It does not record an
invented identity.
accidental changes. The chain does not prove that no one edited it on
purpose: anyone who can write the spool can rebuild it. Resisting root is
out of scope in this lane.
can show a gap. It shows a gap only while the counter itself survives.
that unlock can be lost. Some lost records may never be detectable. The
gap shows when the next record is written, if the counter survived. If
nothing can be written at all, the gap shows only once writing works
again, and a restart before writing recovers can lose it for good. A
refusal would turn a full spool into an outage and would add friction for
the admin.
submission, sent to a write-once target (line 35, cited above). Decision 5
lists the amendment.
Periodic admin check-in
A regular check-in finds a lost phone or an admin who has left early, not at
the next cold boot. It applies to the portable kit and to long-running sites.
These properties must hold:
30 days, and the length is configurable. The deadline is stored durably, so
it survives service and node restarts. Time while the node is powered off
counts toward the period, and a restart alone never extends it.
unlock, run while OpenBao is unsealed. It proves that at least one enrolled
credential and its wrapped copy still work. It does not reseal anything.
check-in and warns about any device not seen within the period, so a
departed admin's device shows up and can be removed. Any one device can
still unlock.
ahead of expiry, by default at 14, 7, 3 and 1 days, and the warning days
are configurable. Each warning carries a one-tap check-in link.
workloads keep running. Privileged changes are refused until an admin
checks in. The recovery package path always works after expiry: it can
check in and enroll a replacement device, and it leaves running workloads
untouched. OpenBao seals at the next restart, as it does today. An
immediate seal is rejected, because it would cause an outage during
operation and a kit may be mid-event.
under the same failure policy. A record may be lost when the spool cannot
write, and a check-in is never blocked by the spool.
consistent with the accepted local-root risk of this lane.
After the unseal
After an unseal, a catch-up step runs before secret consumers retry. This RFC
states the properties and does not design the mechanism:
credential is renewed.
The reason is the login lockout. After a long power-off, the SecretID that
External Secrets Operator uses has expired. #396 records
that failed logins trip the lockout and that a new SecretID does not help
until the lockout ends. The OpenBao documentation gives the defaults: five
failed attempts, a 15-minute lockout, and a lockout that applies to the
userpass, LDAP and AppRole methods (Appendix A). The mechanism depends on
#851, which would remove the case for ESO, and on
#396, which covers the rotation and alerting around
AppRole credentials. It belongs in the plan that follows those two.
Out of scope: regulated lanes
Lanes with legal or regulatory obligations need their own changes later.
Examples are dual control across people, a specific authentication assurance
level and strict write-once audit delivery. This RFC does not design for
them. Tier 1 in ADR-0029 is the existing place for the multi-person case, and
it is also unbuilt.
Questions with options
Order: HA first or a single replica
ADR-0029 defines every tier on a high-availability OpenBao with Raft
(#12). Without it, every OpenBao pod restart seals the
single replica. An upgrade, a node drain or a crash then needs a phone unlock
and causes an outage until the admin acts.
HA keeps OpenBao serving while one node restarts. It does not remove the
unlock. The OpenBao seal documentation says that each node must be unsealed
with the threshold of shares, that partial unsealing is not distributed
across the cluster, and that a restart seals the node again (Appendix A). A
restarted node therefore needs the same phone unlock, and it must be unsealed
before the next node restarts. The same page's migration steps say to bring
back a downed standby before moving on to the others, to keep quorum with
Integrated Storage. A rolling upgrade is a sequence of restarts, each
followed by an unlock.
Options: build HA first, or allow Tier 2 on a single replica. The second
option conflicts with ADR-0029, which says the tiers are not selectable
without the Raft baseline. It needs an ADR-0029 amendment that names the
exception. It does not keep the baseline true. Recommendation: HA first
for any long-lived site, because it keeps OpenBao serving through a
single-node restart. The admin still unlocks each restarted node. Allow a
single replica in the sandbox lane through an explicit ADR-0029 amendment and
a visible warning.
Certificate trust offline
Online sites use a public certificate. An offline kit can also use one if the
kit gets it while online and can renew it, so offline does not always mean a
private authority. A kit that stays off for months may find the certificate
expired when it starts. The options:
phone. It needs a renewal path and a plan for expiry during a long
shutdown.
only for the kit's host name. Support for such limits on a user-installed
authority is unverified on both phone platforms (Appendix B).
fails. A leaked signing key would let an attacker impersonate any site to
that phone, so this is a security decision the operator must approve.
For options 2 and 3 the plan must settle how the authority is renewed and
removed, and how pairing installs it for a non-technical admin. Keeping the
private key off the kit protects it from an attacker with root on the kit,
but it adds a step to every renewal. Root on the kit is an accepted risk in
this lane. Recommendation: option 2 with the private key on the kit, so
renewal needs no admin action, tested on one iPhone and one Android phone.
Use option 1 where the kit has a renewal path. Do not use option 3 without
explicit approval.
Notifications on an offline phone
Whether an offline phone gets a timely alert from a notification server on
the kit's own network is an unconfirmed feasibility question on each phone
platform (Appendix B). Phones limit background work, and an offline kit may
not reach any outside service. Recommendation: treat the unlock page as
the source of truth, and tell the admin to save it to the phone's home screen
at pairing, so the bookmark is the fallback. Test on a real iPhone and a real
Android phone before the plan commits to the offline alert.
The one-time token
ADR-0029 requires a token for each cold boot, printed to the node console or
the SSH login message
(
dmfdeploy/dmfdeploy@1d47e09:docs/decisions/0029-tiered-unseal-posture.md:33).Its purpose is to limit unlocking to people with legitimate infrastructure
access at boot. A non-technical admin without a console or SSH is the person
this RFC serves. Options:
it after a passkey check. It proves that the holder receives the
notification or holds an enrolled credential and passes its user
verification. It does not prove a specific device, and it does not prove
infrastructure access.
an enrolled credential, and the wrapped copy on the phone gates the unlock.
The private network, the rate limit and the audit remain. This needs an ADR-0029 amendment for Tier 2. Tier 1 keeps the
token.
Either choice weakens the original proof of infrastructure access, and the
amendment must say so. The path with no notification is an acceptance case
for either option: a late, lost or expired notification must not leave the
admin without a way to unlock. Recommendation: (b). Under (a) the token
comes from the same passkey, so it adds friction and a dead end without an
independent factor.
Sign-in policy for the unlock
WebAuthn's user-verification result can reflect a PIN or a biometric, and it
does not say which (Appendix A). ADR-0029's Tier 2 row specifies one phone
and one biometric event
(
dmfdeploy/dmfdeploy@1d47e09:docs/decisions/0029-tiered-unseal-posture.md:24).Accepting a device PIN conflicts with that wording, so this needs an ADR-0029
amendment. A biometric-only rule would need platform enforcement that I could
not confirm. Recommendation: amend the Tier 2 row to say user
verification by any method the platform offers, including a device PIN,
because that adds no friction. Record the result, and do not claim a
biometric in the audit or the documentation.
Appendix A: Sources and what I read
bao operator unsealtakes one key share, as an argument (not recommended, because it lands in shell history) or at a hidden prompt, with-resetand-migrateflags-pgp-keysbelongs tobao operator init, it encrypts the generated unseal keys, and the number of keys must match-key-sharesunless-stored-sharesis usedjoinsection.rekeyis zero downtime and needs a quorum of existing keys,-initonly when none is in progress,-canceldiscards submitted keys, a nonce goes with every key, and the command is deprecated in favor ofrotate-keysprfsection. MDN says fewer authenticators support evaluating the PRF when a credential is created.Appendix B: Unconfirmed items
they list WebAuthn PRF support. I did not open the notes' text. Test on
the admin's device and operating system version.
disagree: one older page says Android does not support PRF, and newer
ones say it does with the platform's default passkey store. Test on the
admin's device.
authenticator evaluates the PRF only after user verification, so the page
asks for it and the enrollment test checks it. I also did not read the
WebAuthn text on the user-verification flag and its methods.
browser keeps the origin storage that holds the wrapped copy, or whether
it can be evicted. The plan must test this, and the second enrolled device
and the recovery package cover a lost copy.
timely alert from a notification server on the kit's own network, on
either phone platform. I found no platform source I could check. Test on
real devices, with the bookmark as the fallback.
certificates a passkey can use.
the rule differently. I did not resolve which applies to a host that
equals the RP ID.
Android honor a name constraint on a user-installed certificate
authority, or how private host names work as RP IDs.
resume an interrupted rekey, or whether the old keys stay valid until it
completes.
phone opens the unlock page through a session URL. I did not verify its
internet need or whether PRF output survives it.
Decisions requested
sites, because it keeps OpenBao serving through a single-node restart. The
admin still unlocks each restarted node before the next one restarts. Allow a
single replica in the sandbox lane through an ADR-0029 amendment that
names the exception and a visible warning. Keep the signed, boot-checked
tier and the audited downgrade as the ADR states them. Retire the ADR-0011
automation quorum before Tier 2 is declared, and re-check the ADR-0009
five-share distribution.
its key on the kit, so renewal needs no admin action, and a public
certificate where the kit can renew one. Do not accept an unrestricted
private authority without explicit approval.
test on a real iPhone and a real Android phone before the plan commits to
an offline alert.
reissue after a passkey check, or (b) no token for Tier 2 with an
ADR-0029 amendment. Recommended: (b). The no-notification path is an
acceptance case either way.
number them, and deliver them to the write-once target when reachable. If
the spool cannot write, unseal anyway and accept that the record of that
unlock can be lost, with the gap shown by the next written record. Amend
the ADR-0029 guardrail that requires an audit record for every submission
to a write-once target (line 35) to say this for the sandbox lane.
above, including the reissue and re-verification of the five recovery
shares after a rekey.
phone, the QR code carries a short-lived single-use session URL, and the
phone unwraps locally and submits to the session, so no laptop sees key
material.
platform's user verification, including a device PIN. Record the result,
and make no biometric claim.
properties above, with dependencies on RFC: Authenticate External Secrets Operator to OpenBao with Kubernetes auth #851 and
AppRole SecretID rotation has no pre-expiry schedule or alert — every env silently fails at ~30 days #396.
30-day period that restarts at each unlock or check-in, warnings at 14,
7, 3 and 1 days with a one-tap link, and on expiry running workloads
keep running while privileged changes are refused until a check-in. The
period and the warning days are configurable.
docs/plans/withtracking_issuepointing at Implement ADR-0029 Tier 2 operator unlock with a phone passkey #864, and I carry theconfirmations in Appendix B into its first work package.
All reactions