fix: allow degraded startup without Zulip keys

This commit is contained in:
ANG-Server
2026-07-20 23:43:23 +08:00
parent a72920d1e0
commit 77302354ac
3 changed files with 39 additions and 6 deletions

View File

@@ -66,7 +66,8 @@ ZULIP_MESSAGE_MAX_LENGTH=10000
ZULIP_CONTENT_FILTER_ENABLED=true
# Realtime server
WEBSOCKET_PORT=3000
# The native WebSocket server listens separately from the REST API.
WEBSOCKET_PORT=3001
WEBSOCKET_NAMESPACE=/game
WEBSOCKET_PING_INTERVAL=25000
WEBSOCKET_PING_TIMEOUT=5000

View File

@@ -147,7 +147,7 @@ export class ApiKeySecurityService implements IApiKeySecurityService {
private readonly SECURITY_LOG_RETENTION = 30 * 24 * 3600; // 30天
// 加密密钥(生产环境应从环境变量或密钥管理服务获取)
private readonly encryptionKey: Buffer;
private readonly encryptionKey: Buffer | null;
constructor(
@Inject('REDIS_SERVICE')
@@ -155,9 +155,19 @@ export class ApiKeySecurityService implements IApiKeySecurityService {
) {
// 加密密钥必须由部署环境提供,不允许回退到共享的固定密钥。
const keyFromEnv = process.env.ZULIP_API_KEY_ENCRYPTION_KEY;
const degradedModeEnabled = process.env.ZULIP_DEGRADED_MODE_ENABLED === 'true';
if (!keyFromEnv) {
throw new Error('ZULIP_API_KEY_ENCRYPTION_KEY未配置');
this.encryptionKey = null;
if (!degradedModeEnabled) {
throw new Error('ZULIP_API_KEY_ENCRYPTION_KEY未配置');
}
this.logger.warn(
'Zulip降级模式已启用且未配置API Key加密密钥API Key加密存取功能不可用',
);
return;
}
// 如果环境变量是十六进制格式使用hex解析否则使用utf8。
@@ -736,10 +746,11 @@ export class ApiKeySecurityService implements IApiKeySecurityService {
iv: string;
authTag: string;
} {
const encryptionKey = this.requireEncryptionKey();
const iv = crypto.randomBytes(this.IV_LENGTH);
const cipher = crypto.createCipheriv(
this.ENCRYPTION_ALGORITHM,
this.encryptionKey,
encryptionKey,
iv
);
@@ -764,11 +775,12 @@ export class ApiKeySecurityService implements IApiKeySecurityService {
* @private
*/
private decrypt(encryptedData: string, ivHex: string, authTagHex: string): string {
const encryptionKey = this.requireEncryptionKey();
const iv = Buffer.from(ivHex, 'hex');
const authTag = Buffer.from(authTagHex, 'hex');
const decipher = crypto.createDecipheriv(
this.ENCRYPTION_ALGORITHM,
this.encryptionKey,
encryptionKey,
iv
);
decipher.setAuthTag(authTag);
@@ -779,6 +791,22 @@ export class ApiKeySecurityService implements IApiKeySecurityService {
return decrypted;
}
/**
* 获取已配置的加密密钥。
*
* 降级模式允许服务在没有密钥时启动但涉及Zulip API Key明文的操作仍必须失败
* 以避免使用临时密钥导致数据在重启后无法解密。
*/
private requireEncryptionKey(): Buffer {
if (!this.encryptionKey) {
throw new Error(
'ZULIP_API_KEY_ENCRYPTION_KEY未配置Zulip API Key加密存取功能不可用',
);
}
return this.encryptionKey;
}
/**
* 验证API Key格式
*

View File

@@ -349,7 +349,11 @@ export function validateZulipConfig(
// 生产环境特殊验证
if (isProduction) {
if (!config.security.apiKeyEncryptionKey) {
errors.push('生产环境必须配置API Key加密密钥 (ZULIP_API_KEY_ENCRYPTION_KEY)');
if (config.errorHandling.degradedModeEnabled) {
warnings.push('降级模式未配置API Key加密密钥Zulip API Key加密存取功能不可用');
} else {
errors.push('生产环境必须配置API Key加密密钥 (ZULIP_API_KEY_ENCRYPTION_KEY)');
}
} else if (config.security.apiKeyEncryptionKey.length < 32) {
errors.push('API Key加密密钥长度必须至少32字符');
}