diff --git a/modules/mesh-vault/cmd/mesh-vault/intake.go b/modules/mesh-vault/cmd/mesh-vault/intake.go index b8a03715..9b5648ad 100644 --- a/modules/mesh-vault/cmd/mesh-vault/intake.go +++ b/modules/mesh-vault/cmd/mesh-vault/intake.go @@ -728,6 +728,10 @@ func (v *Vault) RotateFor(caller, secret string, one Narrow, by, why string) (Ch return Changed{}, fmt.Errorf("not rotated: %s is a bus account the controller mints and hands here (ADR 0374 "+ "clause 4): only a password whose hash the bus's user list holds can log in, so the vault makes no new "+ "value for it; `module issue` again hands a new one and replaces this in place", secret) + case isLedgersSecret(secret): + return Changed{}, fmt.Errorf("not rotated: %s is the tool runner's own secret of the ledger accounts of its machine, "+ + "which the vault rewrites on each ledger account minted or retired (ADR 0597 §9); a person's rotation would "+ + "replace every module's credential with a value no ledger account holds", secret) case rec.Origin == OriginIssued: return Changed{}, fmt.Errorf("not rotated: %s is issued by its provider's software, which the mesh cannot "+ "make or rotate (ADR 0305 §9): change it in that software, and `secret issued` takes the value it holds now", diff --git a/modules/mesh-vault/cmd/mesh-vault/ledgers.go b/modules/mesh-vault/cmd/mesh-vault/ledgers.go index e329ed5b..65067bff 100644 --- a/modules/mesh-vault/cmd/mesh-vault/ledgers.go +++ b/modules/mesh-vault/cmd/mesh-vault/ledgers.go @@ -26,6 +26,12 @@ func ledgersSecret(node string) (string, error) { return OwnSecret(node, toolRunnerModule, buscompose.LedgersSecret) } +// isLedgersSecret says secret is a machine's `ledgers`: `own//node-tools/ledgers`. +func isLedgersSecret(secret string) bool { + parts := strings.Split(secret, "/") + return len(parts) == 4 && parts[0] == "own" && parts[2] == toolRunnerModule && parts[3] == buscompose.LedgersSecret +} + // toolRunnerNodes are the machines whose tool runner is wanted, sorted. func (v *Vault) toolRunnerNodes() []string { v.minting.mu.Lock() diff --git a/modules/mesh-vault/cmd/mesh-vault/mint_test.go b/modules/mesh-vault/cmd/mesh-vault/mint_test.go index e380c69d..697ab901 100644 --- a/modules/mesh-vault/cmd/mesh-vault/mint_test.go +++ b/modules/mesh-vault/cmd/mesh-vault/mint_test.go @@ -196,6 +196,21 @@ func TestTheVaultMintsAWantedAccountOnceAndNothingItHolds(t *testing.T) { t.Errorf("wanted again, m is not back in ledgers (%d) with its hash", len(got)) } }}, + // 1966/CAT-V2 (ADR 0597 §9): a person's rotate of a machine's `ledgers` is refused as a bus account's is: it + // would replace every module's credential with a value no ledger account holds. + {"a person's rotate of ledgers is refused", func(t *testing.T, v testVault, accounts *fakeAccounts, priv string) { + w := nodeToolsWant("ace") + v.WantHeard(w.User, wantRaw(t, w), false) + v.LedgersPass() + before, _ := ledgersOf(t, v, priv) + if _, err := v.Rotate("person.jochen", "own/ace/node-tools/ledgers", "jochen", "a test"); err == nil || + !strings.Contains(err.Error(), "ledger accounts of its machine") { + t.Errorf("a person rotated ledgers: %v", err) + } + if after, _ := ledgersOf(t, v, priv); len(after) != len(before) { + t.Errorf("a refused rotation changed ledgers: %d, was %d", len(after), len(before)) + } + }}, } { t.Run(c.name, func(t *testing.T) { v, accounts, priv := mintingVault(t)