feat: 初始化项目结构并添加核心功能模块
- 新增文档模板和导航结构 - 实现服务器基础API路由和控制器 - 添加扩展插件配置和前端框架 - 引入多租户和权限管理模块 - 集成日志和数据库配置 - 添加核心业务模型和类型定义
This commit is contained in:
87
server/src/core/security/CertsMonitorService.ts
Normal file
87
server/src/core/security/CertsMonitorService.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import db from '../../config/database';
|
||||
import { logger } from '../../utils/logger';
|
||||
import { LogMaskingService } from '../security/LogMaskingService';
|
||||
import { SemanticLogService } from '../telemetry/SemanticLogService';
|
||||
import https from 'https';
|
||||
|
||||
/**
|
||||
* [BIZ_INF_106] 租户域名证书过期自动预警 (SSL Watch)
|
||||
* @description 核心逻辑:定时扫描租户配置的域名,获取其 SSL 证书到期时间。
|
||||
* 当到期时间小于 15 天时,自动生成预警并归档至语义日志中心。
|
||||
* 通过 [LogMaskingService] 屏蔽域名 PII。
|
||||
*/
|
||||
export class CertsMonitorService {
|
||||
private static readonly ALERT_THRESHOLD_DAYS = 15; // 预警阈值:15天
|
||||
private static readonly CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 检查间隔:24小时
|
||||
|
||||
/**
|
||||
* 初始化定时任务
|
||||
*/
|
||||
static async init() {
|
||||
this.runCheck();
|
||||
setInterval(() => this.runCheck(), this.CHECK_INTERVAL_MS);
|
||||
logger.info(`[SSLWatch] Certificate monitor initialized (Alert threshold: ${this.ALERT_THRESHOLD_DAYS} days)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行证书扫描
|
||||
*/
|
||||
private static async runCheck() {
|
||||
try {
|
||||
// 1. 获取所有活跃租户的自定义域名 (模拟从 cf_tenants 表中获取)
|
||||
const tenants = await db('cf_tenants').select('id', 'custom_domain').whereNotNull('custom_domain');
|
||||
|
||||
for (const tenant of tenants) {
|
||||
if (!tenant.custom_domain) continue;
|
||||
|
||||
const expirationDate = await this.getCertificateExpirationDate(tenant.custom_domain);
|
||||
if (!expirationDate) continue;
|
||||
|
||||
const daysRemaining = Math.ceil((expirationDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (daysRemaining <= this.ALERT_THRESHOLD_DAYS) {
|
||||
await this.reportExpiration(tenant.id, tenant.custom_domain, daysRemaining, expirationDate);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
logger.error(`[SSLWatch] Scan failed: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取证书过期日期 (通过 HTTPS 请求获取证书)
|
||||
*/
|
||||
private static async getCertificateExpirationDate(domain: string): Promise<Date | null> {
|
||||
return new Promise((resolve) => {
|
||||
const options = {
|
||||
hostname: domain,
|
||||
port: 443,
|
||||
method: 'GET',
|
||||
rejectUnauthorized: false, // 允许自签名或过期证书以便检测
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
const cert = (res.socket as any).getPeerCertificate();
|
||||
if (cert && cert.valid_to) {
|
||||
resolve(new Date(cert.valid_to));
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', () => resolve(null));
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报预警信息
|
||||
*/
|
||||
private static async reportExpiration(tenantId: string, domain: string, daysRemaining: number, expirationDate: Date) {
|
||||
const maskedDomain = LogMaskingService.maskData(domain);
|
||||
const message = `🚨 SSL Certificate Expiration Warning: Domain \`${maskedDomain}\` (Tenant: ${tenantId}) will expire in ${daysRemaining} days (on ${expirationDate.toISOString()}). Please renew the certificate immediately.`;
|
||||
|
||||
await SemanticLogService.logSemantic(message, 'ERROR', 'SECURITY_MONITOR');
|
||||
logger.error(`[SSLWatch] Certificate for domain ${maskedDomain} is expiring soon.`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user