Parcourir la source

fix: support skipping TLS certificate verification for Redis

Add REDIS_TLS_REJECT_UNAUTHORIZED env variable to allow connections
to Redis servers using self-signed or shared certificates.

- Add buildTlsConfig() function for unified TLS configuration
- Support rejectUnauthorized option in both getRedisClient and buildRedisOptionsForUrl
- Update .env.example with new configuration option
- Update README.md and README.en.md documentation
Abner il y a 1 mois
Parent
commit
cea706984b
4 fichiers modifiés avec 32 ajouts et 14 suppressions
  1. 3 0
      .env.example
  2. 1 0
      README.en.md
  3. 1 0
      README.md
  4. 27 14
      src/lib/redis/client.ts

+ 3 - 0
.env.example

@@ -36,6 +36,9 @@ ENABLE_SECURE_COOKIES=true
 # - Fail Open 策略:Redis 不可用时自动降级,不影响服务可用性
 ENABLE_RATE_LIMIT=true                  # 是否启用限流功能(默认:true)
 REDIS_URL=redis://localhost:6379        # Redis 连接地址(Docker 部署使用 redis://redis:6379,支持 rediss:// TLS)
+REDIS_TLS_REJECT_UNAUTHORIZED=true      # 是否验证 Redis TLS 证书(默认:true)
+                                        # 设置为 false 可跳过证书验证,用于自签证书或共享证书场景
+                                        # 仅在 rediss:// 协议时生效
 
 # Session 配置
 SESSION_TTL=300                         # Session 过期时间(秒,默认 300 = 5 分钟)

+ 1 - 0
README.en.md

@@ -246,6 +246,7 @@ Docker Compose is the **preferred deployment method** — it automatically provi
 | `DSN`                                      | -                        | PostgreSQL connection string, e.g., `postgres://user:pass@host:5432/db`.                             |
 | `AUTO_MIGRATE`                             | `true`                   | Executes Drizzle migrations on startup; consider disabling in production for manual control.         |
 | `REDIS_URL`                                | `redis://localhost:6379` | Redis endpoint, supports `rediss://` for TLS providers.                                              |
+| `REDIS_TLS_REJECT_UNAUTHORIZED`            | `true`                   | Validate Redis TLS certificates; set `false` to skip (for self-signed/shared certs).                 |
 | `ENABLE_RATE_LIMIT`                        | `true`                   | Toggles multi-dimensional rate limiting; Fail-Open handles Redis outages gracefully.                 |
 | `SESSION_TTL`                              | `300`                    | Session cache window (seconds) that drives vendor reuse.                                             |
 | `ENABLE_SECURE_COOKIES`                    | `true`                   | Browsers require HTTPS for Secure cookies; set to `false` when serving plain HTTP outside localhost. |

+ 1 - 0
README.md

@@ -249,6 +249,7 @@ Docker Compose 是**首选部署方式**,自动配置数据库、Redis 和应
 | `DSN`                                      | -                        | PostgreSQL 连接串,如 `postgres://user:pass@host:5432/db`.                   |
 | `AUTO_MIGRATE`                             | `true`                   | 启动时自动执行 Drizzle 迁移;生产环境可关闭以人工控制。                      |
 | `REDIS_URL`                                | `redis://localhost:6379` | Redis 地址,支持 `rediss://` 用于 TLS。                                      |
+| `REDIS_TLS_REJECT_UNAUTHORIZED`            | `true`                   | 是否验证 Redis TLS 证书;设为 `false` 可跳过验证(用于自签/共享证书)。      |
 | `ENABLE_RATE_LIMIT`                        | `true`                   | 控制多维限流开关;Fail-Open 策略在 Redis 不可用时自动降级。                  |
 | `SESSION_TTL`                              | `300`                    | Session 缓存时间(秒),影响供应商复用策略。                                 |
 | `ENABLE_SECURE_COOKIES`                    | `true`                   | 仅 HTTPS 场景能设置 Secure Cookie;HTTP 访问(非 localhost)需改为 `false`。 |

+ 27 - 14
src/lib/redis/client.ts

@@ -15,10 +15,29 @@ function maskRedisUrl(redisUrl: string) {
   }
 }
 
+/**
+ * Build TLS configuration for Redis connection.
+ * Supports skipping certificate verification via REDIS_TLS_REJECT_UNAUTHORIZED env.
+ */
+function buildTlsConfig(redisUrl: string): Record<string, unknown> {
+  const rejectUnauthorized = process.env.REDIS_TLS_REJECT_UNAUTHORIZED !== "false";
+
+  try {
+    const url = new URL(redisUrl);
+    return {
+      host: url.hostname,
+      rejectUnauthorized,
+    };
+  } catch {
+    return { rejectUnauthorized };
+  }
+}
+
 /**
  * Build ioredis connection options with protocol-based TLS detection.
  * - When `rediss://` is used, explicitly enable TLS via `tls: {}`
  * - When `redis://` is used, keep plaintext TCP (no TLS option)
+ * - Supports REDIS_TLS_REJECT_UNAUTHORIZED env to skip certificate verification
  */
 export function buildRedisOptionsForUrl(redisUrl: string) {
   const isTLS = (() => {
@@ -46,7 +65,7 @@ export function buildRedisOptionsForUrl(redisUrl: string) {
   } as const;
 
   // Explicit TLS config for Upstash and other managed Redis providers
-  const tlsOptions = isTLS ? { tls: {} as Record<string, unknown> } : {};
+  const tlsOptions = isTLS ? { tls: buildTlsConfig(redisUrl) } : {};
 
   return { isTLS, options: { ...baseOptions, ...tlsOptions } };
 }
@@ -89,20 +108,14 @@ export function getRedisClient(): Redis | null {
       },
     };
 
-    // 2. 如果使用 rediss://,则添加显式的 TLS 和 SNI (host) 配置
+    // 2. 如果使用 rediss://,则添加显式的 TLS 配置(支持跳过证书验证)
     if (useTls) {
-      logger.info("[Redis] Using TLS connection (rediss://)", { redisUrl: safeRedisUrl });
-      try {
-        // 从 URL 中解析 hostname,用于 SNI
-        const url = new URL(redisUrl);
-        redisOptions.tls = {
-          host: url.hostname,
-        };
-      } catch (e) {
-        logger.error("[Redis] Failed to parse REDIS_URL for TLS host:", e);
-        // 如果 URL 解析失败,回退
-        redisOptions.tls = {};
-      }
+      const rejectUnauthorized = process.env.REDIS_TLS_REJECT_UNAUTHORIZED !== "false";
+      logger.info("[Redis] Using TLS connection (rediss://)", {
+        redisUrl: safeRedisUrl,
+        rejectUnauthorized,
+      });
+      redisOptions.tls = buildTlsConfig(redisUrl);
     }
 
     // 3. 使用组合后的配置创建客户端