Files
makemd/server/src/core/security/CertsMonitorService.ts

88 lines
3.2 KiB
TypeScript
Raw Normal View History

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.`);
}
}