Build Charac server and client with OIDC, SSH sessions, and packages
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
# PostgreSQL 管理数据
|
||||
|
||||
## 技术与范围
|
||||
|
||||
Server 使用 EF Core 10 和 Npgsql,目标数据库为 PostgreSQL 18。数据层位于 `src/server/Data/`,直接使用具体的 `AccessDbContext`;授权解析位于 `Authentication/SshAccessResolver.cs`。查询、写入与测试使用 LINQ、ChangeTracker 和 SaveChanges;迁移由 EF 工具生成,不拼接 SQL 或使用 FromSqlRaw / ExecuteSqlRaw。EF 参数化不能代替身份认证、授权和字段校验。
|
||||
|
||||
目前实现管理数据模型、EF 迁移、SSH 登录密钥引用、工作区历史、授权解析、迁移命令与数据库 readiness。OIDC、Session Manager 和受保护的 WAN `/session` 路由已有首版实现;真实私钥 SSH 与正式 Client 的交互式 `ls` 已在 `.100 → .101` 实测;正常退出及重新附着待复测。本机 `database target register` 和 `database grant register` 已可持久登记正式目标、主机公钥、OIDC 身份及授权;`endpoint_regist` 可在本机交互收集端点信息、核对主机指纹并把已有登录私钥复制进服务目录,详见[SSH Broker](ssh-session.md#三参数交互式端点登记);完整的 LAN 网络管理入口与 CA 签发仍待实现。旧 `database enroll-probe`、`database grant-probe` 只面向专用测试库;`database key` 命令管理登录密钥引用和授权绑定。WAN 不接收客户端自报身份来调用授权解析。
|
||||
|
||||
## 第一版模型
|
||||
|
||||
所有实体使用 UUID 主键。身份、目标、授权、主机公钥和登录密钥只有启用后才能参与授权;本机密钥登记命令会明确创建启用的密钥记录。
|
||||
|
||||
| 表(access schema) | 保存内容 | 约束 |
|
||||
| --- | --- | --- |
|
||||
| identities | OIDC issuer、subject、启用状态 | issuer + subject 唯一 |
|
||||
| user_certificate_authorities | 用户证书 CA 名称、公钥、签名密钥引用、启用状态 | 名称唯一;不保存私钥内容 |
|
||||
| ssh_login_keys | 普通 SSH 登录密钥名称、文件名、启用状态 | 名称与文件名唯一;私钥留在 Server 文件系统 |
|
||||
| workspace_records | 正式工作区的身份、目标、账户、认证方式与生命周期历史 | 历史不等于可恢复的在线 SSH 连接 |
|
||||
| targets | 名称、SSH 地址和端口、可选的用户 CA、启用状态 | 名称唯一;可空 CA 外键 |
|
||||
| host_keys | 已确认的目标主机公钥、启用状态 | 目标 + 公钥唯一;目标外键 |
|
||||
| grants | 身份、目标、系统账户、可选登录密钥与证书 principal、启用状态、可选 UTC 到期时间 | 身份 + 目标唯一;身份、目标与登录密钥外键 |
|
||||
|
||||
首版明确采用一个外部身份在每个目标上对应一个系统账户。不同目标可映射不同账户,不同身份映射相同账户也不共享工作区归属。将来如需同一身份在一个目标上选择多个账户,需要扩展授权模型与选择参数。
|
||||
|
||||
私钥文件位于 `[ssh_keys] directory` 指定的受保护目录,数据库只保存密钥名称、普通文件名和启用状态,不保存登录密钥公钥。目标主机公钥仍单独保存在 `host_keys` 中,建连时必须验证。密钥绑定到授权后,Broker 才能为该授权读取文件;数据库记录本身不会生成密钥、修改目标 `authorized_keys` 或使密钥自动获得访问权。`workspace_records` 保留正式工作区的历史,即使 Server 停止也不会自动清理。其 `ClosedAt` 为空只表示未观察到结束,不能作为仍可附着的依据;在线状态仍以进程内 Session Manager 为准。详见[SSH Broker](ssh-session.md)。
|
||||
|
||||
密码登录不要求 CA;`targets.UserCertificateAuthorityId` 可为空。配置该字段时,它表示 Server 为该目标签发用户登录证书所用的 CA。管理员仍须在 LAN 内配置目标 sshd 信任对应 CA,并建立 principal 与账户的允许关系;数据库关联不会自动修改 sshd。`host_keys` 是 Server 验证 sshd 身份时使用的主机公钥白名单,与用户 CA 分开。首版保存多个精确公钥以支持人工轮换,目标主机证书 CA 的验证策略尚未实现。
|
||||
|
||||
删除行为为 Restrict,防止误删身份、目标或 CA 时悄悄级联移除授权。SaveChanges 校验必填字段、长度、非空 ID、端口范围和 UTC 到期时间;数据库承担非空列、长度、唯一键和外键约束。实体值与关联是否合法仍须由未来 LAN 管理入口校验,例如主机公钥是否真实、目标地址是否允许、签名密钥引用是否在受信范围内。
|
||||
|
||||
## 授权检查与签发
|
||||
|
||||
`SshAccessResolver.ResolveAsync` 仅接受已完成 OIDC 验证的 `ExternalIdentity` 与目标 ID。一次参数化 LINQ 查询同时要求身份、目标、授权启用,授权未到期,且存在启用的目标主机公钥。无匹配时返回空结果;数据库故障抛出错误,调用方必须拒绝继续操作。查询使用 AsNoTracking,不缓存授权,所以后续调用可以看到已提交的撤销。
|
||||
|
||||
返回值是供 Broker 使用的内部授权快照,包含目标地址、系统账户、可选 principal 与 CA 信息、受信主机公钥及授权截止时间,不对 Client 返回。它既不是签发好的 SSH 证书,也不是可长期重复使用的访问令牌。
|
||||
|
||||
目标调用时机为创建会话、重新附着、接管,以及建立新的后端 SSH 连接之前。终端每次按键或输出不查询数据库,使用工作区当前 lease 检查控制权。附着与接管还必须检查工作区归属,目标授权通过不代表可以访问其他身份的工作区。
|
||||
|
||||
授权判断与后续网络连接并非原子事务:查询返回后仍可能发生撤销。签发应紧接授权检查、限制证书 principal 和有效期,且不超过授权截止时间;不可长期保存授权快照后再签发。Session Manager 后续需要消费撤销通知或定期复核,定义最大撤销生效延迟,并撤销现有控制权及按策略关闭连接。SSH 证书到期只限制后续认证,不会自动踢掉已有连接。这些签发与在线撤销行为目前尚未实现。
|
||||
|
||||
## 配置与迁移
|
||||
|
||||
开发时优先读取仓库根目录被 Git 忽略的 `workspace-access.config`;可从根目录 `workspace-access.config.example` 复制模板。发布后可用全局 `--config <路径>` 指定受保护的配置文件,或自行将同名文件放在可执行文件旁;显式路径不存在时直接报错。`workspace-access.config` 支持:
|
||||
|
||||
```ini
|
||||
[database]
|
||||
host=127.0.0.1
|
||||
port=5432
|
||||
name=workspace_access
|
||||
username=workspace_access
|
||||
password=<数据库密码>
|
||||
timeout=5
|
||||
maximum_pool_size=100
|
||||
```
|
||||
|
||||
`host`、`name`、`username` 是必填项;`port`、`password`、`timeout`(秒)、`maximum_pool_size` 可选。Server 用 `NpgsqlConnectionStringBuilder` 组装连接,密码中的连接字符串分隔符不会被当作其他参数。旧的 `[database] connection_string` 仍可单独使用,但不可与拆分字段混用。可选的环境变量 `WORKSPACE_ACCESS_DATABASE` 优先于文件,填写完整的 Npgsql 连接字符串。包含密码的部署配置应由服务管理器或受权限保护的配置提供,不能提交仓库或出现在命令行参数中。未配置数据库连接时 Server 仍可启动,readiness 报告 `database=not_configured`。
|
||||
|
||||
Server 为每个请求范围创建独立的 `AccessDbContext`,查询时由 Npgsql 连接池提供连接;一个 `DbContext` 不跨并发请求共享。可用 `maximum_pool_size` 限制并发数据库连接。当前授权查询是短查询,无需在 Server 内另建共享连接或异步命令队列;如果将来需要严格的数据库写入顺序,应在具体业务操作处定义顺序与事务边界。
|
||||
|
||||
由管理员创建专用数据库及角色。正常运行的 WAN 服务使用最小权限角色;当前授权查询只需管理表读取权限。LAN 管理入口将来需要独立的写权限。迁移使用临时注入的迁移角色连接,具备对应数据库/schema 的建表与修改权限。不要让 WAN 服务常态使用 postgres 超级用户。数据库不保存 CA 私钥、OIDC 令牌或终端输出。
|
||||
|
||||
开发时先配置连接,再执行:
|
||||
|
||||
```powershell
|
||||
dotnet run --project src/server -- --config workspace-access.config database migrate
|
||||
```
|
||||
|
||||
发布后可运行 `char_rac_server database migrate`。该命令显式调用 EF MigrateAsync,重复运行只应用尚未执行的迁移;如果指定数据库不存在且账号有创建权限,EF 可以创建它。执行前确认连接指向专用数据库并检查迁移。正常服务启动不自动迁移。迁移命令允许单条语句最多执行两分钟,运行时查询默认十秒超时,避免数据库建表耗时与连接授权请求共用短超时。
|
||||
|
||||
添加后续迁移:
|
||||
|
||||
```powershell
|
||||
dotnet tool restore
|
||||
dotnet ef migrations add ChangeName --project src/server --output-dir Data/Migrations -- --config workspace-access.config
|
||||
```
|
||||
|
||||
EF 工具与运行时使用相同配置。更改模型后须提交迁移、模型快照和依赖锁文件。迁移创建需要配置数据库连接,但不需要数据库在线。
|
||||
|
||||
`/health/ready` 的 database 值为 `not_configured`、`unavailable`、`migration_required` 或 `ready`。检查实际连接、待执行迁移和管理表读取权限。即使 database 为 ready,尚未完成的托管后端 SSH 凭据、完整终端客户端和会话端到端验收仍使整体返回 HTTP 503。
|
||||
|
||||
## 数据维护与保留
|
||||
|
||||
PostgreSQL 的 `autovacuum` 默认启用,会根据各表的插入、更新和删除量自动执行 `VACUUM` / `ANALYZE`。默认约每分钟检查一次数据库,但这不是每分钟清理一次每张表;未达到触发条件的表不会因此运行 `VACUUM`。正式部署应保持 `autovacuum` 与 `track_counts` 启用,先观察运行数据,再决定是否针对高写入量的表调整阈值。当前没有证据需要另设固定时间的全库 `VACUUM` 任务,也不应把 `VACUUM FULL` 当作周期任务:它会对表取得排他锁。
|
||||
|
||||
可在 pgAdmin 的 Query Tool 中执行以下**只读**查询,检查当前实例设置及 `access` schema 的维护情况:
|
||||
|
||||
```sql
|
||||
SHOW autovacuum;
|
||||
SHOW track_counts;
|
||||
SHOW autovacuum_naptime;
|
||||
|
||||
SELECT relname, n_live_tup, n_dead_tup,
|
||||
last_autovacuum, autovacuum_count,
|
||||
last_autoanalyze, autoanalyze_count
|
||||
FROM pg_stat_user_tables
|
||||
WHERE schemaname = 'access'
|
||||
ORDER BY relname;
|
||||
```
|
||||
|
||||
`n_live_tup` 和 `n_dead_tup` 是估算值;刚建好或很少改动的表尚无 `last_autovacuum` 记录是正常现象。普通 `VACUUM` 只回收已删除或更新的旧行及索引项,供后续写入复用,不会删除仍有效的业务记录。数据库连接池与 PostgreSQL 缓存也不需要定时清空。所有实体主键由应用生成 UUIDv7,没有需要回收、重置或复用的自增主键序列。
|
||||
|
||||
`workspace_records` 是工作区历史,不是数据库垃圾;`identities`、`targets`、`host_keys`、`ssh_login_keys`、`grants` 等是持久管理数据。当前不对它们设置自动删除期限。若将来需要压缩历史,须先确定审计保留期、备份和清理范围,只针对已结束且超过保留期的工作区记录实施单独的限批清理;不能把 `ClosedAt IS NULL` 当成在线判断并直接删除。专用测试库中由本机 Broker 探测生成的记录仍由显式 `database clear-probes` 命令清理,不影响正式记录。
|
||||
|
||||
PostgreSQL 机制参考:[Routine Vacuuming](https://www.postgresql.org/docs/18/routine-vacuuming.html)、[Vacuuming Configuration](https://www.postgresql.org/docs/18/runtime-config-vacuum.html)、[Statistics Monitoring](https://www.postgresql.org/docs/18/monitoring-stats.html)。
|
||||
|
||||
## 验收
|
||||
|
||||
设置 `WORKSPACE_ACCESS_TEST_DATABASE` 为专用 PostgreSQL 18 测试数据库的连接字符串后运行 `./build.ps1 --target Verify --locked-restore`。测试会在指定数据库的 `access` schema 上执行真实 EF 迁移;每个 `DbContext` 通过 Npgsql 连接池获取连接,用例结束时按生成的 ID 删除自身测试数据。不会删除数据库,也不要求测试角色拥有 `CREATEDB`。测试角色需要在该数据库中创建 schema 和表、读写业务表的权限。迁移建立的表与历史记录会保留;测试进程异常终止时也可能留下带随机 ID 的测试记录,因此不要把生产数据库配置为测试目标。未设置此变量时数据库集成测试明确跳过,原有核心测试仍运行。连接上限为 1 的本地测试角色可以顺序运行这些用例;并行运行其他数据库客户端仍可能受该上限影响。
|
||||
|
||||
测试覆盖迁移重复执行与模型一致性、唯一键与外键、一次授权查询、跨用户/issuer/目标隔离、带 SQL 特殊字符的输入、禁用状态、过期与撤销后的再次授权。数据库测试公钥是占位数据,仅验证存储和授权条件;另有主机公钥格式与精确匹配单元测试,真实 SSH 登录与证书登录仍待验收。
|
||||
|
||||
参考:[Npgsql EF Core 10](https://www.npgsql.org/efcore/release-notes/10.0.html)、[EF 迁移部署](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying)。
|
||||
Reference in New Issue
Block a user