flnxi-Wu/docs/许可证链路安全评估_20260312.md
2026-06-03 18:41:52 +08:00

23 KiB
Raw Blame History

许可证链路安全评估

历史快照提示:本文是 2026-03-12 的安全评估和后续局部复核记录,不代表当前代码、部署脚本和许可证服务的完整最新状态。当前接入和部署以 docs/许可证服务接入说明.md、docs/deployment/部署说明.md、docker-compose.prod.yml 和实际代码为准。

评估日期:2026-03-12

复核更新:2026-05-13。本文件仍是安全评估快照,不是操作入口;执行整改前必须按当前代码、部署文件和实际环境复核。

1. 评估范围

本次评估基于当前仓库代码静态核对,重点覆盖以下链路:

  • 主应用本地许可证缓存、激活、验证、心跳
  • 主应用启动初始化、默认管理员创建
  • license-server 的管理员认证、发证、验证、心跳、免费注册、API Key
  • flnxi-agent 与 flnxi-agent-go 的安装、更新、容器控制能力

不包含:

  • 线上服务器真实配置核验
  • 第三方网络链路抓包
  • 动态渗透测试

2. 总体结论

当前这套方案可以拦住普通误用和低水平滥用,但还不能算“硬防破解”。

核心问题不是单一算法不安全,而是:

  • 本地可控环境里承载了过多关键判断
  • 高权限 Agent 控制面暴露过大
  • 初始化和默认配置偏宽松
  • license-server 虽已完成项目隔离主链路,但仍有少量历史壳路由和文档口径需要继续清理

简单评价:

  • 防普通误用:中等偏上
  • 防低水平破解:中等
  • 防有经验的定向破解:偏弱
  • 运维默认安全性:偏弱

2.1 整改状态更新(截至 2026-03-12)

本轮已完成的收口:

  • 已补齐已确认的许可证执法漏口:部门相关 POST / PATCH / DELETE 与自定义模板 PUT / DELETE / migrate 已补许可证校验
  • 已收紧本地许可证缓存信任边界:本地读取统一改为“解密 -> 解析签名许可证 -> 验签 -> 从签名原文恢复配置”
  • 已修正 /api/license/check:不再只凭本地缓存结构恢复激活,而是先走本地许可证验签
  • 已给 flnxi-agent 与 flnxi-agent-go 增加基础管理认证;Node 版同时补了安全头和 Socket Origin 校验
  • 已把 Agent 认证从“可选启用”收紧到“默认关闭管理面”:未配置 AGENT_AUTH_USERNAME / AGENT_AUTH_PASSWORD 时,仅保留 /api/health
  • 已收紧系统初始化入口:/api/system/init 现在在“无管理员”场景下要求 SYSTEM_INIT_TOKEN,有管理员后只允许管理员执行
  • 已取消固定初始口令:不再内置 88888888,改为“环境变量提供或运行时生成一次性密码”,并在首次登录后强制改密
  • 已去掉主应用与 Node Agent 对许可证服务的明文 HTTP 默认回退,默认改为 https://license.flnxi.com
  • 已把主应用和 Node Agent 接到项目维度公开接口:通过 LICENSE_PROJECT_CODE 命中 license-server 的 /api/license/{projectCode}/... 与 /api/version/{projectCode}/...
  • 已收紧 Agent 安装链路里的内部凭据:Node / Go 两个 Agent 在安装时都会为 PostgreSQL、MinIO、NEXTAUTH_SECRET 生成随机值,不再写死弱口令
  • 已收紧 license-server 部署默认值:docker-compose.yml 改为强制读取关键环境变量,deploy.ps1 在缺少 .env 时会生成随机密钥和一次性管理员初始密码
  • 已把 Go Agent 一键安装脚本和 Docker 示例改为默认只发布到 127.0.0.1,需要局域网直连时必须显式放开
  • 已完成 license-server 后台管理链路的项目隔离:管理员后台真实读写已切到 /api/admin/{projectCode}/...
  • 已完成 license-server 公开验证链路的项目隔离:激活、验证、心跳、免费注册、发证、版本检查都已有项目维度入口
  • 旧的无项目后台接口和无项目公开接口已改为 410 或跳转,不再默认落到某个项目库
  • API Key 已收口到“超管 + 项目维度”路径:/api/admin/{projectCode}/api-keys

