docs(server): correct overstated WORM/config-edit comment; pin two-step demotion
The decider comment claimed standing config-edit "only ever grants VerbA, so it can never write/delete/create WORM records." True for a single decision, but it overstated the guarantee: a config-editor who administers a WORM zone can edit that zone's .zddc (inherit:false drops the embedded worm:), after which ordinary writes are no longer clamped. That two-step demotion is intended — owning a subtree's policy includes its worm: marker, and the edit is access-logged — so WORM is tamper-EVIDENT to its policy owner, not tamper-PROOF. Rewrite the comment to say so (and note where to gate worm: relaxation behind elevation if a deployment needs tamper-proof markers), and add TestStandingConfigEdit_WormDemotionIsTwoStep pinning the boundary (direct WORM write denied unelevated), the lever (config-edit allowed), and the consequence (post-demotion write allowed). Surfaced by the deferred-findings triage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d14516a74d
commit
88ef2dd921
2 changed files with 59 additions and 5 deletions
|
|
@ -243,11 +243,21 @@ func (d *InternalDecider) Allow(_ context.Context, input AllowInput) (bool, erro
|
|||
// is a STANDING permission: a subtree admin (admins: cascade) or a
|
||||
// holder of the `a` verb may edit the config of subtrees they
|
||||
// administer WITHOUT elevating. This sits ABOVE the WORM clamp because
|
||||
// config is not WORM-protected data — and it only ever grants VerbA,
|
||||
// so it can never write/delete/create WORM *records* (those need
|
||||
// W/C/D, which stay clamped and behind the elevated bypass above).
|
||||
// Elevation is thus purely additive: it adds the WORM/destructive
|
||||
// overrides, never gating config-edit you already have authority for.
|
||||
// config is not WORM-protected data.
|
||||
//
|
||||
// Scope: this grants VerbA only, so no SINGLE decision here authorizes a
|
||||
// WORM *record* write/delete/create (those need W/C/D, which stay clamped
|
||||
// below, behind the elevated bypass above). It does NOT, however, make a
|
||||
// WORM zone tamper-proof against its own policy owner: a config-editor who
|
||||
// administers a WORM directory may edit that directory's .zddc — including
|
||||
// relaxing or (via inherit:false) dropping its `worm:` marker — after
|
||||
// which ordinary writes to that subtree are no longer clamped. That
|
||||
// two-step demotion is intended (owning a subtree's policy includes its
|
||||
// worm: declaration) and is access-logged/transparent: WORM is therefore
|
||||
// tamper-EVIDENT to its config owner, not tamper-PROOF. A deployment that
|
||||
// needs the marker immutable except under elevation should gate worm:
|
||||
// relaxation behind IsActiveAdmin. The edit-then-write composition is
|
||||
// pinned in standing_config_test.go (TestStandingConfigEdit_WormDemotionIsTwoStep).
|
||||
if verb == zddc.VerbA && zddc.IsConfigEditor(chain, email) {
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,3 +72,47 @@ func TestStandingConfigEdit(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestStandingConfigEdit_WormDemotionIsTwoStep documents the (intended)
|
||||
// composition that a single-action view of the decider hides: a config-editor
|
||||
// who administers a WORM zone cannot write a WORM record directly, but CAN
|
||||
// demote the zone by editing its own .zddc, after which an ordinary write is
|
||||
// no longer clamped. WORM is thus tamper-evident to its policy owner, not
|
||||
// tamper-proof. Pinned so the behavior is an explicit, tested decision — if a
|
||||
// deployment ever needs WORM markers immutable except under elevation, this is
|
||||
// the test that must change alongside gating worm: relaxation behind
|
||||
// IsActiveAdmin in policy.InternalDecider.Allow.
|
||||
func TestStandingConfigEdit_WormDemotionIsTwoStep(t *testing.T) {
|
||||
d := &InternalDecider{}
|
||||
dec := func(chain zddc.PolicyChain, p zddc.Principal, action string) bool {
|
||||
ok, _ := AllowActionFromChainP(context.Background(), d, chain, p, "/proj/probe", action)
|
||||
return ok
|
||||
}
|
||||
alice := zddc.Principal{Email: "alice@x", Elevated: false} // config-editor, NOT elevated
|
||||
|
||||
// Before — alice administers a WORM zone (admins: + a non-nil worm list).
|
||||
worm := zddc.PolicyChain{
|
||||
Levels: []zddc.ZddcFile{{Admins: []string{"alice@x"}, Worm: []string{}}},
|
||||
HasAnyFile: true,
|
||||
}
|
||||
// The boundary holds: a direct WORM record write is denied unelevated...
|
||||
if dec(worm, alice, ActionWrite) {
|
||||
t.Error("unelevated config-editor must NOT directly write a WORM record")
|
||||
}
|
||||
// ...but she CAN edit the zone's policy (VerbA) — the lever for demotion.
|
||||
if !dec(worm, alice, ActionAdmin) {
|
||||
t.Error("config-editor should be able to edit the WORM zone's .zddc unelevated")
|
||||
}
|
||||
|
||||
// After — alice has rewritten that .zddc: inherit:false dropped the
|
||||
// embedded worm: and her acl now grants rwcd (the post-edit cascade the
|
||||
// file API persists). The subtree is no longer WORM, so her write lands —
|
||||
// still unelevated. This is step two of the intended demotion.
|
||||
demoted := zddc.PolicyChain{
|
||||
Levels: []zddc.ZddcFile{{ACL: zddc.ACLRules{Permissions: map[string]string{"alice@x": "rwcd"}}}},
|
||||
HasAnyFile: true,
|
||||
}
|
||||
if !dec(demoted, alice, ActionWrite) {
|
||||
t.Error("after the config-editor demotes the zone, the ordinary write should be allowed")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue