fix: allow degraded startup without Zulip keys
This commit is contained in:
@@ -66,7 +66,8 @@ ZULIP_MESSAGE_MAX_LENGTH=10000
|
|||||||
ZULIP_CONTENT_FILTER_ENABLED=true
|
ZULIP_CONTENT_FILTER_ENABLED=true
|
||||||
|
|
||||||
# Realtime server
|
# Realtime server
|
||||||
WEBSOCKET_PORT=3000
|
# The native WebSocket server listens separately from the REST API.
|
||||||
|
WEBSOCKET_PORT=3001
|
||||||
WEBSOCKET_NAMESPACE=/game
|
WEBSOCKET_NAMESPACE=/game
|
||||||
WEBSOCKET_PING_INTERVAL=25000
|
WEBSOCKET_PING_INTERVAL=25000
|
||||||
WEBSOCKET_PING_TIMEOUT=5000
|
WEBSOCKET_PING_TIMEOUT=5000
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export class ApiKeySecurityService implements IApiKeySecurityService {
|
|||||||
private readonly SECURITY_LOG_RETENTION = 30 * 24 * 3600; // 30天
|
private readonly SECURITY_LOG_RETENTION = 30 * 24 * 3600; // 30天
|
||||||
|
|
||||||
// 加密密钥(生产环境应从环境变量或密钥管理服务获取)
|
// 加密密钥(生产环境应从环境变量或密钥管理服务获取)
|
||||||
private readonly encryptionKey: Buffer;
|
private readonly encryptionKey: Buffer | null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject('REDIS_SERVICE')
|
@Inject('REDIS_SERVICE')
|
||||||
@@ -155,11 +155,21 @@ export class ApiKeySecurityService implements IApiKeySecurityService {
|
|||||||
) {
|
) {
|
||||||
// 加密密钥必须由部署环境提供,不允许回退到共享的固定密钥。
|
// 加密密钥必须由部署环境提供,不允许回退到共享的固定密钥。
|
||||||
const keyFromEnv = process.env.ZULIP_API_KEY_ENCRYPTION_KEY;
|
const keyFromEnv = process.env.ZULIP_API_KEY_ENCRYPTION_KEY;
|
||||||
|
const degradedModeEnabled = process.env.ZULIP_DEGRADED_MODE_ENABLED === 'true';
|
||||||
|
|
||||||
if (!keyFromEnv) {
|
if (!keyFromEnv) {
|
||||||
|
this.encryptionKey = null;
|
||||||
|
|
||||||
|
if (!degradedModeEnabled) {
|
||||||
throw new Error('ZULIP_API_KEY_ENCRYPTION_KEY未配置');
|
throw new Error('ZULIP_API_KEY_ENCRYPTION_KEY未配置');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.warn(
|
||||||
|
'Zulip降级模式已启用且未配置API Key加密密钥,API Key加密存取功能不可用',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 如果环境变量是十六进制格式,使用hex解析;否则使用utf8。
|
// 如果环境变量是十六进制格式,使用hex解析;否则使用utf8。
|
||||||
if (/^[0-9a-fA-F]+$/.test(keyFromEnv) && keyFromEnv.length === 64) {
|
if (/^[0-9a-fA-F]+$/.test(keyFromEnv) && keyFromEnv.length === 64) {
|
||||||
this.encryptionKey = Buffer.from(keyFromEnv, 'hex');
|
this.encryptionKey = Buffer.from(keyFromEnv, 'hex');
|
||||||
@@ -736,10 +746,11 @@ export class ApiKeySecurityService implements IApiKeySecurityService {
|
|||||||
iv: string;
|
iv: string;
|
||||||
authTag: string;
|
authTag: string;
|
||||||
} {
|
} {
|
||||||
|
const encryptionKey = this.requireEncryptionKey();
|
||||||
const iv = crypto.randomBytes(this.IV_LENGTH);
|
const iv = crypto.randomBytes(this.IV_LENGTH);
|
||||||
const cipher = crypto.createCipheriv(
|
const cipher = crypto.createCipheriv(
|
||||||
this.ENCRYPTION_ALGORITHM,
|
this.ENCRYPTION_ALGORITHM,
|
||||||
this.encryptionKey,
|
encryptionKey,
|
||||||
iv
|
iv
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -764,11 +775,12 @@ export class ApiKeySecurityService implements IApiKeySecurityService {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private decrypt(encryptedData: string, ivHex: string, authTagHex: string): string {
|
private decrypt(encryptedData: string, ivHex: string, authTagHex: string): string {
|
||||||
|
const encryptionKey = this.requireEncryptionKey();
|
||||||
const iv = Buffer.from(ivHex, 'hex');
|
const iv = Buffer.from(ivHex, 'hex');
|
||||||
const authTag = Buffer.from(authTagHex, 'hex');
|
const authTag = Buffer.from(authTagHex, 'hex');
|
||||||
const decipher = crypto.createDecipheriv(
|
const decipher = crypto.createDecipheriv(
|
||||||
this.ENCRYPTION_ALGORITHM,
|
this.ENCRYPTION_ALGORITHM,
|
||||||
this.encryptionKey,
|
encryptionKey,
|
||||||
iv
|
iv
|
||||||
);
|
);
|
||||||
decipher.setAuthTag(authTag);
|
decipher.setAuthTag(authTag);
|
||||||
@@ -779,6 +791,22 @@ export class ApiKeySecurityService implements IApiKeySecurityService {
|
|||||||
return decrypted;
|
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格式
|
* 验证API Key格式
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -349,7 +349,11 @@ export function validateZulipConfig(
|
|||||||
// 生产环境特殊验证
|
// 生产环境特殊验证
|
||||||
if (isProduction) {
|
if (isProduction) {
|
||||||
if (!config.security.apiKeyEncryptionKey) {
|
if (!config.security.apiKeyEncryptionKey) {
|
||||||
|
if (config.errorHandling.degradedModeEnabled) {
|
||||||
|
warnings.push('降级模式未配置API Key加密密钥,Zulip API Key加密存取功能不可用');
|
||||||
|
} else {
|
||||||
errors.push('生产环境必须配置API Key加密密钥 (ZULIP_API_KEY_ENCRYPTION_KEY)');
|
errors.push('生产环境必须配置API Key加密密钥 (ZULIP_API_KEY_ENCRYPTION_KEY)');
|
||||||
|
}
|
||||||
} else if (config.security.apiKeyEncryptionKey.length < 32) {
|
} else if (config.security.apiKeyEncryptionKey.length < 32) {
|
||||||
errors.push('API Key加密密钥长度必须至少32字符');
|
errors.push('API Key加密密钥长度必须至少32字符');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user