本轮已验证的结果:

  • flnxi-agent 已通过 tsc --noEmit
  • flnxi-agent-go 已补齐 go.sum,并通过容器内最小编译验证:go test ./internal/api -run TestDoesNotExist、go test . -run TestDoesNotExist
  • 主应用新增测试已通过:app/activate/__tests__/page.test.tsx、lib/__tests__/system-init.test.ts

本轮仍未解决的重点:

  • 仍有部分部署文档和非主路径脚本保留历史示例值,需要继续清理到统一口径
  • 2026-05-25 复核:docker-compose.prod.yml 中 NEXTAUTH_SECRET 已改为必填校验;客户交付仍必须显式配置强随机值
  • Go Agent install.sh 当前启动容器时没有传入 AGENT_AUTH_USERNAME / AGENT_AUTH_PASSWORD,因此默认只保留 /api/health,不能把该脚本理解为“安装后管理面已可安全使用”
  • 私有化部署场景下,本地宽限期和本地缓存仍只能视为容灾,不应再被当作“硬防破解”
  • 历史旧入口目前仍保留壳路由,虽不再承载真实逻辑,但后续仍建议物理删除

2026-05-30 补充:主应用到 license-server 的 activate / verify / heartbeat / register-free / premium-modules 主链路已要求 LICENSE_REQUEST_SIGNING_SECRET,请求体携带 timestamp、nonce 和 HMAC signature。本文早期关于“主应用请求缺少签名/重放防护”的旧结论已过期,只能作为历史问题来源,不再作为当前事实。

2026-06-01 补充:intranetPenetration 已在内网穿透启动、状态、停止入口做服务端许可证拦截,并且要求付费模块凭证;apiAccess 当前没有作为素材系统客户功能交付,标准版和专业版默认配置已改为 false,对应展示文案调整为预留能力。此前“apiAccess 已定义但未见生产执法点”的判断,对当前代码应理解为“暂不交付、不可售卖”,而不是“已售卖但缺拦截”。

2026-06-03 补充:apiAccess 仍是预留能力,但服务端门禁已统一到 paid-core。如果未来许可证把 apiAccess 打开,也必须同时通过本地许可证、授权模块凭证、模块运行状态和 paid-core 的 apiAccess:use 动作授权。

2026-06-01 补充:自定义模板不只在管理维护入口做许可证拦截,普通读取和使用入口也已按许可证收口。/api/templates、/api/upload-templates、/api/material-types 在当前许可证不包含 customTemplates 时只返回内置模板,不继续暴露历史自定义模板,避免客户降级后仍使用付费模板能力。

2026-06-02 补充:付费模块链路已进一步拆成“模块授权台账 + 短期镜像拉取凭证”。license-server 新增 LicenseModuleGrant 和 ModulePullTokenAudit,premium-modules 清单来源改为授权台账;客户侧获取模块核心镜像前必须先拿 15 分钟有效的 pullToken,Registry 前置代理再通过 /api/registry/auth 校验 token、token 审计记录、仓库路径、清单 digest、许可证状态、激活实例和模块授权状态。主应用只缓存授权凭证和短期拉包凭证,不直接挂 Docker Socket;Go Agent 已提供最小模块同步入口,负责拉取模块镜像、校验 digest、启动模块容器并在健康检查失败时尝试恢复旧模块容器。

3. 链路概览

当前大致链路如下:

  1. 主应用激活页调用本地 /api/license/activate 或 /api/license/activate-free
  2. 主应用后端根据 LICENSE_PROJECT_CODE 代理请求到远端 license-server 的项目维度公开接口
  3. license-server 返回签名许可证
  4. 主应用把许可证写入本地 system_configs
  5. 主应用后续通过本地缓存、宽限期和心跳维持运行状态
  6. 主应用的部分功能开关和数量限制,直接依赖本地缓存中的许可证配置
  7. Agent 负责安装、升级、容器操作和环境初始化,并通过同一个项目编码获取版本信息

