From 91e1123fa688ac3660b0c8d0276ee562c3ef9a79 Mon Sep 17 00:00:00 2001 From: xuans Date: Wed, 30 Sep 2026 21:49:00 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=98=BF=E9=87=8C=E4=BA=91?= =?UTF-8?q?=E6=8E=88=E6=9D=83=E6=8E=A2=E6=B5=8B=E9=9A=8F=E6=9C=BA=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E5=90=8D=E6=A0=BC=E5=BC=8F=E5=B9=B6=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E5=9B=9E=E5=BD=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dnsprovider/aliyun_authorization.go | 15 ++++- .../aliyun_authorization_label_test.go | 58 +++++++++++++++++++ doc/Linux生产流程测试记录.md | 7 +++ doc/Scert整改任务执行清单.md | 6 +- doc/Scert整改合并优化清单.md | 10 ++++ 5 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 backend/internal/dnsprovider/aliyun_authorization_label_test.go diff --git a/backend/internal/dnsprovider/aliyun_authorization.go b/backend/internal/dnsprovider/aliyun_authorization.go index d09e2d4..61718d8 100644 --- a/backend/internal/dnsprovider/aliyun_authorization.go +++ b/backend/internal/dnsprovider/aliyun_authorization.go @@ -4,6 +4,7 @@ import ( "context" "crypto/rand" "encoding/base64" + "encoding/hex" "errors" "fmt" "strings" @@ -97,7 +98,7 @@ func (c *AliyunAuthorizationChecker) verifyResolved(ctx context.Context, request if err != nil { return result, err } - suffix, err := randomAuthorizationValue(18) + suffix, err := randomAuthorizationLabel() if err != nil { return result, err } @@ -289,6 +290,15 @@ func randomAuthorizationValue(size int) (string, error) { return base64.RawURLEncoding.EncodeToString(data), nil } +func randomAuthorizationLabel() (string, error) { + // 主机记录标签不能以连字符结尾;完整编码随机字节,避免替换字符造成分布偏差。 + data := make([]byte, 18) + if _, err := rand.Read(data); err != nil { + return "", err + } + return hex.EncodeToString(data), nil +} + func dereferenceString(value *string) string { if value == nil { return "" @@ -307,6 +317,9 @@ func DiagnoseAliyunError(err error, domainName string) string { } msg := err.Error() lowered := strings.ToLower(msg) + if strings.Contains(lowered, "invalidrr.format") { + return "阿里云拒绝了授权探测记录名格式,本次验证未通过;这不代表 RAM 权限不足,请检查系统生成的记录名,不要扩大账号权限" + } if strings.Contains(msg, "权威 DNS 托管区") || strings.Contains(lowered, "soa lookup") { return "未能从公开 DNS 的 SOA 记录识别该域名唯一的权威托管区,系统未构造阿里云客户端,也未创建临时 TXT 记录。请检查域名是否已正确委派并可被公网 DNS 查询后重试" } diff --git a/backend/internal/dnsprovider/aliyun_authorization_label_test.go b/backend/internal/dnsprovider/aliyun_authorization_label_test.go new file mode 100644 index 0000000..eeb4aea --- /dev/null +++ b/backend/internal/dnsprovider/aliyun_authorization_label_test.go @@ -0,0 +1,58 @@ +package dnsprovider + +import ( + "encoding/base64" + "encoding/hex" + "errors" + "strings" + "testing" +) + +func TestAliyunAuthorizationRecordLabelUsesSafeRandomSuffix(t *testing.T) { + for _, name := range []string{"example.com", "web.example.com", "*.web.example.com", "www.sub.example.com"} { + t.Run(name, func(t *testing.T) { + seen := make(map[string]bool) + for attempt := 0; attempt < 32; attempt++ { + api := &authorizationRecordAPI{} + report, err := newAuthorizationCheckerForTest(api).Verify(t.Context(), name, scopeCredentials()) + if err != nil || !report.Verified { + t.Fatalf("授权闭环验证失败:report=%+v err=%v", report, err) + } + label := strings.SplitN(api.rr, ".", 2)[0] + const prefix = "_scert-authorization-" + suffix := strings.TrimPrefix(label, prefix) + if !strings.HasPrefix(label, prefix) || len(label) > 63 || len(suffix) != 36 { + t.Fatalf("随机主机记录标签必须使用固定长度安全后缀:%q", label) + } + decoded, err := hex.DecodeString(suffix) + if err != nil || len(decoded) != 18 || suffix != strings.ToLower(suffix) { + t.Fatalf("随机后缀必须完整编码十八字节随机数且只使用小写十六进制:%q", suffix) + } + if seen[suffix] { + t.Fatalf("不同请求复用了随机探测后缀:%q", suffix) + } + seen[suffix] = true + targetSuffix := strings.TrimSuffix(strings.TrimPrefix(name, "*."), ".example.com") + if name == "example.com" { + targetSuffix = "" + } else { + targetSuffix = "." + targetSuffix + } + if api.rr != label+targetSuffix || report.RecordName != api.rr+".example.com." { + t.Fatalf("探测记录越出指定域名范围:rr=%q record=%q", api.rr, report.RecordName) + } + value, err := base64.RawURLEncoding.DecodeString(api.value) + if err != nil || len(value) != 32 || callNames(api.calls) != "add,describe,delete" { + t.Fatalf("记录名修复不应改变 TXT 随机强度或写读清理闭环:value=%q calls=%v", api.value, api.calls) + } + } + }) + } +} + +func TestAliyunInvalidRecordFormatDiagnosisDoesNotSuggestPermissionChange(t *testing.T) { + message := DiagnoseAliyunError(errors.New("SDKError: Code: InvalidRR.Format secret-provider-detail"), "web.example.com") + if !strings.Contains(message, "记录名格式") || !strings.Contains(message, "权限不足") || strings.Contains(message, "secret-provider-detail") { + t.Fatalf("随机记录名格式错误应提供脱敏且不误导扩权的说明:%s", message) + } +} diff --git a/doc/Linux生产流程测试记录.md b/doc/Linux生产流程测试记录.md index 8bb64a5..15b5cb1 100644 --- a/doc/Linux生产流程测试记录.md +++ b/doc/Linux生产流程测试记录.md @@ -1,4 +1,11 @@ # Linux 生产流程测试记录 +## DNS 标签整改本地验证(2026-09-30 21:48) + +- 修改前检查点 0864300 已推送。新增正式回归在旧代码下全部失败:随机后缀不是规定的三十六位安全编码,InvalidRR.Format 被误归入网络或权限通用提示。 +- 记录名随机后缀改用十八字节密码学随机数的完整小写十六进制编码,TXT 随机值继续独立生成三十二字节,避免尾部连字符及大小写折叠问题,不依赖失败重试。 +- 四类域名各三十二次回归检查后缀可还原十八字节、标签不超过六十三字节、范围不变、TXT 可还原三十二字节、调用顺序 add/describe/delete 不变。随机样本唯一性检查不是独立的熵证明;随机来源和完整编码由源码确定。 +- DNS 包完整测试、静态检查和竞态通过;后端全量测试与静态检查退出 0。新修复尚未部署,不提前声明云端问题已复审解决。 + ## 重载失败恢复与新增 DNS 阻塞(2026-09-30 21:45) - 21:33 正式页面部署已有证书成功,报告 status=success、verification_status=success,任务 fe00eb0d-dba8-400e-8c8a-852ab4abb451;页面区分推送与 HTTPS 验证。 diff --git a/doc/Scert整改任务执行清单.md b/doc/Scert整改任务执行清单.md index 435b4d4..f327792 100644 --- a/doc/Scert整改任务执行清单.md +++ b/doc/Scert整改任务执行清单.md @@ -5,8 +5,10 @@ - [x] 新候选正常部署通过;真实重载后主动返回失败,原文件 inode、哈希和权限恢复,无残留锁;恢复正常命令后重新部署成功。 - [x] 再次申请在 DNS 授权探测阶段返回 InvalidRR.Format,未生成第二张证书;产品权限接口相邻两次验证一成功一失败,失败记录首标签以连字符结尾。 -- [~] 将记录名随机后缀与 TXT 随机值分离,记录名采用固定长度安全字符集;补正式回归,保留随机强度和精确清理规则。 -- [ ] 本地回归、正式构建、部署后权限探测及签发复测。 +- [x] 将记录名随机后缀与 TXT 随机值分离,十八字节随机数完整编码为三十六个小写十六进制字符;保留 TXT 随机强度和精确清理规则。 +- [x] 新增四类域名各三十二次正式回归及格式错误脱敏诊断回归,修复前失败、修复后通过;DNS 包测试、静态检查和竞态通过。 +- [x] 后端全量测试与静态检查退出 0。 +- [ ] 正式构建、部署后权限探测及签发复测。 - [!] 同域名旧有效证书的公网负向验证尚未执行到 HTTPS 阶段;不能将本次 DNS 失败计入 HTTPS 验收。服务器重载命令已恢复正常。 ## 当前执行任务(2026-09-30 21:33,最新候选实机复测) diff --git a/doc/Scert整改合并优化清单.md b/doc/Scert整改合并优化清单.md index cc62826..c73751e 100644 --- a/doc/Scert整改合并优化清单.md +++ b/doc/Scert整改合并优化清单.md @@ -1,4 +1,14 @@ # Scert 整改、合并与优化清单 +## 最新阻塞整改(2026-09-30 21:48,DNS 探测随机标签) + +- [x] fc57b8e 已实机正常部署;重载执行后返回失败时文件回滚,恢复正常命令后再次部署成功,详细边界见 Linux 测试记录。 +- [x] 实机发现 DNS 授权探测随机记录名首标签可能以连字符结尾,导致 InvalidRR.Format;相邻请求同一账户一成功一失败,并非已经证明 RAM 权限不足。 +- [x] 随机记录名改用十八字节随机数的小写十六进制编码;TXT 仍使用独立三十二字节随机值,精确写入、查询和删除闭环不变。增加格式错误独立脱敏诊断,不引导扩大权限。 +- [x] 新回归修复前失败、修复后通过;覆盖根域、子域、泛域、多级子域,每种连续三十二次,检查字符集、随机强度、标签长度、目标范围、清理闭环;DNS 包测试、静态检查和竞态通过。 +- [x] 后端全量测试及静态检查退出 0。 +- [~] 提交后需规范正式构建和部署,再继续真实权限及 HTTPS 负向验收。 +- [!] 不将本次 DNS 失败算作 HTTPS 身份负向通过;单机多域名、多服务器、三数据库和清库首次流程仍有待办。 + ## 最新实机进度(2026-09-30 21:33,最新候选已部署) - [x] 部署后 HTTPS 身份与状态修复 fc57b8e 已完成全量本地检查及应用、接口、部署专项竞态;前端 239 项通过。