fengling-gateway/.planning/codebase/CONCERNS.md
2026-02-28 15:44:16 +08:00

499 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# YARP 网关项目技术债务与关注点分析
> 分析日期2026-02-28
> 分析范围:核心代码、配置、数据访问层
---
## 一、严重安全问题 🔴
### 1.1 硬编码凭据泄露
**文件位置:** `src/Config/RedisConfig.cs:5`
```csharp
public string ConnectionString { get; set; } = "81.68.223.70:16379,password=sl52788542";
```
**问题描述:** Redis 连接字符串包含明文密码,直接硬编码在源代码中。此代码提交到版本控制系统后,密码将永久暴露。
**影响范围:**
- 攻击者获取代码后可直接访问 Redis 服务
- 违反安全合规要求如等保、GDPR
**改进建议:**
```csharp
// 使用环境变量或密钥管理服务
public string ConnectionString { get; set; } =
Environment.GetEnvironmentVariable("REDIS_CONNECTION_STRING") ?? string.Empty;
```
---
### 1.2 配置文件凭据泄露
**文件位置:** `src/appsettings.json:19,28`
```json
"DefaultConnection": "Host=81.68.223.70;Port=15432;Database=fengling_gateway;Username=movingsam;Password=sl52788542"
"ConnectionString": "81.68.223.70:6379"
```
**问题描述:** 数据库连接字符串和 Redis 配置包含明文凭据,且这些配置文件通常会被提交到 Git 仓库。
**改进建议:**
- 使用 `appsettings.Development.json` 存储开发环境配置,并加入 `.gitignore`
- 生产环境使用环境变量或 Azure Key Vault / AWS Secrets Manager
- 敏感配置使用 `dotnet user-secrets` 管理
---
### 1.3 JWT 令牌未验证
**文件位置:** `src/Middleware/JwtTransformMiddleware.cs:39-40`
```csharp
var jwtHandler = new JwtSecurityTokenHandler();
var jwtToken = jwtHandler.ReadJwtToken(token);
```
**问题描述:** 中间件仅**读取**JWT令牌未进行签名验证、过期检查或颁发者验证。攻击者可伪造任意JWT令牌。
**影响范围:**
- 任何人可伪造租户ID、用户ID、角色信息
- 可冒充任意用户访问系统
**改进建议:**
```csharp
// 应使用标准的 JWT 验证流程
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _jwtConfig.Authority,
ValidAudience = _jwtConfig.Audience,
IssuerSigningKey = GetSigningKey() // 从配置获取公钥
};
var principal = jwtHandler.ValidateToken(token, validationParameters, out _);
```
---
### 1.4 API 端点无认证保护
**文件位置:** `src/Controllers/GatewayConfigController.cs``src/Controllers/PendingServicesController.cs`
**问题描述:** 所有管理API端点均未添加 `[Authorize]` 特性,任何人可直接调用:
- `POST /api/gateway/tenants` - 创建租户
- `POST /api/gateway/routes` - 创建路由
- `POST /api/gateway/clusters/{clusterId}/instances` - 添加服务实例
- `POST /api/gateway/pending-services/{id}/assign` - 分配服务
**影响范围:**
- 攻击者可随意修改网关配置
- 可注入恶意服务地址进行流量劫持
**改进建议:**
```csharp
[ApiController]
[Route("api/gateway")]
[Authorize(Roles = "Admin")] // 添加认证要求
public class GatewayConfigController : ControllerBase
```
---
### 1.5 租户ID头部信任问题
**文件位置:** `src/Middleware/TenantRoutingMiddleware.cs:25`
```csharp
var tenantId = context.Request.Headers["X-Tenant-Id"].FirstOrDefault();
```
**问题描述:** 直接从请求头读取租户ID未与JWT中的租户声明进行比对验证。攻击者可伪造 `X-Tenant-Id` 头部访问其他租户数据。
**改进建议:**
```csharp
// 从已验证的 JWT claims 中获取租户ID
var jwtTenantId = context.User.FindFirst("tenant")?.Value;
var headerTenantId = context.Request.Headers["X-Tenant-Id"].FirstOrDefault();
if (!string.IsNullOrEmpty(jwtTenantId) && jwtTenantId != headerTenantId)
{
// 记录安全事件
_logger.LogWarning("Tenant ID mismatch: JWT={JwtTenant}, Header={HeaderTenant}",
jwtTenantId, headerTenantId);
context.Response.StatusCode = StatusCodes.Status403Forbidden;
return;
}
```
---
## 二、技术债务 🟠
### 2.1 ID生成策略问题
**文件位置:** `src/Controllers/GatewayConfigController.cs:484-487`
```csharp
private long GenerateId()
{
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
}
```
**问题描述:** 使用时间戳毫秒生成ID在高并发场景下可能产生重复ID。
**改进建议:**
- 使用数据库自增主键(已有配置)
- 或使用雪花算法Snowflake ID
- 或使用 `Guid.NewGuid()`
---
### 2.2 Redis连接重复初始化
**文件位置:**
- `src/Program.cs:39-60` - 注册 `IConnectionMultiplexer`
- `src/Services/RedisConnectionManager.cs:25-46` - 内部再次创建连接
**问题描述:** Redis连接被初始化两次造成资源浪费和配置不一致风险。
**改进建议:**
```csharp
// Program.cs 中只注册一次
builder.Services.AddSingleton<IRedisConnectionManager, RedisConnectionManager>();
builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
sp.GetRequiredService<IRedisConnectionManager>().GetConnection());
```
---
### 2.3 DTO 内嵌定义
**文件位置:** `src/Controllers/GatewayConfigController.cs:444-481`
**问题描述:** 多个DTO类定义在Controller内部不利于复用和测试。
**改进建议:**
- 将 DTO 移至 `src/DTOs/``src/Models/Dto/` 目录
- 使用 Auto Mapper 或 Mapster 进行对象映射
---
### 2.4 魔法数字
**文件位置:** 多处使用数字常量
```csharp
// RouteCache.cs:99
.Where(r => r.Status == 1 && !r.IsDeleted)
// GatewayConfigController.cs:239
route.Status = 1;
// KubernetesPendingSyncService.cs:13
private readonly TimeSpan _syncInterval = TimeSpan.FromSeconds(30);
```
**问题描述:** 状态值、超时时间等使用硬编码数字,降低代码可读性和可维护性。
**改进建议:**
```csharp
// 定义常量或枚举
public static class RouteStatus
{
public const int Active = 1;
public const int Inactive = 0;
}
public static class ServiceConstants
{
public static readonly TimeSpan DefaultSyncInterval = TimeSpan.FromSeconds(30);
}
```
---
### 2.5 异步方法命名不一致
**文件位置:** `src/Config/DatabaseRouteConfigProvider.cs:23`
```csharp
_ = LoadConfigAsync(); // Fire-and-forget without await
```
**问题描述:** 构造函数中调用异步方法但未等待完成,可能导致初始化竞态条件。
**改进建议:**
- 使用工厂模式异步初始化
- 或在 `Program.cs` 中显式调用初始化方法
---
## 三、性能瓶颈风险 🟡
### 3.1 负载均衡锁竞争
**文件位置:** `src/LoadBalancing/DistributedWeightedRoundRobinPolicy.cs:48-53`
```csharp
var lockAcquired = db.StringSet(
lockKey,
lockValue,
TimeSpan.FromMilliseconds(500),
When.NotExists
);
```
**问题描述:** 每次请求都需要获取Redis分布式锁高并发下会成为瓶颈。锁获取失败时降级策略不可靠。
**影响:**
- 单集群QPS受限
- Redis延迟增加时网关吞吐量下降
**改进建议:**
- 考虑使用本地缓存 + 定期同步策略
- 或使用一致性哈希算法避免锁需求
- 增加本地计数器作为快速路径
---
### 3.2 路由缓存全量加载
**文件位置:** `src/Services/RouteCache.cs:94-137`
```csharp
var routes = await db.TenantRoutes
.Where(r => r.Status == 1 && !r.IsDeleted)
.ToListAsync();
```
**问题描述:** 每次重载都清空并重新加载所有路由,大数据量下性能差。
**改进建议:**
- 实现增量更新机制
- 使用版本号比对只更新变更项
- 添加分页加载支持
---
### 3.3 数据库查询未优化
**文件位置:** `src/Controllers/GatewayConfigController.cs:145-148`
```csharp
var currentRouteVersion = await db.TenantRoutes
.OrderByDescending(r => r.Version)
.Select(r => r.Version)
.FirstOrDefaultAsync(stoppingToken);
```
**问题描述:** 每次轮询都执行 `ORDER BY` 查询获取最大版本号,缺少索引优化。
**改进建议:**
```sql
-- 添加索引
CREATE INDEX IX_TenantRoutes_Version ON "TenantRoutes" ("Version" DESC);
-- 或使用 MAX 聚合
SELECT MAX("Version") FROM "TenantRoutes";
```
---
### 3.4 PostgreSQL NOTIFY 连接管理
**文件位置:** `src/Data/GatewayDbContext.cs:72-75`
```csharp
using var connection = new NpgsqlConnection(connectionString);
connection.Open();
using var cmd = new NpgsqlCommand($"NOTIFY {ConfigNotifyChannel.GatewayConfigChanged}", connection);
cmd.ExecuteNonQuery();
```
**问题描述:** 每次保存变更都创建新的数据库连接发送通知,连接开销大。
**改进建议:**
- 使用连接池中的连接
- 或复用 `PgSqlConfigChangeListener` 中的连接发送通知
---
## 四、脆弱区域 🟠
### 4.1 租户路由外键约束
**文件位置:** `src/Migrations/20260201120312_InitialCreate.cs:83-89`
```csharp
table.ForeignKey(
name: "FK_TenantRoutes_Tenants_TenantCode",
column: x => x.TenantCode,
principalTable: "Tenants",
principalColumn: "TenantCode",
onDelete: ReferentialAction.Restrict);
```
**问题描述:** `TenantRoutes.TenantCode` 有外键约束,但全局路由(`IsGlobal=true`)时 `TenantCode` 可为空字符串,可能导致数据一致性问题。
**改进建议:**
- 全局路由使用特定的占位符(如 "GLOBAL"
- 或修改外键约束为条件约束
---
### 4.2 健康检查配置硬编码
**文件位置:** `src/Config/DatabaseClusterConfigProvider.cs:77-86`
```csharp
HealthCheck = new HealthCheckConfig
{
Active = new ActiveHealthCheckConfig
{
Enabled = true,
Interval = TimeSpan.FromSeconds(30),
Timeout = TimeSpan.FromSeconds(5),
Path = "/health"
}
}
```
**问题描述:** 健康检查路径和间隔硬编码,不同服务可能需要不同的健康检查配置。
**改进建议:**
- 将健康检查配置存储在数据库
- 或在模型中添加健康检查配置字段
---
### 4.3 端口选择逻辑
**文件位置:** `src/Controllers/PendingServicesController.cs:119-120`
```csharp
var discoveredPorts = JsonSerializer.Deserialize<List<int>>(pendingService.DiscoveredPorts) ?? new List<int>();
var primaryPort = discoveredPorts.FirstOrDefault() > 0 ? discoveredPorts.First() : 80;
```
**问题描述:** 简单选择第一个端口作为主端口,可能不适合所有服务场景。
**改进建议:**
- 支持端口选择策略配置
- 优先选择知名端口(如 80, 443, 8080
- 允许用户在审批时选择端口
---
### 4.4 异常处理不完整
**文件位置:** `src/Services/PgSqlConfigChangeListener.cs:59-62`
```csharp
catch (Exception ex)
{
_logger.LogError(ex, "Failed to initialize PgSql listener");
// 未重试或终止服务
}
```
**问题描述:** 初始化失败后仅记录日志,服务继续运行但功能不完整。
**改进建议:**
```csharp
catch (Exception ex)
{
_logger.LogError(ex, "Failed to initialize PgSql listener, retrying in 5 seconds...");
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
await InitializeListenerAsync(stoppingToken); // 重试
}
```
---
### 4.5 状态变更无事务保护
**文件位置:** `src/Controllers/PendingServicesController.cs:137-145`
```csharp
db.ServiceInstances.Add(newInstance);
pendingService.Status = (int)PendingServiceStatus.Approved;
// ...
await db.SaveChangesAsync();
```
**问题描述:** 创建实例和更新状态在同一事务中,但如果缓存重载失败,数据可能不一致。
**改进建议:**
- 使用 TransactionScope 或数据库事务明确边界
- 添加补偿机制处理失败情况
---
## 五、可维护性问题 🟡
### 5.1 日志结构不统一
**问题描述:** 日志消息格式不统一,有的包含结构化数据,有的仅是文本。
**改进建议:**
- 制定统一的日志格式规范
- 使用结构化日志模板:`LogInformation("Operation {Operation} completed for {Entity} with ID {Id}", "Create", "Route", route.Id)`
---
### 5.2 缺少单元测试
**问题描述:** 项目中未发现测试项目,核心逻辑缺少测试覆盖。
**改进建议:**
- 创建 `tests/YarpGateway.Tests/` 测试项目
- 对以下核心组件编写单元测试:
- `RouteCache` - 路由查找逻辑
- `JwtTransformMiddleware` - JWT 解析逻辑
- `DistributedWeightedRoundRobinPolicy` - 负载均衡算法
---
### 5.3 配置验证缺失
**文件位置:** `src/Config/JwtConfig.cs`, `src/Config/RedisConfig.cs`
**问题描述:** 配置类没有验证逻辑,无效配置可能导致运行时错误。
**改进建议:**
```csharp
public class JwtConfig : IValidatableObject
{
public string Authority { get; set; } = string.Empty;
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrWhiteSpace(Authority))
yield return new ValidationResult("Authority is required", new[] { nameof(Authority) });
}
}
```
---
## 六、改进优先级建议
| 优先级 | 问题 | 风险等级 | 建议处理时间 |
|--------|------|----------|--------------|
| P0 | 硬编码凭据泄露 | 严重 | 立即修复 |
| P0 | JWT未验证 | 严重 | 立即修复 |
| P0 | API无认证保护 | 严重 | 立即修复 |
| P1 | 租户ID信任问题 | 高 | 1周内 |
| P1 | ID生成策略 | 高 | 1周内 |
| P2 | 负载均衡锁竞争 | 中 | 2周内 |
| P2 | 路由缓存优化 | 中 | 2周内 |
| P3 | DTO内嵌定义 | 低 | 1个月内 |
| P3 | 缺少单元测试 | 低 | 持续改进 |
---
## 七、总结
本项目存在多个**严重安全漏洞**,主要涉及:
1. 敏感信息硬编码
2. 认证授权缺失
3. 输入验证不足
技术债务主要集中在代码组织、异常处理和性能优化方面。建议优先处理安全相关问题,然后逐步优化性能和可维护性。
---
*文档由自动化分析生成,建议人工复核后纳入迭代计划。*