这意味着:

  • 远端服务端负责“发证和远端判定”
  • 本地主应用负责“落地缓存和日常执法”

当前最薄弱的就是后半段。

4. 已确认风险

4.1 高风险(已部分整改):本地许可证缓存信任边界曾经过宽

相关文件:

  • lib/license/license-service.ts
  • app/api/license/verify/route.ts
  • lib/license/server-config.ts
  • app/api/license/check/route.ts

关键现象:

  • 本地保存的 license 数据里包含 cachedConfig 和 lastVerified
  • 功能判断、用户上限、素材上限会直接读取 cachedConfig
  • 验证失败后,只要还在宽限期内,就会继续信任本地缓存
  • /api/license/check 只要看到本地结构像许可证,就会恢复已激活状态

状态更新(2026-03-12):

  • 已修复:本地许可证读取已改为先验签,再从签名原文恢复 LicenseConfig
  • 已修复:/api/license/check 现在通过 getLocalLicense() 验签后才恢复激活
  • 剩余风险:宽限期、本地时钟和客户自控主机这一前提没有改变,因此这里仍不应按“本地硬防破解”理解

直接影响:

  • 只要有人能改本地数据库里的 system_configs,就有机会伪造本地授权状态
  • 只要再配合阻断外联,就可能让系统在本地“带假证运行”
  • 宽限期字段同样落在本地,可被人为延长

风险判断:

这不是理论问题,是当前信任边界设计过宽的问题。

建议优先级:P0

建议方向:

  • 本地不要直接信任 cachedConfig
  • 功能判断优先使用“服务端签名原文重新验签后的结果”
  • 宽限期不能只依赖本地可改时间字段
  • 本地缓存至少要加入篡改检测和签名校验失败后的硬降级

4.2 高风险(已部分整改):两套 Agent 仍是高权限控制面,但认证、默认开放面和安装凭据都已做第一轮收口

相关文件:

  • flnxi-agent/src/server.ts
  • flnxi-agent/src/system-manager.ts
  • flnxi-agent-go/main.go
  • flnxi-agent-go/internal/api/handlers.go

关键现象:

  • Node 版 Agent 当前已接入 Basic Auth、基础安全头,以及 Socket 连接时的 Origin / 认证校验
  • Go 版 Agent 当前已接入 Basic Auth,中间件默认放行 /api/health
  • Node / Go 两个 Agent 当前都会在安装时生成随机的数据库、MinIO 和 NEXTAUTH_SECRET,不再在源码里写死固定安装凭据
  • 两个 Agent 仍然都提供安装、更新、重启、停止、容器管理等高权限能力
  • 当前已不再“未配置就直接开放”;如果未配置 AGENT_AUTH_USERNAME 和 AGENT_AUTH_PASSWORD,管理面会直接关闭,仅保留 /api/health
  • Go Agent 的一键安装脚本当前默认把宿主机端口只发布到 127.0.0.1;需要局域网直连时,必须显式改成 0.0.0.0
  • Go Agent 的一键安装脚本当前没有传入管理账号密码;安装后如不额外配置认证,除 /api/health 外管理面会关闭
  • Go 版默认仍监听所有网卡,也没有像 Node 版那样对浏览器来源做额外限制
  • Go 版里的 authToken 仍只是用于镜像仓库认证,不是请求调用者认证

直接影响:

  • 这类接口本质上仍接近“远程 Docker 控制台”
  • 当前虽然已补基础口令认证,但只要部署时没配账号密码、或继续直接暴露管理端口,风险仍然很高
  • 一旦被利用,风险级别仍高于普通业务接口漏洞

风险判断:

这是当前整条链路里最危险的面之一。

建议优先级:P0

建议方向:

  • 更理想的方式仍是统一到 127.0.0.1 / 内网反代 / 更窄的 ACL 访问
  • 对安装、更新、容器操作做审计日志
  • 严格减少 Agent 可操作面,不要默认暴露完整容器控制能力

