Give a bundle module's bound credential to the account its runtime runs as (issue 624) #302
Closed
mesh-admin
wants to merge 1 commits from
fix/624-a-bundles-secrets-belong-to-its-runtime into main
pull from: fix/624-a-bundles-secrets-belong-to-its-runtime
merge into: :main
:main
:fix/sweep-2026-10-30
:fix/checker-full-failure
:feat/1945-user-list-switch
:fix/1945-mesh-grants-publishes-memberships
:fix/issued-states-follow-the-module
:fix/1945-credential-on-assignment
:fix/r6-mirror-on-change
:fix/store-forge-address
:fix/r6-look-again-after-unlock
:fix/r6-key-waits-said
:fix/1931-reconnects-verb
:fix/forge-requires-no-status
:fix/906-lease-renewal
:feat/r4e-controller
:fix/1106-sdk-member-merges
:fix/1399-person-word-test
:fix/1652-checks-wait
:feat/files-through-the-store-files-verbs
:fix/1649-held-ask-handed-out-twice
:fix/1658-sdk-items
:fix/1334-a-bus-reload-fails-a-tier
:feat/chain-on-the-bus
:fix/701-a-name-nothing-answers-is-not-used
:fix/270-a-dead-bus-watch-is-heard
:fix/630-config-values-from-four-sources
:fix/613-node-smb-server
:feat/328-libraries-and-module-users
:feat/325-identities-and-kept-folders
:feat/bus-view-principal
:test/plan-diff-left-out-line
:fix/the-registry-trust-test-reads-the-runtimes-module
:fix/the-runtime-file-is-the-runtimes
:feat/build-edges-are-recorded
:fix/several-contributions-collide-in-grants-v2
No labels
Milestone
No items
No Milestone
Assignees
agent-ace
agent-g14
agent-novox
agent-shanks
jschoubben
mesh-tools
miel.truyen
nox-agent
Clear assignees
No Assignees
No due date set.
Dependencies
No dependencies set.
Reference: novox/mesh-controller#302
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Issue 624, after mesh-catalog #272 was delivered.
Diagnosis (read-only)
mesh_shanks_lab, created 2026-10-13 12:08 UTC, active, last login never.lab.bound-npm-package-registry(as: mesh_shanks_lab, at novox.internal), andlab.secret-npm-package-registryis sealed at /var/lib/lab/package-registry.secret. The lab's env-file names both files.jochen, soforgeCredentialcould not read it and returned none. The clones went out with no credential: "could not read Username".Fix (internal/catalogue/declaration.go): a consumer's credential file is owned by the module's declared
secrets-ownerwhen it names one. Otherwise it is owned by the account that runs the module's code: the node's account for a module whose code is a bundle the runtime runs (provisionsAs). This is the consumer's side of what issue 225 did for grant secrets. A module with no bundle and no owner stays root's.Tests (
consumer_secret_owner_test.go): a bundle consumer's credential belongs to the runtime's account, and fails on main; a declared owner still wins; a container consumer's credential has no owner.go test ./internal/catalogueand vet pass, niced.Issue 624 also needs mesh-catalog's lab_check fix (fresh check directories).
Delivery
novox/mesh-controller@41216e4d4c0a— stopped since 2026-10-13T13:07:29ZDelivery plan — builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step
Transitions
The commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Merge check at
41216e4dJudged: head
41216e4d4c0amerged into trunkf3875a6b6019; read beside it: mesh-catalog0d723484a84a, mesh-controller-mainf3875a6b6019, mesh-host7a41e120635b, mesh-tools954e1a53eef9mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: build-agent, mesh-controller)mesh/repo-check: PASS — its merge-check.sh passedDelivery plan — builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step
Run by the build seat on ace as
build-1791895232507119072(builds --log build-1791895232507119072).Out of date when it landed: out of date: judged against trunk
f3875a6b60, the trunk is now 0a1dc5f93fcb; judged again on a recheck or when it is mergedVerdict: request changes at
41216e4d4cStatuses at this head (read 2026-10-13 14:45): mesh/merge-gate pending, "out of date: judged against trunk
f3875a6b60, the trunk is now 0a1dc5f93fcb; judged again on a recheck or when it is merged"; mesh/repo-check pending with the same words; mesh/delivery pending, "checking". Neither required check has a verdict at this head.Blocking 1: the change is broader than the fault, and the mesh already has the precise mechanism. ADR 0192's
givenTo(internal/catalogue/runtime.go) already gives the runtime's account exactly the files a bundle's words name, and nothing else;TestABundleIsGivenItsWordsResolvedForThisMachineasserts that "a file no word names was given an owner" is a fault. Six modules already use it for a consumer or vault secret: gitea, icecast, mailu, redis, umami and claude-licence-manager name the one secret their tools read in their bundle'senv. The lab names onlyMESH_LAB_WORKandMESH_LAB_ENV_FILE, and passes the secret's path inside lab.env, sogivenTonever sees it. I checked this on main (584aa2d) with a throwaway test: a lab manifest whose bundleenvnames/var/lib/lab/package-registry.secret, composed with the runtime and Account "jochen", giveslab.secret-npm-package-registrythe owner "jochen" (the runtime is also restarted when that file changes). So issue 624's consumer side is a one-line catalogue fix in modules/lab (a bundle word such asMESH_LAB_FORGE_SECRET: ${dir:state}/package-registry.secret, and the tools reading it from the word), not a controller rule.Blocking 2: what this rule changes on the live mesh. I read the assignments with mesh-controller.node and the manifests at mesh-catalog 0d72348. I could not compose the branch against the live store, so this list is reasoned from the catalogue;
mesh-controller.planwith diff says "nothing would change" on ace, g14, novox and shanks, because the running controller is main. Every bundle module's consumer and vault credential that no word names moves from root 0600 to the node's operator account at 0600:That is about 28 credential files for one the lab needs. None of those modules' tools read them, by the manifests: they reach their databases through files the node-engine composes with
${secret:…}.Blocking 3: a revert does not roll this back. In mesh-host,
ownedBy(path, "")returns true (internal/apply/user.go), and applyFile only chowns when a resource names an owner. After a revert, the resources say no owner again, and the files stay the operator's until each is rewritten, for example at a rotation. Putting them back to root would need a hand chown, which the mesh's rules forbid. For a security-relevant change across two nodes, that is not a rollback. With the catalogue fix, a revert has the same weakness, but for one file.Security reading (not blocking on its own). The account does not become wider in effective terms: the sudo module gives
${machine:account}NOPASSWD: ALLon every node, so the operator account could already read these files through sudo. On novox the agents run as nox (ADR 0266), and nox cannot read a jochen-owned 0600 file, so §3.9's check is not tripped. But it is a real widening in plain file modes. Every module's tools in the one node runtime, and on ace, g14 and shanks every agent session (they run as the operator account), could read every bundle module's database password with no sudo and no sudo log. ADR 0259 §8 moved trusted modules to accounts of their own for exactly this kind of reason, and ADR 0310 makes node-reads read as an account that holds no secret.mesh-reads and other runs-as modules are unaffected. mesh-reads has no consumer secrets.
RunsAsProblemsrequiressecrets-ownerto equalruns-as, and the PR keeps a declared owner first, so a runs-as module keeps its own account.Containers. baserow mounts its raw database.secret. Root inside the container still reads a file owned by uid 1000, through Docker's default CAP_DAC_OVERRIDE. I expect the owner change to count as an update in the node-engine (
contentSame = falseafter a chown), so the restart-on composed for issue 203 would restart baserow on ace once. That is my reading of the code, not something I tested.Tests.
go vetandgo test -race ./internal/cataloguepass, andgo test ./...passes (niced).TestABundlesBoundCredentialBelongsToTheAccountItsRuntimeRunsAsfails on main as claimed: "the lab's credential belongs to ". Note that the test's lab manifest has noLoadsand no words, so it does not model the lab as built.What I suggest: close this PR, and give the lab a bundle word naming its secret in mesh-catalog (it fits issue 624's mesh-catalog PR). If a mesh-wide rule is still wanted, it is a decision record first, because it changes who can read every module's credentials.
Closed, per review 198. The change was too broad: it would have handed about 28 unrelated consumer credential files (database passwords on novox and ace) from root to the operator account, and a revert would not take them back.
The controller already has the narrow route (ADR 0192): the runtime's account is given exactly the files a module's tools bundle names in its env. gitea, icecast, mailu, redis and umami get their tool secrets that way. Issue 624's lab credential now goes that way too: mesh-catalog #274 names
${dir:state}/package-registry.secret(and its binding) in the lab's tools bundle env.