pr_01m47d15m3e54sn21z27rpy5n9/services/runner/src/credentials.test.ts
| 1 | import assert from "node:assert/strict"; |
| 2 | import { createHash } from "node:crypto"; |
| 3 | import { test } from "node:test"; |
| 4 | |
| 5 | import { credentialHashes, holdCredentials, pushGrant, remotePath, revokeCredentials, sha256Hex } from "./credentials.ts"; |
| 6 | |
| 7 | test("a remote names its repository", () => { |
| 8 | assert.deepEqual(remotePath("https://g1t.sh/acme/rocket.git"), { namespace: "acme", name: "rocket" }); |
| 9 | assert.deepEqual(remotePath("https://g1t.sh/pulls/pul_01abc.git"), { namespace: "pulls", name: "pul_01abc" }); |
| 10 | assert.equal(remotePath("https://g1t.sh/acme"), null); |
| 11 | }); |
| 12 | |
| 13 | test("a fork is the pull request's own; a branch of the repository is not", () => { |
| 14 | const repo = { namespace: "acme", name: "rocket" }; |
| 15 | assert.deepEqual(pushGrant(repo, { namespace: "pulls", name: "pul_1" }, "main"), { |
| 16 | repo: { namespace: "pulls", name: "pul_1" }, |
| 17 | branch: null, |
| 18 | }); |
| 19 | assert.deepEqual(pushGrant(repo, { namespace: "Acme", name: "Rocket" }, "fix-login"), { |
| 20 | repo: { namespace: "Acme", name: "Rocket" }, |
| 21 | branch: "fix-login", |
| 22 | }); |
| 23 | }); |
| 24 | |
| 25 | test("tokens are hashed the way identity stores them", async () => { |
| 26 | const token = "g1t_0123456789abcdef"; |
| 27 | assert.equal(await sha256Hex(token), createHash("sha256").update(token).digest("hex")); |
| 28 | const hashes = await credentialHashes({ G1T_TOKEN: token, G1T_AGENT_TOKEN: "g1t_x", OTHER: "g1t_y", CHECK_TOKEN: "c" }); |
| 29 | assert.equal(hashes.length, 2); |
| 30 | assert.ok(hashes.every((hash) => /^[0-9a-f]{64}$/.test(hash))); |
| 31 | }); |
| 32 | |
| 33 | /** Identity, as far as the credentials' lifecycle uses it. */ |
| 34 | function fakeIdentity() { |
| 35 | const calls: { method: string; body: unknown }[] = []; |
| 36 | return { |
| 37 | calls, |
| 38 | fetch: async (url: string, init?: RequestInit) => { |
| 39 | calls.push({ method: url.split("/rpc/")[1], body: JSON.parse(String(init?.body)) }); |
| 40 | return new Response("true"); |
| 41 | }, |
| 42 | }; |
| 43 | } |
| 44 | |
| 45 | function memoryStorage() { |
| 46 | const map = new Map<string, unknown>(); |
| 47 | return { |
| 48 | map, |
| 49 | put: async (key: string, value: unknown) => void map.set(key, value), |
| 50 | get: async <T>(key: string) => map.get(key) as T | undefined, |
| 51 | delete: async (key: string) => map.delete(key), |
| 52 | }; |
| 53 | } |
| 54 | |
| 55 | test("a sandbox's credentials are bound to its run and revoked once when it stops", async () => { |
| 56 | const identity = fakeIdentity(); |
| 57 | const storage = memoryStorage(); |
| 58 | await holdCredentials(identity, storage, { G1T_TOKEN: "g1t_a", G1T_AGENT_TOKEN: "g1t_b" }, "run_1"); |
| 59 | assert.equal(identity.calls[0].method, "bind_run_credentials"); |
| 60 | assert.deepEqual((identity.calls[0].body as { runId: string }).runId, "run_1"); |
| 61 | await revokeCredentials(identity, storage); |
| 62 | await revokeCredentials(identity, storage); |
| 63 | const revokes = identity.calls.filter((call) => call.method === "revoke_run_credentials"); |
| 64 | assert.equal(revokes.length, 1); |
| 65 | assert.equal((revokes[0].body as { tokenHashes: string[] }).tokenHashes.length, 2); |
| 66 | }); |
| 67 | |
| 68 | test("a sandbox with no run record still has its credentials revoked", async () => { |
| 69 | const identity = fakeIdentity(); |
| 70 | const storage = memoryStorage(); |
| 71 | await holdCredentials(identity, storage, { G1T_TOKEN: "g1t_a" }, null); |
| 72 | assert.equal(identity.calls.length, 0); |
| 73 | await revokeCredentials(identity, storage); |
| 74 | assert.equal(identity.calls[0].method, "revoke_run_credentials"); |
| 75 | }); |