Files
lingniu-vehicle-ingest/vehicle-data-platform/docs/customer-scope-api-enforcement-matrix.md

195 lines
10 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.
# 车辆数据中台客户 Scope API 覆盖矩阵
更新时间2026-07-14
状态:安全审计与实施设计;尚未开放客户主体。
## 1. 当前结论
车辆中台当前的 Bearer Token 只表达 `viewer/operator/admin` 功能角色,不表达客户或车辆数据范围。
因此,即使 OneOS 已能登录客户,也不能直接把客户 token 映射成 `viewer`:这会让客户读取全量车辆、轨迹、
告警、统计及其他人的导出文件。
2026-07-14 已只读核验 ECS 当前运行状态:
- `AUTH_MODE=enforce`
- 使用 `AUTH_TOKENS_JSON`,未使用单一 `AUTH_TOKEN`
- `/opt/lingniu-vehicle-platform/env/platform.env` 权限为 `0600 root:root`
- `lingniu-vehicle-platform` 服务已启用且正在运行。
该配置能阻止匿名访问普通平台 API但不能提供客户级隔离。
## 2. 独立发现:高德服务端接口绕过平台鉴权
`app.NewServer` 当前先把平台 API 包在 `withAPIAuth` 中,之后又在外层注册
`withAMapReverseGeocodeAPI`。所以 `/api/map/reverse-geocode` 在进入鉴权中间件前就会被处理;只要配置了
`AMAP_API_KEY`,匿名请求即可消耗服务端高德额度。
整改要求:
- 将逆地理编码路由放回鉴权之后;
- 对客户和内部主体分别限流,并限制经纬度格式和并发;
- 不记录完整 access token
- `app-config.js` 可继续公开,但只能暴露高德 Web JS 公钥和代理路径,不暴露服务端 API Key
- 高德安全代理因浏览器 SDK 需要可访问,应在边缘层配置域名来源、速率和响应缓存,不能把它视为客户身份接口。
## 3. 主体与权限模型
`Principal` 至少扩展为:
```text
name
role # 仅内部功能角色
subject_type # employee | service | customer
subject_id # OneOS sys_user.user_id
customer_id # 仅 customer 必填
tenant_id
identity_version
capabilities # vehicle:read, history:read, export:create ...
```
约束:
- 内部静态 token 只能生成 `employee/service` 主体;
- 客户主体只能来自 OneOS 签名、短时且 `aud=vehicle-data-platform` 的声明;
- 浏览器 Query、Body、Cookie 或普通转发头中的 `customerId` 均无效;
- 客户权限不能通过 `roleRank(viewer)` 复用内部 viewer 权限,需要独立 capability 判断;
- 未知 `subject_type`、缺失 customer ID、签名异常或身份版本失效全部拒绝。
## 4. QueryScope 机制
所有 Store 查询显式接收 `QueryScope`,避免新增方法时忘记从 `context.Context` 读取 Scope
```text
mode: unrestricted | customer_current | customer_period
customer_id
active_version
valid_from / valid_to # 历史持有期查询
```
`unrestricted` 只能由已验证的内部 employee/service 主体创建。空客户 Scope 表示“零车辆”,绝不能解释为不限制。
MySQL 当前数据查询应 JOIN
```text
business_scope_state(id=1)
business_customer_vehicle_scope(
source_version = active_version,
customer_id = principal.customer_id,
vin = 业务表.vin
)
```
同时要求:
- `last_success_at` 距当前时间不超过 10 分钟;
- active version 存在且快照计数完整;
- 快照缺失或过期返回 `503 BUSINESS_SCOPE_UNAVAILABLE`,而不是空列表或全量数据;
- 快照新鲜但该客户确实没有车辆时返回空列表;
- 单车越权统一返回 404避免泄露 VIN 是否存在。
TDengine 无法直接 JOIN MySQL Scope单车查询先在 MySQL 做授权判断;多车查询从同一 active version 读取授权
VIN 集合,设置数量上限后再生成参数化过滤。不能先从 TDengine 取全量再由前端过滤。
## 5. API 覆盖矩阵
### 5.1 客户首期允许,但必须按当前 Scope 过滤
| API 组 | 代表路由 | 必须实施的控制 |
| --- | --- | --- |
| 全局监控 | `/api/v2/monitor/summary``/map` | 车辆、点位、聚合、告警数和所有汇总均从同一客户 Scope 计算;禁止混用全局 DashboardSummary |
| 车辆列表与解析 | `/api/vehicles``/resolve``/detail` | VIN、车牌、终端号解析必须先限定 Scope越权关键字表现为不存在 |
| 最新状态 | `/api/realtime/vehicles``/locations` | 列表、total、分页和筛选均在 SQL 层过滤 |
| 车辆只读档案 | `GET /api/v2/vehicles/{vin}/profile` | 先校验 VIN只返回门户需要的非敏感字段 |
| 最新遥测 | `/api/v2/vehicles/{vin}/telemetry/latest` | 路径 VIN 校验 Scope原始协议帧字段按白名单输出 |
| 轨迹 | `/api/v2/tracks` | 车牌/VIN/终端解析、轨迹点、停留点和统计都使用同一授权 VIN |
| 历史查询 | `/api/history/locations``/api/v2/history/query``/series` | 首期只允许当前车辆且 eventAt 不早于 `scope_start_at`;不能读取交付前数据 |
| 里程 | `/api/mileage/summary``/daily``/api/v2/statistics/mileage` | 列表与汇总按 VIN 和授权时间过滤total 不能泄露全局数量 |
| 在线统计 | `/api/statistics/online-summary``/online-vehicles` | 分母、在线数、离线数、趋势和明细均限定客户车辆 |
| 告警只读 | `/api/v2/alerts/summary``/events``/events/{id}` | 列表 JOIN event.vin Scope详情按 ID+Scope 查询,越权返回 404 |
| 指标元数据 | `/api/v2/metrics``/api/v2/history/metrics` | 可返回全局字段定义,但过滤内部专用/敏感指标 |
| 逆地理编码 | `/api/map/reverse-geocode` | 必须鉴权和限流;不依赖客户参数,不返回其他车辆信息 |
### 5.2 客户首期明确禁止
| API 组 | 路由 | 原因 |
| --- | --- | --- |
| 平台总览 | `/api/dashboard/summary` | 包含全局车辆、帧量、Kafka/服务健康信息 |
| 接入诊断 | `/api/vehicles/coverage*``/api/vehicle-service*` | 包含协议覆盖、终端、原始帧和数据质量诊断 |
| 原始帧 | `/api/history/raw-frames*` | 包含协议证据和内部字段;首期使用白名单遥测/历史 API 代替 |
| 质量中心 | `/api/quality/*`、旧 `/api/alert-events/*` 别名 | 平台级质量问题,不是客户业务告警 |
| 运维 | `/api/ops/*` | 数据源、容量、Kafka、Redis 和服务健康仅内部可见 |
| 接入管理 | `/api/v2/access/*` | 身份绑定、阈值和未解析终端均为内部运维能力 |
| 车辆档案写入 | `PUT /api/v2/vehicles/{vin}/profile``/vehicle-profiles/sync` | 只允许内部管理员/同步服务 |
| 告警规则/处置 | `/api/v2/alerts/rules*``events/{id}/actions` | 首期客户只读,不能修改平台规则或全局事件状态 |
| 平台通知 | `/api/v2/alerts/notifications*` | 当前通知没有客户 owner读取与已读操作均可能跨客户 |
禁止应在服务端 capability 层返回 403不能只隐藏前端菜单。
### 5.3 导出必须单独重构后才能开放
当前导出实现有四个 P0 问题:
1. `CreateHistoryExport` 没有 `context`,创建时无法固化主体;
2. `HistoryExportJob` 没有 owner/customer/scope version
3. `ListHistoryExports` 返回全局任务;
4. `HistoryExportFile(id)` 只校验 ID 和完成状态,知道 ID 即可下载。
整改要求:
- `Create/List/Download` 全部接收 context 和 Principal
- Job 固化 `owner_subject_id``customer_id``scope_version`、已解析 VIN、时间范围和审计 request ID
- 创建任务时校验每个 VIN异步执行前再次校验
- 列表只返回当前 owner下载同时校验 owner 和当前授权,越权返回 404
- 客户停用、车辆授权撤销或 Scope 失效时停止未完成任务并拒绝下载;
- 文件名和 CSV 元数据不得包含其他客户信息;文件按 owner 隔离目录并设置 `0640`
- 定期清理过期文件与 jobs 记录。
在上述改造完成前,客户主体不授予 `export:create``export:download`
## 6. 当前 Scope 与历史 Scope 的边界
已实现的 `business_customer_vehicle_scope` 只保存当前有效车辆。它足以保护实时监控,但不能完整实现“客户还车后
仍可查看持有期间历史”。
首期安全策略:
- 只允许查询当前仍在 Scope 中的车辆;
- 历史起点不得早于当前记录的 `scope_start_at`
- 车辆还车并从当前 Scope 移除后,历史访问也随即关闭。
若产品确认需要持有期历史,再新增版本化区间投影:
```text
customer_id, vin, contract_id, valid_from, valid_to, source_version
```
历史查询必须满足 VIN 和 `valid_from <= event_at < valid_to`。不能仅凭“客户曾经拥有过这辆车”开放该 VIN 的全部历史。
## 7. 必须覆盖的越权测试
准备客户 A、客户 B、内部 viewer、内部 operator、内部 admin 和 service 六类主体,至少覆盖:
1. A 在列表、关键字解析、路径 VIN、POST body、分页游标中请求 B 的车辆;
2. A 直接访问 B 的告警事件 ID、导出 ID 和下载 URL
3. A 使用车牌或终端号绕过 VIN 校验;
4. A 的空 Scope 不返回全量Scope 缺失/过期返回 503
5. 快照切换期间所有查询只使用一个 active version不混合新旧结果
6. total、summary、地图聚合数量和在线率不泄露全局或 B 的数量;
7. A 只能读取交付后的历史点,不能读取交付前记录;
8. 车辆从 A 还车并交付给 B 后A 的实时访问立即 404B 只能读取自身授权时间段;
9. 客户主体访问运维、接入、质量、写档案、规则和处置接口均为 403
10. 匿名逆地理编码失败;认证请求达到限额后返回 429
11. 客户 token 的 customer ID 不能被 Query/Header/Body 覆盖;
12. 服务重启后导出 owner、Scope 和下载授权仍保持,不能退化成全局任务。
## 8. 推荐实施顺序
1. 扩展 Principal 和 OneOS JWT 校验,但暂不开放客户路由;
2. 引入强类型 QueryScope先改车辆解析与单车详情
3. 改所有列表、汇总、地图、统计和 TDengine 查询;
4. 改告警详情 IDOR、逆地理编码鉴权限流
5. 重构导出 owner 与异步 Scope
6. 完成双客户自动化测试和生产影子查询;
7. 最后为测试客户启用 capabilities默认保持客户入口关闭。