111 lines
4.4 KiB
TypeScript
111 lines
4.4 KiB
TypeScript
import { describe, expect, test, beforeAll, beforeEach } from "bun:test";
|
|
import { mkdtemp } from "fs/promises";
|
|
import { join } from "path";
|
|
import { tmpdir } from "os";
|
|
|
|
let signBuildToken: typeof import("./build-signing").signBuildToken;
|
|
let verifyBuildToken: typeof import("./build-signing").verifyBuildToken;
|
|
let isBuildBanned: typeof import("./build-signing").isBuildBanned;
|
|
let addBuildToBanlist: typeof import("./build-signing").addBuildToBanlist;
|
|
let removeBuildFromBanlist: typeof import("./build-signing").removeBuildFromBanlist;
|
|
let resetCache: () => void;
|
|
|
|
beforeAll(async () => {
|
|
process.env.DATA_DIR = await mkdtemp(join(tmpdir(), "overlord-build-signing-test-"));
|
|
const mod = await import("./build-signing");
|
|
signBuildToken = mod.signBuildToken;
|
|
verifyBuildToken = mod.verifyBuildToken;
|
|
isBuildBanned = mod.isBuildBanned;
|
|
addBuildToBanlist = mod.addBuildToBanlist;
|
|
removeBuildFromBanlist = mod.removeBuildFromBanlist;
|
|
resetCache = mod.resetBuildSigningCacheForTests;
|
|
});
|
|
|
|
describe("build-signing", () => {
|
|
test("sign/verify roundtrip preserves payload", async () => {
|
|
const payload = { v: 1 as const, bid: "build-abc-123", uid: 42, iat: 1700000000 };
|
|
const token = await signBuildToken(payload);
|
|
expect(typeof token).toBe("string");
|
|
expect(token).toContain(".");
|
|
|
|
const verified = await verifyBuildToken(token);
|
|
expect(verified).not.toBeNull();
|
|
expect(verified!.v).toBe(1);
|
|
expect(verified!.bid).toBe("build-abc-123");
|
|
expect(verified!.uid).toBe(42);
|
|
expect(verified!.iat).toBe(1700000000);
|
|
});
|
|
|
|
test("sign/verify works with null uid", async () => {
|
|
const payload = { v: 1 as const, bid: "build-no-owner", uid: null, iat: 1700000001 };
|
|
const token = await signBuildToken(payload);
|
|
const verified = await verifyBuildToken(token);
|
|
expect(verified).not.toBeNull();
|
|
expect(verified!.uid).toBe(null);
|
|
});
|
|
|
|
test("tampered payload fails verification", async () => {
|
|
const token = await signBuildToken({ v: 1, bid: "real-build", uid: 1, iat: 1700000000 });
|
|
const [payloadB64, sigB64] = token.split(".");
|
|
const tamperedPayload = Buffer.from(
|
|
JSON.stringify({ v: 1, bid: "evil-build", uid: 1, iat: 1700000000 }),
|
|
)
|
|
.toString("base64")
|
|
.replace(/\+/g, "-")
|
|
.replace(/\//g, "_")
|
|
.replace(/=+$/g, "");
|
|
expect(await verifyBuildToken(`${tamperedPayload}.${sigB64}`)).toBeNull();
|
|
});
|
|
|
|
test("tampered signature fails verification", async () => {
|
|
const token = await signBuildToken({ v: 1, bid: "real-build", uid: 1, iat: 1700000000 });
|
|
const [payloadB64] = token.split(".");
|
|
const fakeSig = "A".repeat(86);
|
|
expect(await verifyBuildToken(`${payloadB64}.${fakeSig}`)).toBeNull();
|
|
});
|
|
|
|
test("legacy UUID-style buildTag returns null (so DB fallback path triggers)", async () => {
|
|
expect(await verifyBuildToken("550e8400-e29b-41d4-a716-446655440000")).toBeNull();
|
|
});
|
|
|
|
test("garbage input returns null", async () => {
|
|
expect(await verifyBuildToken("")).toBeNull();
|
|
expect(await verifyBuildToken("not-a-token")).toBeNull();
|
|
expect(await verifyBuildToken(".")).toBeNull();
|
|
expect(await verifyBuildToken("a.")).toBeNull();
|
|
expect(await verifyBuildToken(".b")).toBeNull();
|
|
});
|
|
|
|
test("payload missing required fields returns null", async () => {
|
|
const badPayloadB64 = Buffer.from(JSON.stringify({ hello: "world" }))
|
|
.toString("base64")
|
|
.replace(/\+/g, "-")
|
|
.replace(/\//g, "_")
|
|
.replace(/=+$/g, "");
|
|
expect(await verifyBuildToken(`${badPayloadB64}.AAAA`)).toBeNull();
|
|
});
|
|
|
|
test("token signed with a different key fails verification", async () => {
|
|
const token = await signBuildToken({ v: 1, bid: "build-x", uid: 1, iat: 1700000000 });
|
|
const verifiedFirst = await verifyBuildToken(token);
|
|
expect(verifiedFirst).not.toBeNull();
|
|
|
|
process.env.DATA_DIR = await mkdtemp(join(tmpdir(), "overlord-build-signing-test-rotated-"));
|
|
resetCache();
|
|
|
|
expect(await verifyBuildToken(token)).toBeNull();
|
|
});
|
|
|
|
test("banlist add/remove/check", () => {
|
|
expect(isBuildBanned("nope")).toBe(false);
|
|
addBuildToBanlist("ban-me");
|
|
expect(isBuildBanned("ban-me")).toBe(true);
|
|
addBuildToBanlist("ban-me");
|
|
expect(isBuildBanned("ban-me")).toBe(true);
|
|
removeBuildFromBanlist("ban-me");
|
|
expect(isBuildBanned("ban-me")).toBe(false);
|
|
removeBuildFromBanlist("ban-me");
|
|
expect(isBuildBanned("ban-me")).toBe(false);
|
|
});
|
|
});
|