完成SSH私钥禁用复核与测试修复

修复数据库历史快照和备份恢复测试,清理 SSH 私钥敏感信息测试引用,更新中文整改清单与生产测试记录。后端、前端测试和构建均已通过,整体生产验收仍保持待复审。
This commit is contained in:
xuans 2026-10-02 05:49:44 +08:00
parent 5af752b25a
commit 462050144b
7 changed files with 46 additions and 9 deletions

View File

@ -93,7 +93,7 @@ func newSQLiteBusinessFixture(t *testing.T) sqliteBusinessFixture {
{`INSERT INTO users (id, username, email, password_hash, role, groups_json) VALUES ('owner', 'recovery-owner', 'owner@example.test', ?, 'super_admin', '[]')`, []any{string(hash)}},
{`INSERT INTO users (id, username, email, password_hash, role, groups_json) VALUES ('member', 'recovery-member', 'member@example.test', ?, 'user', '["测试分组"]')`, []any{string(hash)}},
{`INSERT INTO domains (id, name, certificate_type, sans_json, status, expires_at, auto_renew, acme_environment, dns_provider) VALUES ('domain', 'example.test', 'single', '["example.test"]', 'issued', ?, 1, 'staging', 'alidns')`, []any{fixture.certificate.NotAfter}},
{`INSERT INTO servers (id, name, address, port, username, private_key_ref, host_key_fingerprint, certificate_path, private_key_path, certificate_chain_path, web_server, reload_command_json, validation_command_json) VALUES ('server', '恢复测试服务器', '192.0.2.10', 22, 'deploy', '', 'test-only-fingerprint', '/test/cert.pem', '/test/key.pem', '/test/chain.pem', 'nginx', '["nginx","-s","reload"]', '["nginx","-t"]')`, nil},
{`INSERT INTO servers (id, name, address, port, username, auth_method, private_key_ref, password_ref, host_key_fingerprint, certificate_path, private_key_path, certificate_chain_path, web_server, reload_command_json, validation_command_json) VALUES ('server', '恢复测试服务器', '192.0.2.10', 22, 'deploy', 'password', '', '', 'test-only-fingerprint', '/test/cert.pem', '/test/key.pem', '/test/chain.pem', 'nginx', '["nginx","-s","reload"]', '["nginx","-t"]')`, nil},
{`INSERT INTO domain_servers (domain_id, server_id) VALUES ('domain', 'server')`, nil},
{`INSERT INTO tasks (id, domain_id, task_type, status, stage, message, operator_id, certificate_version_id, idempotency_key) VALUES ('task', 'domain', 'deploy', 'success', 'completed', '部署完成', 'owner', ?, 'recovery-idempotency')`, []any{fixture.certificate.Version}},
{`INSERT INTO task_attempts (id, task_id, attempt_number, stage, status, message) VALUES ('attempt', 'task', 1, 'deploy', 'success', '证书已推送')`, nil},

View File

@ -165,7 +165,8 @@ func snapshotSQLiteHistoricalBusiness(t *testing.T, db *sql.DB) map[string]sqlit
filtered = append(filtered, column)
}
columns = filtered
} for index, column := range columns {
}
for index, column := range columns {
columns[index] = `"` + strings.ReplaceAll(column, `"`, `""`) + `"`
}
query := "SELECT " + strings.Join(columns, ",") + ` FROM "` + name + `" ORDER BY 1`

View File

@ -67,6 +67,9 @@ func snapshotSQLiteReplacementState(t *testing.T, db *sql.DB) map[string]sqliteH
result := snapshotSQLiteHistoricalBusiness(t, db)
for _, table := range []string{"projects", "domains", "servers"} {
query := "SELECT rowid, * FROM " + table + " ORDER BY id"
if table == "servers" {
query = "SELECT rowid, id, name, address, port, username, host_key_fingerprint, certificate_path, private_key_path, certificate_chain_path, web_server, reload_command_json, created_at, updated_at, validation_command_json, password_ref, project_id, owner_id, created_by, updated_by FROM servers ORDER BY id"
}
result[table+"_with_rowid"] = sqliteHistoricalTable{query: query, rows: snapshotSQLiteUpgradeQuery(t, db, query)}
}
return result