4.3 高风险(已部分整改):系统初始化入口已收口,但默认部署安全仍需继续推进

相关文件:

  • app/api/system/init/route.ts
  • lib/license/license-service.ts
  • app/activate/page.tsx
  • docker-compose.prod.yml

关键现象:

  • /api/system/init 现在在“尚未有管理员”的场景下要求一次性 SYSTEM_INIT_TOKEN
  • 一旦系统里已有管理员,后续初始化操作只允许管理员登录后执行
  • 初始管理员密码不再固定写死;若未提供 INIT_ADMIN_PASSWORD,系统会在初始化时生成一次性密码
  • 初始管理员首次登录后会被强制修改密码,不再依赖“默认密码是否等于某个固定值”
  • 生产编排里已去掉 INIT_ADMIN_PASSWORD=88888888 这种弱默认回退

直接影响:

  • 新装环境“任何人都能直接打初始化接口”的风险已明显下降
  • 固定弱口令被大规模复用的风险已下降
  • 但如果 SYSTEM_INIT_TOKEN 配得过弱、或初始化响应被不当保存,仍然会留下新的管理面风险

风险判断:

这一项已经从“公开高危入口”下降到“已收口但仍需继续收紧”。

建议优先级:P0

建议方向:

  • 部署脚本应默认生成强随机 SYSTEM_INIT_TOKEN
  • 初始化成功页/接口返回的一次性密码不应再被额外持久化或二次暴露
  • 后续可继续考虑把“手动初始化”进一步收口到仅部署脚本或仅内网入口
  • 需要同步清理 Go Agent 静态页面里仍残留的旧默认账号文案

4.4 高风险(已部分整改):默认通信已开始切 HTTPS,部署弱默认值也已缩小,但仍有部分高价值密钥待继续清理

相关文件:

  • app/api/license/activate/route.ts
  • app/api/license/verify/route.ts
  • app/api/license/heartbeat/route.ts
  • app/api/license/activate-free/route.ts
  • lib/license/server-config.ts
  • lib/license/license-service.ts
  • flnxi-agent/src/system-manager.ts
  • flnxi-agent-go/internal/api/handlers.go
  • license-server/docker-compose.yml
  • license-server/deploy.ps1

关键现象:

  • 主应用和 Node Agent 已把 LICENSE_SERVER_URL 默认值改为 https://license.flnxi.com
  • Node / Go Agent 安装链路里原先写死的数据库密码、MinIO 密码、NEXTAUTH_SECRET 已改为运行时随机生成
  • Go Agent 读取 Harbor 版本列表时,已不再内置固定管理员账号密码,而是改为可选环境变量
  • license-server/docker-compose.yml 已不再回退到默认 JWT / 数据库口令,缺关键环境变量会直接拒绝启动
  • license-server/deploy.ps1 和 scripts/generate-keys.ts 已改为生成随机密钥和一次性管理员初始密码

直接影响:

  • HTTP 链路更容易被中间人拦截、篡改、重放
  • 明文默认口令会导致环境一旦直接复用默认值就进入高危状态
  • 写死的仓库管理员口令影响面甚至超出当前项目

风险判断:

这是代码安全和部署安全同时存在的问题。

建议优先级:P0

建议方向:

  • 默认全部改为 HTTPS 地址或要求显式配置
  • 清除所有硬编码口令、默认密钥、仓库管理员账号密码
  • 部署失败也不能自动回退到弱默认值

4.5 中高风险:license-server 的 API Key 管理权限过宽

相关文件:

  • license-server/app/api/admin/api-keys/route.ts
  • license-server/app/api/admin/[projectCode]/api-keys/route.ts
  • license-server/lib/project-auth.ts

关键现象:

  • 旧的无项目 API Key 接口已废弃,不再承载真实逻辑
  • 当前真实 API Key 管理入口已改为项目维度,并要求 verifySuperAdmin()
  • 项目级 API Key 前端页面也已补上,但还缺更细的审计与生命周期管理

直接影响:

  • 普通项目管理员已不能再接触 API Key 管理能力
  • 但 API Key 仍属于高价值发证凭据,如果缺少审计、轮换和用途隔离,后续仍可能形成运维风险

