- Monorepo: NestJS API (Fastify) + React web (antd/ProComponents) + shared packages - PostgreSQL/Prisma (unified resources, relations graph, audit, events, jobs) - Auth/RBAC/workspace isolation, credential vault (AES-256-GCM) - Servers/SSH, Docker discovery & lifecycle, Job Center - Websites & deployments (git provider credential clone, env, health verification) - Domains/DNS (Cloudflare adapter), certificates (TLS probe), monitoring - Automation (cron/event/webhook HMAC + notifications), AI Gateway, MCP (29 tools) - AI chat window (LLM function-calling + rules fallback, SSE streaming, approvals) - Notes: Vditor WYSIWYG markdown editor, non-blocking autosave - Tests: 35 unit/security tests; UI audit clean (desktop+mobile, deep links)
85 lines
3.1 KiB
TypeScript
85 lines
3.1 KiB
TypeScript
/** 安全回归测试(Phase A,对应 11-TEST-ACCEPTANCE 安全测试项) */
|
|
import { describe, test, expect } from "bun:test";
|
|
import {
|
|
buildSignatureHeader,
|
|
verifyWebhookSignature,
|
|
SIGNATURE_TOLERANCE_MS,
|
|
} from "../apps/api/src/common/security/webhook-signature";
|
|
import { SecurityPolicyService, assertProductionSecrets } from "../apps/api/src/common/security/security-policy.service";
|
|
import { ConfigService } from "../apps/api/src/common/config/config.service";
|
|
|
|
describe("webhook HMAC 签名", () => {
|
|
const secret = "whsec-test";
|
|
const body = JSON.stringify({ event: "deployment.failed" });
|
|
|
|
test("合法签名通过", () => {
|
|
const header = buildSignatureHeader(secret, body);
|
|
expect(verifyWebhookSignature(secret, header, body).ok).toBe(true);
|
|
});
|
|
|
|
test("错误密钥被拒绝", () => {
|
|
const header = buildSignatureHeader(secret, body);
|
|
expect(verifyWebhookSignature("other", header, body).ok).toBe(false);
|
|
});
|
|
|
|
test("篡改请求体被拒绝", () => {
|
|
const header = buildSignatureHeader(secret, body);
|
|
expect(verifyWebhookSignature(secret, header, body + "x").ok).toBe(false);
|
|
});
|
|
|
|
test("过期时间戳(重放)被拒绝", () => {
|
|
const stale = Math.floor((Date.now() - SIGNATURE_TOLERANCE_MS - 60_000) / 1000);
|
|
const header = `t=${stale},v1=${require("node:crypto").createHmac("sha256", secret).update(`${stale}.${body}`).digest("hex")}`;
|
|
const result = verifyWebhookSignature(secret, header, body);
|
|
expect(result.ok).toBe(false);
|
|
expect(result.reason).toContain("replay");
|
|
});
|
|
|
|
test("缺失/畸形签名被拒绝", () => {
|
|
expect(verifyWebhookSignature(secret, undefined, body).ok).toBe(false);
|
|
expect(verifyWebhookSignature(secret, "t=abc,v1=zz", body).ok).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("限流器", () => {
|
|
test("窗口内超过上限被拒绝", () => {
|
|
const svc = new SecurityPolicyService(new ConfigService());
|
|
let allowed = 0;
|
|
for (let i = 0; i < 12; i++) {
|
|
if (svc.checkRate("login:1.2.3.4")) allowed++;
|
|
}
|
|
expect(allowed).toBeLessThanOrEqual(10);
|
|
});
|
|
|
|
test("不同维度键相互隔离", () => {
|
|
const svc = new SecurityPolicyService(new ConfigService());
|
|
expect(svc.checkRate("login:5.5.5.5")).toBe(true);
|
|
expect(svc.checkRate("login:6.6.6.6")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("注册开关", () => {
|
|
test("生产模式下默认关闭", () => {
|
|
const svc = new SecurityPolicyService(new ConfigService());
|
|
// 本测试进程 NODE_ENV 非 production → 默认开启;显式环境由部署配置决定
|
|
const verdict = svc.checkRegistration();
|
|
expect(typeof verdict.allowed).toBe("boolean");
|
|
});
|
|
});
|
|
|
|
describe("生产 secret 校验", () => {
|
|
test("拒绝已知开发默认 APP_SECRET", () => {
|
|
const origEnv = process.env.NODE_ENV;
|
|
const origSecret = process.env.APP_SECRET;
|
|
try {
|
|
process.env.NODE_ENV = "production";
|
|
process.env.APP_SECRET = "dev-devos-app-secret-change-me-please-0123456789";
|
|
process.env.ENCRYPTION_KEY = "a".repeat(64);
|
|
expect(() => new ConfigService()).toThrow(/APP_SECRET/);
|
|
} finally {
|
|
process.env.NODE_ENV = origEnv;
|
|
process.env.APP_SECRET = origSecret;
|
|
}
|
|
});
|
|
});
|