forked from EduCraft/curriculum-project-hub
feat: secure organization Feishu credentials
This commit is contained in:
@@ -3,6 +3,10 @@ import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { ProviderConnectionService, verifyStoredProviderEnvelopes } from "../../src/connections/providerConnections.js";
|
||||
import {
|
||||
FeishuApplicationConnectionService,
|
||||
verifyStoredFeishuApplicationEnvelopes,
|
||||
} from "../../src/connections/feishuApplicationConnections.js";
|
||||
import { LocalSecretEnvelope, loadLocalSecretKeyringFile, type SecretEnvelopeV1 } from "../../src/security/secretEnvelope.js";
|
||||
import { LOCAL_KEK_ROTATION_ADVISORY_LOCK, rotateLocalKek } from "../../src/security/localKekRotation.js";
|
||||
import { DEFAULT_ORG_ID, prisma, resetDb } from "./helpers.js";
|
||||
@@ -36,10 +40,21 @@ describe("local KEK rotation", () => {
|
||||
baseUrl: "https://provider.example/api",
|
||||
authToken: "credential-stays-encrypted",
|
||||
});
|
||||
await new FeishuApplicationConnectionService(prisma, oldSecrets, async () => {}).rotateCustomerApplication({
|
||||
organizationId: DEFAULT_ORG_ID,
|
||||
actorUserId: "rotation-admin",
|
||||
appId: "cli_rotation",
|
||||
appSecret: "feishu-credential-stays-encrypted",
|
||||
botOpenId: "ou_rotation_bot",
|
||||
});
|
||||
const before = await prisma.providerCredentialVersion.findFirstOrThrow({
|
||||
include: { connection: true },
|
||||
});
|
||||
const beforeEnvelope = before.envelope as unknown as SecretEnvelopeV1;
|
||||
const beforeFeishu = await prisma.feishuApplicationCredentialVersion.findFirstOrThrow({
|
||||
include: { connection: true },
|
||||
});
|
||||
const beforeFeishuEnvelope = beforeFeishu.envelope as unknown as SecretEnvelopeV1;
|
||||
const directory = await mkdtemp(join(tmpdir(), "cph-kek-rotation-"));
|
||||
temporaryDirectories.push(directory);
|
||||
const keyringFile = join(directory, "keyring.json");
|
||||
@@ -99,29 +114,47 @@ describe("local KEK rotation", () => {
|
||||
expect(result).toMatchObject({
|
||||
previousActiveKeyId: "local-old",
|
||||
activeKeyId: expect.stringMatching(/^local-20260710T123456Z-/),
|
||||
rewrapped: 1,
|
||||
verified: 1,
|
||||
rewrapped: 2,
|
||||
verified: 2,
|
||||
});
|
||||
const after = await prisma.providerCredentialVersion.findUniqueOrThrow({ where: { id: before.id } });
|
||||
const afterEnvelope = after.envelope as unknown as SecretEnvelopeV1;
|
||||
expect(after.keyId).toBe(result.activeKeyId);
|
||||
expect(afterEnvelope.payload).toEqual(beforeEnvelope.payload);
|
||||
expect(afterEnvelope.wrappedDek).not.toEqual(beforeEnvelope.wrappedDek);
|
||||
const afterFeishu = await prisma.feishuApplicationCredentialVersion.findUniqueOrThrow({
|
||||
where: { id: beforeFeishu.id },
|
||||
});
|
||||
const afterFeishuEnvelope = afterFeishu.envelope as unknown as SecretEnvelopeV1;
|
||||
expect(afterFeishu.keyId).toBe(result.activeKeyId);
|
||||
expect(afterFeishuEnvelope.payload).toEqual(beforeFeishuEnvelope.payload);
|
||||
expect(afterFeishuEnvelope.wrappedDek).not.toEqual(beforeFeishuEnvelope.wrappedDek);
|
||||
const keyringSource = await readFile(keyringFile, "utf8");
|
||||
expect(keyringSource).toContain("local-old");
|
||||
expect(keyringSource).toContain(result.activeKeyId);
|
||||
expect(keyringSource).not.toContain("credential-stays-encrypted");
|
||||
expect(keyringSource).not.toContain("feishu-credential-stays-encrypted");
|
||||
|
||||
const rotatedSecrets = new LocalSecretEnvelope(await loadLocalSecretKeyringFile(keyringFile));
|
||||
await expect(verifyStoredProviderEnvelopes(prisma, rotatedSecrets)).resolves.toBe(1);
|
||||
await expect(verifyStoredFeishuApplicationEnvelopes(prisma, rotatedSecrets)).resolves.toBe(1);
|
||||
expect(() => oldSecrets.decryptJson({
|
||||
purpose: "provider-connection",
|
||||
organizationId: before.connection.organizationId,
|
||||
connectionId: before.connection.id,
|
||||
secretVersionId: before.id,
|
||||
}, afterEnvelope)).toThrow("secret envelope key is unavailable");
|
||||
expect(() => oldSecrets.decryptJson({
|
||||
purpose: "feishu-application",
|
||||
organizationId: beforeFeishu.connection.organizationId,
|
||||
connectionId: beforeFeishu.connection.id,
|
||||
secretVersionId: beforeFeishu.id,
|
||||
}, afterFeishuEnvelope)).toThrow("secret envelope key is unavailable");
|
||||
await expect(prisma.auditEntry.count({
|
||||
where: { action: "provider_secret.kek_rewrapped", organizationId: DEFAULT_ORG_ID },
|
||||
})).resolves.toBe(1);
|
||||
await expect(prisma.auditEntry.count({
|
||||
where: { action: "feishu_application_secret.kek_rewrapped", organizationId: DEFAULT_ORG_ID },
|
||||
})).resolves.toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user