Dual control¶
Dual control — "four-eyes" — makes a single approval insufficient for a high-consequence action. When enabled, that action no longer applies on the initiator's approval alone: it becomes a pending request that a second, distinct authorised operator must co-sign before it takes effect. This is the separation-of-duties control security teams ask for by name, built directly on the daemon's peer-UID identity registry.
Enterprise edition
Dual control is compiled only into the Enterprise build and is off by default. It needs a notion of authorised, distinct operators to enforce against — which the daemon already provides via kernel peer-UID.
Co-signing rides on authenticated UIDs — there is no key file
There is no --key and no break-glass here (that path was removed in 0.10.2). A co-signer proves who they are exactly as any operator does: their Unix UID, read from the kernel by the daemon via SO_PEERCRED. Co-signing an action is simply a distinct authorised operator re-issuing the same op command; the daemon counts distinct UIDs and applies the action once the threshold is met.
What "no single-person path" means¶
Two mechanisms decide whether four-eyes is in force for an action, and they compose:
- The env floor —
ETMINAN_DUAL_CONTROL_ACTIONS, a set of action tokens. - The signed, latched policy — a signed row in
baseline.db, set viaop dual-control-policy set.
The effective policy is the union of the two: the env var can only ever add coverage, and the signed policy latches actions on such that unsetting the env can never remove them. The effective threshold is max(env, signed-policy, 2) — both can only raise it, never drop it below two.
The policy is authoritative over the env
A latched signed policy is the whole point of the design: once you sign it on, an operator with shell access on the verifier cannot weaken four-eyes by editing an environment variable. And changing the policy is itself four-eyes — op dual-control-policy set opens a request a distinct admin must co-sign. There is no single-person path to disable it.
Configuring the env floor¶
ETMINAN_DUAL_CONTROL_ACTIONS is a comma-separated list of tokens. Unset / empty / off / none = off. An unknown token is a hard error — a misconfiguration can never silently fail open to single-signed.
| Token | Covers |
|---|---|
baseline-approve |
Baseline drift approval — the four-eyes-eligible action |
default |
Expands to baseline-approve |
# Enable four-eyes on baseline approval
ETMINAN_DUAL_CONTROL_ACTIONS=baseline-approve
# Tuning knobs
ETMINAN_DUAL_CONTROL_THRESHOLD=2 # M-of-N; default 2, clamped up (never below 2)
ETMINAN_DUAL_CONTROL_TTL_HOURS=168 # how long an un-completed request stays open (default 7 days)
The signed, latched policy¶
op dual-control-policy show prints the current signed floor; op dual-control-policy set changes it. Because set is itself four-eyes, it does not apply immediately — it records a pending request that a distinct admin must co-sign by re-issuing the same command.
# Inspect the signed floor
etminan-verifier op dual-control-policy show
# Propose latching four-eyes on baseline-approve at threshold 2 (opens a co-sign request)
etminan-verifier op dual-control-policy set \
--actions baseline-approve --threshold 2 \
--reason "enforce separation of duties on baseline approval"
# → recorded as a pending request (1 of 2). NOT applied yet.
# A DIFFERENT authorized admin co-signs by re-issuing the SAME command:
# etminan-verifier op dual-control-policy set --actions baseline-approve --threshold 2 --reason "…"
--actions accepts baseline-approve (or off/none to disable, which is also a four-eyes change). Each request captures the effective threshold at creation time, so a later config change never retroactively alters an open request.
The co-sign flow¶
sequenceDiagram
autonumber
actor A as Operator A (initiator, UID a)
actor B as Operator B (distinct, UID b)
participant V as etminan-verifierd
participant Q as dual_control_requests
participant DB as baseline.db + audit_log
A->>V: op approve web-01 (peer-UID a)
V->>V: build canonical payload; record UID a's approval
V->>Q: record pending request (payload + UID a)
V-->>A: NOT applied — needs a distinct co-signer (1 of 2)
Note over V: emits a `dual-control-pending` notification through the alarm channels
Note over B: sees the dual-control-pending notification
B->>V: op approve web-01 (peer-UID b)
V->>V: b ≠ a? b authorised for the action + host scope?
V->>Q: record UID b's co-signature over the SAME payload
alt threshold reached (2 distinct UIDs)
V->>DB: apply the action (same code path as single-signed)
V->>Q: mark request applied
V-->>B: APPLIED
else still short
V-->>B: co-signed, still needs more
end
Concretely:
# 1. Operator A initiates — the action becomes a pending request, not applied
etminan-verifier op approve web-01
# 2. A `dual-control-pending` notification fires so a second operator knows to act
# 3. A DIFFERENT authorised operator co-signs by re-issuing the same command
etminan-verifier op approve web-01
# → once 2 distinct UIDs have approved, it applies automatically
What makes it a genuine two-person proof¶
Both operators approve the exact same canonical payload, so both approvals bind to one set of bytes — two people vouching for one decision, not two loosely-related actions. On top of that, the daemon enforces at co-sign time:
- Authorised for the action and scope. Each co-signer must independently satisfy the action's required role and host scope under the default-deny RBAC matrix — the same bar as the direct action.
- Distinct UID. The same UID cannot approve twice; a re-run by the initiator is idempotent, never a second approval. Because the UID is supplied by the kernel (
SO_PEERCRED), it cannot be forged — the strongest defence against one operator standing in for two, short of an external identity system.
M-of-N, not just two
Set ETMINAN_DUAL_CONTROL_THRESHOLD (or the policy --threshold) above 2 to require more distinct approvers. Every value is clamped up to a floor of 2 — below that isn't four-eyes.
Storage, audit, and verification¶
Requests and approvals live in dual_control_requests / dual_control_approvals in baseline.db; every initiate, co-sign, apply, and expiry is also appended to the hash-chained audit log. Un-completed requests past their TTL are swept to expired.
baseline verify-signatures re-checks the four-eyes backing after the fact: a batch (or the signed policy row) marked dual-control must be backed by a request whose stored canonical payload equals the row's current signed payload, with the required number of distinct, authorised co-signatures — so a hand-edited row or a forged single-signed apply is caught.
Where to go next¶
- RBAC — the peer-UID identity registry and role/scope matrix each co-signer is checked against.
- Baseline review & drift — the
op approveaction four-eyes protects. - The audit log — where every request and co-signature is recorded.