风险判断:

这一项已从“明确权限边界错误”下降为“已收口但仍需继续完善”。

建议优先级:P2

建议方向:

  • 保持 API Key 为超管专属
  • 继续完善项目级 API Key 隔离
  • 增加创建、禁用、查看的审计记录

4.6 中低风险:多项目改造主链路已收口,但仍有历史壳接口待物理清理

相关文件:

  • license-server/lib/db.ts
  • license-server/app/api/admin/dashboard/route.ts
  • license-server/app/api/admin/licenses/route.ts
  • license-server/app/api/admin/alerts/route.ts
  • license-server/app/api/admin/versions/route.ts
  • license-server/app/api/admin/customers/route.ts
  • license-server/app/api/admin/[projectCode]/dashboard/route.ts

关键现象:

  • license-server 的真实后台管理链路已经切到 /api/admin/{projectCode}/...
  • 旧的无项目后台接口已不再执行真实业务逻辑,而是返回 410
  • 旧的无项目后台页面也已改为跳转到项目选择页
  • 真实运行链路里,项目隔离现在由 mainPrisma + getProjectDb(projectCode) 明确承担

直接影响:

  • 当前不会再因为旧后台接口误打到默认项目库
  • 但保留历史壳接口会继续增加维护噪音,也容易让后续阅读代码的人误判真实入口

风险判断:

这项风险已明显下降,更偏维护性和清理优先级问题。

建议优先级:P2

建议方向:

  • 后续物理删除旧单项目后台接口
  • 继续保持所有许可证后台读写统一走项目维度
  • 文档中明确哪些旧入口已废弃,避免继续被当正式入口使用

4.7 中风险:主应用 CORS 当前是“反射任意 Origin”

相关文件:

  • middleware.ts

关键现象:

  • 虽然定义了 ALLOWED_ORIGINS
  • 但真正返回头时直接使用 requestOrigin || origin
  • 允许名单没有真正参与判断

直接影响:

  • 现在未必马上能构成完整攻击链
  • 但它会放大以后新增接口时的跨域风险

风险判断:

属于应尽早收口的基础安全配置问题。

建议优先级:P2

建议方向:

  • 只对白名单来源返回 Access-Control-Allow-Origin
  • 不要对任意来源做反射

4.8 中风险:许可证服务端公开接口缺少真正的请求签名校验

相关文件:

  • license-server/lib/types.ts
  • license-server/app/api/license/[projectCode]/activate/route.ts
  • license-server/app/api/license/[projectCode]/verify/route.ts
  • license-server/app/api/license/[projectCode]/heartbeat/route.ts
  • license-server/app/api/license/[projectCode]/register-free/route.ts
  • license-server/app/api/license/[projectCode]/generate/route.ts

关键现象:

  • 类型里定义了 signature
  • 但实际 activate / verify / heartbeat 的 schema 没有要求请求签名
  • 当前主要依赖时间戳和硬件指纹
  • 免费注册接口会根据 instanceId 直接返回已有激活码

直接影响:

  • 接口更容易被脚本化探测和重放
  • 设备级身份校验强度不足

风险判断:

它还不是当前最致命的问题,但后面如果系统规模变大,会越来越明显。

建议优先级:P2

建议方向:

  • 给客户端到 license-server 的请求增加签名或短期令牌
  • 免费注册和已有激活码返回逻辑需要进一步收紧

4.9 补充结论:按私有化部署场景重看后,当前更大的问题仍是“本地执法过重”,但已确认漏口已先补齐一轮

相关文件:

  • docs/项目目标与目标环境.md
  • README.md
  • middleware.ts
  • app/api/departments/route.ts
  • app/api/admin/upload-templates/route.ts
  • app/api/admin/upload-templates/[id]/route.ts
  • app/api/admin/upload-templates/[id]/migrate/route.ts
  • lib/license/check-limits.ts
  • lib/license/license-service.ts
  • app/api/license/check/route.ts

