Files
opsx-vpn/docs/specs/SDD-007-built-in-feishu-issuer.md
T

6.5 KiB
Raw Blame History

SDD-007:内置飞书 OIDC 发行者

字段 内容
级别 S3(公网身份入口、签名密钥、生产网关变更)
状态 实施中,尚未验证真实飞书登录
日期 2026-10-22

1. 现状与目标

当前 OpenVPN OIDC 模式调用 openvpn-auth-oauth2,要求外部标准 issuer;/api/tunnels/openvpn/oidc-readiness 仅检测已安装插件与 HTTPS 管理入口。Nginx 只允许管理页面/API,其他路径返回 444;现有 LDAP 与密码专线均应保留。

增加单租户、单机部署的 Go /id 身份模块:飞书网页 OAuth 授权后,向插件提供受限的 OIDC authorization code 流程。仅已启用的管理员配置可提供发现文档、JWKS 与发码;不把登录成功视为自动获得 VPN 权限。沿用现有 HTTPS 域名及数据库,不引入外部身份服务器。

2. 功能与约束

  • FR-01:管理员配置飞书 App ID/Secret(密钥不回显)、允许的 tenant key;系统生成并仅在服务端使用 VPN OIDC Client Secret,管理页面只显示 Client ID;复用现有 TLS 域名。
  • FR-02:提供 discovery、JWKS、authorize、飞书回调、token、userinfo;只接收登记的一个 HTTPS 回调地址,发放一次性短时 code;签发 RS256 ID Token。飞书返回的 open_id 与 tenant_key 组合成稳定 subject。
  • FR-03:OpenVPN 插件通过机密客户端交换授权码;客户端凭据、回调、nonce、state 绑定,存在 code_challenge 时校验 S256;不接受外部指定的上游 URL。
  • FR-04:前端展示 issuer、飞书回调及 VPN 回调、准备状态,允许选择内置 issuer 填写专线配置,但不覆盖既有配置。
  • FR-05:Nginx 仅允许同域 /id/ 的固定身份端点转发到管理进程;/oauth2/ 转发到单条已启动的认证组件(仅就绪后启用);其他未知路径保持 444。
  • NFR-01:签名密钥 0600、密钥材料不可回显,令牌与随机状态只保存哈希或受限数据;严格 TLS、过期时间、状态原子消费、请求限流及日志脱敏。
  • NFR-02:未启用或配置不完整时不发布可用 issuer,不破坏现有 OpenVPN/LDAP/管理入口。

3. 方案与契约

发行者建议 https://<现有域名>/id;只按 issuer 文档发布实际 URL,不假设根目录 discovery。飞书上游回调 https://<现有域名>/id/feishu/callback;插件回调 https://<现有域名>/oauth2/callback。飞书 App ID/Secret 只用于上游;插件使用系统生成的独立 VPN 凭据。数据加密利用现有 secret.key;RSA 签名私钥独立文件,备份必须同时包含数据库和密钥。

独立 internal/identityprovider 包承载协议逻辑和存储;api 管理路由只对管理员开放;Nginx 模板明确路由优先级。JWT 仅签发 openid 授权范围,不在令牌里包含飞书访问令牌。用户授权初期先采用飞书应用可用范围 + 明确租户约束;后续如需逐用户允许名单再增加独立配置。

接口参考 /Users/logan/devops/demo/id/Feishu-OIDC-Workers 的飞书官方端点定义与 JSON 换码格式;授权状态和客户端凭据隔离参考 /Users/logan/devops/demo/id/dex 的协议边界。没有复制其实现;Workers 示例把原始飞书 code 转交客户端的做法不用于本项目。

4. 风险与验收

风险 防护 / 恢复
伪造授权回调 / 重放 code 随机 state 与 code、短有效期、数据库原子消费、精确 redirect URI 和客户端绑定
泄露私钥或飞书密钥 0600 与 AES-GCM、API 不回显、部署时不传入对话中曾公开的旧密钥
多专线共用 /oauth2/ 网关只选择唯一运行中的同域 OIDC 专线,冲突时不启用转发
网关修改影响管理入口 先预览、备份现有站点、nginx -t 后 reload;失败恢复旧站点

验收:禁用与错误配置拒绝授权;模拟飞书服务测试完整码流、重放、错误租户、错误回调/nonce/PKCE、JWT 验签;真实飞书应用授权和 OpenVPN WebAuth 需单独联调。发布顺序:完成本地测试 → 部署但默认禁用 → 管理员轮换并保存飞书凭据 → 预览并启用网关 → 测试专线正/负向登录 → 再决定生产切换。恢复:禁用 issuer、恢复 Nginx 旧站点和旧二进制;不删除现有专线或密钥。

5. 专线与身份配置分工(编辑页迭代)

现有专线编辑页仍把外部 OIDC 的 issuer 与 VPN Client Secret 输入框作为主流程,容易让管理员误把飞书 App ID/Secret 填入专线。管理员仅在「设置 → 飞书身份」维护全局飞书 App ID、App Secret 和 tenant_key;专线的内置模式自动引用当前 issuer、独立 VPN Client ID 和公网入口。编辑既有外部 OIDC 专线时保持原始设置,只有明确切换来源才更改字段。外部 OIDC 仍允许手工配置,不把飞书上游凭据放入专线。

验收:专线编辑和新建都能直达身份设置;内置模式不展示飞书密钥或可编辑的 VPN 密钥,提示飞书回调与 VPN 回调的区别;外部模式保留原先的输入能力;服务端对内置标记校验已启用的发行者、Client ID 和当前域名,不因提交旧密钥而回退至外部模式。该迭代不自动切换、启用或重启已有专线;上线前构建前端并跑相关 Go 测试,上线后检查管理入口与禁用的身份入口,失败恢复上一版二进制。

6. 公网白名单与页面故障修复

线上新增 /api/system/identity 后托管 Nginx 仍使用旧白名单,浏览器看到 ERR_EMPTY_RESPONSE(Nginx 444);本机管理进程可正常返回 401。已有 /api/system/tls/domain 路由在无凭据时返回 401,不能把用户同时报告的域名保存失败归结于同一缺失路由。Vue 侧栏在 route 创建前注册 watch,导致初始化时引用尚未声明的变量;WireGuard SVG 含截断的路径数据。

每次启动且确认当前是有效 Nginx HTTPS 托管站点时,按已注册 Gin 路由重建站点白名单;文件内容相同则不重载,冲突或 nginx -t 失败时保留旧站点并记录日志,不阻止本机服务启动。公网只同步已注册 API、固定身份端点与当前单条运行的同域认证组件,未知路径仍 444。验收:新身份管理 API 不再返回空连接,未经认证返回 401;既有域名 PUT 也仍返回 401,授权后的保存需单独实测;设置和编辑页无布局初始化异常;SVG 路径有效;Nginx 测试通过。恢复时使用 /tmp 的上一版二进制和原 Nginx 站点快照重新加载,不修改专线或飞书凭据。