diff --git a/test/integration/mesh.test.ts b/test/integration/mesh.test.ts index f48f905..70ba0c3 100644 --- a/test/integration/mesh.test.ts +++ b/test/integration/mesh.test.ts @@ -248,10 +248,18 @@ test("a credential reaches both ends and the mesh holds neither", { skip, timeou `"provides":[{"name":"postgres-database","scope":"mesh"}],"serves":{"postgres-database":{"port":5432}},` + `"grants":{"postgres-database":"/var/lib/mesh-host/grants"},` + `"receives":{"postgres-database":"/var/lib/mesh-host/grants/mesh.json"},"resources":[]}' > /tmp/pg.json`); + // The consumer also writes a configuration file with a hole in it, which is how nearly every + // real program takes a credential: a sealed file is a password alone, and almost nothing reads + // one. The mesh cannot compose the document — it discarded the value — so the module supplies it + // with `${secret:...}` in it and the host, the only thing that sees both halves, fills it in. await must("anchor", `printf %s '{"module":"meshboard","version":"1",` + `"requires":["postgres-database"],"contributes":{"postgres-database":{"name":"meshboard"}},` + `"binds":{"postgres-database":"/etc/meshboard/database.json"},` + - `"secrets":{"postgres-database":"/etc/meshboard/database.password"},"resources":[]}' > /tmp/app.json`); + `"secrets":{"postgres-database":"/etc/meshboard/database.password"},` + + `"resources":[{"id":"env","type":"file","path":"/etc/meshboard/database.env","mode":"0600",` + + `"content":"PGHOST=$\{bound:postgres-database:at\}\\nPGPORT=$\{bound:postgres-database:port\}\\n` + + `PGUSER=$\{bound:postgres-database:as\}\\nPGPASSWORD=$\{secret:postgres-database\}\\n"}]}' ` + + `> /tmp/app.json`); await must("anchor", `docker cp /tmp/pg.json mesh-control:/pg.json`); await must("anchor", `docker cp /tmp/app.json mesh-control:/app.json`); await mesh("module add /pg.json"); @@ -281,6 +289,24 @@ test("a credential reaches both ends and the mesh holds neither", { skip, timeou // Only the machine it is for may read it. assert.match(await must("laptop", `stat -c %a /etc/meshboard/database.password`), /^600/); + // And the configuration with holes in it arrived filled. **This is the only place the two + // substitutions are proven against a real host**: the mesh fills what it knows in the clear + // before sending, the host opens the sealed value and fills the rest on the machine, and the + // two expressions that find the holes live in different repositories. + const filled = await must("laptop", `cat /etc/meshboard/database.env`); + assert.match(filled, /^PGPASSWORD=.+$/m, `the password was never put in:\n${filled}`); + assert.ok(filled.includes(`PGPASSWORD=${onConsumer}`), + `the file holds a different password from the credential file:\n${filled}`); + assert.match(filled, /^PGUSER=mesh_laptop_meshboard$/m, + `the consumer was not told what name to present:\n${filled}`); + assert.match(filled, /^PGPORT=5432$/m, `the port did not arrive as a port:\n${filled}`); + assert.doesNotMatch(filled, /\$\{/, + `a placeholder survived to the machine and would be read as a value:\n${filled}`); + assert.match(await must("laptop", `stat -c %a /etc/meshboard/database.env`), /^600/); + + // The password is in that file and nowhere the mesh could read it — which is the whole point of + // filling the hole on the machine rather than composing the document in the control plane. + // And it is nowhere it could have been read on the way. The declaration crossed the broker; the // database is the control plane's; the state is what the node reported back. for (const [machine, where] of [