View File

@ -57,31 +57,31 @@ func TestStoreListsMetadataAndDeletesReferenceWithoutLeakingValues(t *testing.T)
if err != nil {
t.Fatalf("打开敏感信息存储失败:%v", err)
}
if err := store.Put(t.Context(), "secret://ssh/key-b", map[string]string{"private_key": "secret-b"}); err != nil {
if err := store.Put(t.Context(), "secret://dns/provider-b", map[string]string{"api_token": "secret-b"}); err != nil {
t.Fatalf("保存第二个 SSH 私钥失败:%v", err)
}
if err := store.Put(t.Context(), "secret://dns/provider", map[string]string{"token": "dns-secret"}); err != nil {
t.Fatalf("保存 DNS 凭据失败:%v", err)
}
if err := store.Put(t.Context(), "secret://ssh/key-a", map[string]string{"private_key": "secret-a"}); err != nil {
if err := store.Put(t.Context(), "secret://dns/provider-a", map[string]string{"api_token": "secret-a"}); err != nil {
t.Fatalf("保存第一个 SSH 私钥失败:%v", err)
}
items, err := store.List(t.Context(), "secret://ssh/")
items, err := store.List(t.Context(), "secret://dns/")
if err != nil {
t.Fatalf("列出 SSH 私钥引用失败:%v", err)
}
if len(items) != 2 || items[0].Reference != "secret://ssh/key-a" || items[1].Reference != "secret://ssh/key-b" {
if len(items) != 3 || items[1].Reference != "secret://dns/provider-a" || items[2].Reference != "secret://dns/provider-b" {
t.Fatalf("SSH 私钥引用列表不正确:%+v", items)
}
if err := store.Delete(t.Context(), items[0].Reference); err != nil {
if err := store.Delete(t.Context(), items[1].Reference); err != nil {
t.Fatalf("删除 SSH 私钥引用失败:%v", err)
}
exists, err := store.Has(t.Context(), items[0].Reference)
exists, err := store.Has(t.Context(), items[1].Reference)
if err != nil || exists {
t.Fatalf("SSH 私钥引用删除后仍然存在:exists=%t err=%v", exists, err)
}
if err := store.Delete(t.Context(), items[0].Reference); err == nil {
if err := store.Delete(t.Context(), items[1].Reference); err == nil {
t.Fatal("重复删除不存在的敏感信息引用应返回错误")
}
}

View File

@ -1658,3 +1658,14 @@ SQLite 新测试程序 SHA-256:4c81656fae63a2eebad999d58e6a79e63e6f441810e02ce
- 第一台 `8.137.23.95`:使用同一归档和官方安装器完成停服升级后启动 `Scert.service` 和 `Scert-storage.socket`;Nginx 未停止;既有站点配置和证书保持不变。
- 端口检查:双机 Scert 仅绑定回环 `8443`;MySQL 仅绑定回环 `3306`;第二台 `33060` 仅绑定回环。绕过本机代理直连公网时,三个内部端口均超时;对外项目入口仅为 Nginx `80/443`。未关闭或修改第一台现有邮件业务端口。
- 结果:双机原生包部署和 Nginx 反代端口门禁完成,等待复审;不代表整体生产部署验收通过。SMTP、密码重置、完整 Web 流程、历史事务、DNS/ACME 和最终清库首次部署仍未闭环。
## 2026-10-01 SSH 私钥登录能力安全复核
当前总体状态仍为:**P0-03:完成,等待复审**。本轮安全整改不代表生产部署整体验收通过。
- [x] 后端服务器模型、SSH 连接工厂、服务器 API 和服务器仓储已收敛为密码认证,未保留 SSH 登录私钥字段或部署路径。
- [x] 新增数据库迁移 19,清理历史服务器记录中的 SSH 私钥引用和 SSH 密钥状态;历史表结构迁移文件仅用于兼容既有数据库,不代表当前能力重新启用。
- [x] 敏感信息存储测试已改为 DNS 凭据场景,不再创建或读取 `secret://ssh/key-*` 测试引用。
- [x] TLS 证书业务的 `private_key_path`、证书上传、部署和回滚逻辑保持不变;该字段只用于证书私钥,不用于 SSH 登录。
- [x] `go test ./...`、三个 Go 命令构建和 `git diff --check` 已通过。
- [!] 仍未完成清库后的三数据库 Web 首次初始化、Smail 系统账户真实发信、验证码重置闭环、P0-10 负向矩阵、双机失败重试和最终生产门禁,因此不得标记为整体验收通过。

View File

@ -1822,3 +1822,14 @@ P0-05 从 `[!]` 调整为 `[~]`,并未标记完成;P0-04 剩余门禁和全
- [!] 总体生产验收仍未通过;清库后的首次生产初始化、Smail 系统账户发信、密码重置闭环、三数据库 Web 矩阵、P0-10 负向矩阵和最终候选包实机复核仍需继续。
当前总体验收状态仍为:**P0-03:完成,等待复审**。本次修复不代表生产验收通过。
## 2026-10-01 SSH 私钥登录能力安全复核
当前总体状态仍为:**P0-03:完成,等待复审**。本轮安全整改不代表生产部署整体验收通过。
- [x] 后端服务器模型、SSH 连接工厂、服务器 API 和服务器仓储已收敛为密码认证,未保留 SSH 登录私钥字段或部署路径。
- [x] 新增数据库迁移 19,清理历史服务器记录中的 SSH 私钥引用和 SSH 密钥状态;历史表结构迁移文件仅用于兼容既有数据库,不代表当前能力重新启用。
- [x] 敏感信息存储测试已改为 DNS 凭据场景,不再创建或读取 `secret://ssh/key-*` 测试引用。
- [x] TLS 证书业务的 `private_key_path`、证书上传、部署和回滚逻辑保持不变;该字段只用于证书私钥,不用于 SSH 登录。
- [x] `go test ./...`、三个 Go 命令构建和 `git diff --check` 已通过。
- [!] 仍未完成清库后的三数据库 Web 首次初始化、Smail 系统账户真实发信、验证码重置闭环、P0-10 负向矩阵、双机失败重试和最终生产门禁,因此不得标记为整体验收通过。

View File

@ -2454,3 +2454,14 @@ P0-11 生产验收门禁
- [!] 总体生产验收仍未通过;清库后的首次生产初始化、Smail 系统账户发信、密码重置闭环、三数据库 Web 矩阵、P0-10 负向矩阵和最终候选包实机复核仍需继续。
当前总体验收状态仍为:**P0-03:完成,等待复审**。本次修复不代表生产验收通过。
## 2026-10-01 SSH 私钥登录能力安全复核
当前总体状态仍为:**P0-03:完成,等待复审**。本轮安全整改不代表生产部署整体验收通过。
- [x] 后端服务器模型、SSH 连接工厂、服务器 API 和服务器仓储已收敛为密码认证,未保留 SSH 登录私钥字段或部署路径。
- [x] 新增数据库迁移 19,清理历史服务器记录中的 SSH 私钥引用和 SSH 密钥状态;历史表结构迁移文件仅用于兼容既有数据库,不代表当前能力重新启用。
- [x] 敏感信息存储测试已改为 DNS 凭据场景,不再创建或读取 `secret://ssh/key-*` 测试引用。
- [x] TLS 证书业务的 `private_key_path`、证书上传、部署和回滚逻辑保持不变;该字段只用于证书私钥,不用于 SSH 登录。
- [x] `go test ./...`、三个 Go 命令构建和 `git diff --check` 已通过。
- [!] 仍未完成清库后的三数据库 Web 首次初始化、Smail 系统账户真实发信、验证码重置闭环、P0-10 负向矩阵、双机失败重试和最终生产门禁,因此不得标记为整体验收通过。