anthropic bed: package a module's own npm deps, stage grant files readably
Two harness fixes the green end-to-end run needed: - build-module-runtime.sh installs a module's non-@novox runtime deps under /app/modules/<module>/node_modules, so a module can carry a private dependency (the anthropic-manager seals with tweetnacl-sealedbox-js). The shared tree still answers @novox/* and common packages. A no-op for modules that declare none. - stageIntoControl chmods the manager's 0600 adopt/refresh outputs to 0644 on the anchor host before docker cp, so the distroless mesh-control (non-root, no chmod) can read the staged file. What is staged is a sealed box or the access token, never a cleartext refresh token. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
@@ -98,6 +98,17 @@ async function mesh(command: string, timeoutMs?: number): Promise<string> {
|
||||
return must(`docker exec mesh-control /mesh-control ${command}`, timeoutMs);
|
||||
}
|
||||
|
||||
// The manager's adopt/refresh runtime writes its outputs as root, mode 0600 (secret files). To hand
|
||||
// one to `mesh-control` — whose process runs as a non-root user — the test relaxes the mode on the
|
||||
// anchor host (where `must` is root) and then copies it in: `docker cp` preserves the source mode, so
|
||||
// the file lands 0644 and mesh-control (a distroless image with no `chmod` of its own) can read it.
|
||||
// What is staged this way is a sealed box or the access token, never a cleartext refresh token, so a
|
||||
// world-readable copy discloses nothing the control plane does not already hold. In production the
|
||||
// operator who ran adopt owns the file and this does not arise.
|
||||
async function stageIntoControl(hostPath: string, dest: string): Promise<void> {
|
||||
await must(`chmod 0644 ${hostPath} && docker cp ${hostPath} mesh-control:${dest}`);
|
||||
}
|
||||
|
||||
async function meshTry(command: string): Promise<{ out: string; ok: boolean }> {
|
||||
return on(`docker exec mesh-control /mesh-control ${command}`);
|
||||
}
|
||||
@@ -279,7 +290,7 @@ test("model access refreshes on the manager node and delivers only the access to
|
||||
assert.match(sealedGrant, /"sealed"\s*:/, `the adopted grant is not a sealed box:\n${sealedGrant}`);
|
||||
|
||||
// Store the sealed box in the control plane — which never sees the refresh token.
|
||||
await must(`docker cp /var/lib/mesh/anthropic-manager/out/grant.json mesh-control:/grant.json`);
|
||||
await stageIntoControl(`/var/lib/mesh/anthropic-manager/out/grant.json`, `/grant.json`);
|
||||
await mesh(`licence set-grant personal --file /grant.json`);
|
||||
|
||||
// --- 2. the host unseals: a push mounts the cleartext refresh token at the manager's secret path --
|
||||
@@ -318,8 +329,8 @@ test("model access refreshes on the manager node and delivers only the access to
|
||||
// --- 4. submit: the control plane is handed only the access token + opaque box -------------------
|
||||
// The consumer is put on the licence now — an access token exists to seal to it.
|
||||
await mesh(`licence use personal ${MACHINE} anthropic-consumer`);
|
||||
await must(`docker cp /var/lib/mesh/anthropic-manager/out/access-token mesh-control:/access-token`);
|
||||
await must(`docker cp /var/lib/mesh/anthropic-manager/out/new-grant.json mesh-control:/new-grant.json`);
|
||||
await stageIntoControl(`/var/lib/mesh/anthropic-manager/out/access-token`, `/access-token`);
|
||||
await stageIntoControl(`/var/lib/mesh/anthropic-manager/out/new-grant.json`, `/new-grant.json`);
|
||||
const submitted = await mesh(`licence submit-refresh personal --access-file /access-token --grant-file /new-grant.json`);
|
||||
assert.match(submitted, /sealed to 1 holder/, submitted);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user