关键现象:

  • 当前项目真实定位是企业私有化部署,目标设备是 NAS / Linux 服务器,普通最终用户只通过浏览器访问
  • 这意味着客户往往实际控制容器、数据库、环境变量和本地缓存,本地许可证判断天然难以做到“硬防破解”
  • middleware.ts 已明确不再承担许可证快速检查,当前主要依赖各个 API 自己做许可证判断
  • 已确认的部门隔离漏口已补齐:POST / PATCH / DELETE 现在都要求同级许可证
  • 已确认的自定义模板漏口已补齐:模板编辑、删除、迁移现在都要求同级许可证
  • apiAccess 当前仍是预留能力,标准版和专业版默认不出售;若未来开启,已要求走 paid-core 的 apiAccess:use 门禁
  • /api/license/check 已改为先做本地验签,再恢复激活状态

直接影响:

  • 当前已确认的几处直接漏口已经关闭,许可证执法一致性比之前好一些
  • 但整体上仍更接近“拦普通误用”和“提高破解成本”,还不是“能扛有主机控制权用户的定向破解”
  • 已声明但未落地的能力执法仍会继续制造新的不一致风险
  • 在客户自控服务器场景下,只要本地缓存和本地数据库还能被直接接触,远端发证的价值就会被削弱

风险判断:

这不是单点漏洞,而是产品部署模型与当前执法架构共同造成的边界问题。

建议优先级:P0

建议方向:

  • 不再以“本地绝对防破解”为目标,转为“统一执法 + 提高成本 + 留痕 + 可追责”
  • 所有付费功能改为集中式检查,避免散落在零碎 API 中各自判断
  • 本地缓存只保留容灾价值,不应继续承担最终真相
  • 优先补齐已确认的许可证漏口,再考虑更复杂的防篡改设计

5. 后续修复优先级建议

P0:必须优先处理

  1. 清除剩余的硬编码口令和默认弱密钥
  2. 继续把 Agent 从“默认安全”收口到“默认内网 / 反代优先”
  3. 继续把许可证执法点集中化,并补齐尚未落地的已声明能力
  4. 复核宽限期、本地时钟和本地容灾策略,避免再次形成过宽信任边界
  5. 把初始化令牌的生成、传递和审计流程纳入正式部署脚本

P1:应在下一阶段完成

  1. 给所有已声明的许可证能力补齐真实生产执法点
  2. 继续清理历史壳接口和旧文档入口
  3. 收紧项目级 API Key 的审计、轮换与用途隔离
  4. 明确公开接口的接入方约束和项目编码管理流程

P2:作为体系完善

  1. 修正 CORS 白名单逻辑
  2. 给许可证公开接口补请求签名、限流、审计
  3. 补全安全相关监控和异常告警

6. 建议的整改顺序

建议按下面顺序推进:

  1. 先清理剩余硬编码密钥,并把初始化令牌流程接进正式部署入口
  2. 再继续收紧 Agent 的默认暴露面、认证与审计
  3. 再处理 license-server 权限边界和多项目收口
  4. 最后处理 CORS、接口签名、限流和审计完善

原因:

  • 前两项仍是当前最直接的高危入口
  • 后两项更偏体系长期稳定性和可维护性

7. 当前结论摘要

当前系统不是完全没有保护,而是“远端有一定保护,本地执法仍偏重,且默认部署安全性还不够”。

如果按当前项目真实目标来看,它是部署在客户自己控制的 NAS / Linux 服务器上的私有化产品。在这种前提下,真正意义上的“硬防破解”天然就比较弱。

本轮收口之后,方向已经比之前更合理,但后续正确方向依然不应是单纯追求“本地绝对防住”,而应是:

  • 统一许可证执法入口
  • 收紧本地缓存信任边界
  • 把高价值能力尽量建立在更难被本地完全替代的链路上
  • 同时加强审计、留痕、异常检测和运营管控

如果面对的是普通客户环境,这套方案暂时还能工作;如果面对的是懂容器、懂数据库、懂抓包、懂改配置的人,当前风险暴露仍然比较明显。

后续若继续做新版本功能移植,建议把“安全收口”列为并行主任务,不要继续在当前信任边界上叠更多功能。