package dnsprovider import ( "context" "errors" "strings" "testing" ) func scopeCredentials() map[string]string { return map[string]string{"access_key_id": "test-id", "access_key_secret": "test-secret", "managed_zones": "example.com,sub.example.com", "allowed_domains": "www.sub.example.com,*.example.com"} } func TestAliyunScopeBoundaries(t *testing.T) { scope, err := ParseAliyunScope(scopeCredentials()) if err != nil { t.Fatal(err) } for _, tc := range []struct{ name, zone string }{ {"www.sub.example.com", "sub.example.com"}, {"WWW.EXAMPLE.COM.", "example.com"}, {"*.example.com", "example.com"}, {"example.com", ""}, {"evil-example.com", ""}, {"example.com.evil.net", ""}, {"deep.www.example.com", ""}, {"*.sub.example.com", ""}, {"pcux.cn", ""}, {"com", ""}, } { t.Run(tc.name, func(t *testing.T) { zone, err := scope.Resolve(tc.name) if zone != tc.zone || (err != nil) != (tc.zone == "") { t.Fatalf("范围校验错误:zone=%s err=%v", zone, err) } }) } for _, value := range []string{"", "com", "co.uk", "*.example.com", "example.com,", "example.com,co.uk"} { credentials := scopeCredentials() credentials["managed_zones"] = value if _, err := ParseAliyunScope(credentials); err == nil { t.Fatalf("不安全托管区未拒绝:%s", value) } } credentials := scopeCredentials() credentials["allowed_domains"] = "outside.net" if _, err := ParseAliyunScope(credentials); err == nil { t.Fatal("越界的允许范围必须整体拒绝") } } type fakeAliyunRecords struct { zones, rrs, values, deleted []string addErr, describeErr, deleteErr error describeInfo aliyunRecordInfo canceledCleanup bool } func (f *fakeAliyunRecords) Add(_ context.Context, zone, rr, value string) (string, error) { f.zones = append(f.zones, zone) f.rrs = append(f.rrs, rr) f.values = append(f.values, value) return "created-record-id", f.addErr } func (f *fakeAliyunRecords) Describe(_ context.Context, _ string) (aliyunRecordInfo, error) { if f.describeErr != nil { return aliyunRecordInfo{}, f.describeErr } if f.describeInfo.RecordID != "" { return f.describeInfo, nil } if len(f.zones) == 0 || len(f.rrs) == 0 || len(f.values) == 0 { return aliyunRecordInfo{}, errors.New("没有可供查询的临时记录") } return aliyunRecordInfo{ RecordID: "created-record-id", DomainName: f.zones[len(f.zones)-1], RR: f.rrs[len(f.rrs)-1], Type: "TXT", Value: f.values[len(f.values)-1], }, nil } func (f *fakeAliyunRecords) Delete(ctx context.Context, id string) error { f.deleted = append(f.deleted, id) f.canceledCleanup = ctx.Err() != nil return f.deleteErr } func TestAliyunProbeNeverFallsBackOrCallsCloudOutsideScope(t *testing.T) { api := &fakeAliyunRecords{addErr: errors.New("Forbidden.RAM")} checker := NewAliyunAuthorizationChecker() constructed := 0 checker.recordAPI = func(map[string]string) (aliyunRecordAPI, error) { constructed++; return api, nil } credentials := scopeCredentials() for _, name := range []string{"example.com", "outside.net", "pcux.cn", "*.sub.example.com"} { if _, err := checker.Verify(t.Context(), name, credentials); err == nil { t.Fatal("越界请求未拒绝") } } if constructed != 0 { t.Fatal("越界请求不能创建云客户端") } if _, err := checker.Verify(t.Context(), "www.sub.example.com", credentials); err == nil { t.Fatal("RAM 拒绝必须返回失败") } if len(api.zones) != 1 || api.zones[0] != "sub.example.com" || !strings.HasSuffix(api.rrs[0], ".www") { t.Fatalf("必须仅写授权子域:%v %v", api.zones, api.rrs) } if len(api.deleted) != 0 { t.Fatal("新增失败不能猜测记录 ID 删除") } api.addErr = nil api.deleteErr = errors.New("Forbidden.RAM") report, err := checker.Verify(t.Context(), "www.sub.example.com", credentials) if err == nil || report.Verified || !report.Domains[0].Writable || report.Domains[0].Cleanup { t.Fatal("清理失败不能证明权限通过") } api.deleteErr = nil report, err = checker.Verify(t.Context(), "www.sub.example.com", credentials) if err != nil || !report.Verified || api.deleted[len(api.deleted)-1] != "created-record-id" { t.Fatal("必须删除本次新增记录") } } func TestAliyunProviderPinsZoneAndCleanupIdentity(t *testing.T) { scope, _ := ParseAliyunScope(scopeCredentials()) api := &fakeAliyunRecords{} p := &scopedAliyunProvider{scope: scope, api: api, records: make(map[[32]byte]string), challengeInfo: func(_ context.Context, name, _ string) (string, string, string) { fqdn := "_acme-challenge." + name + "." return fqdn, fqdn, "txt-value" }} if err := p.Present(t.Context(), "outside.net", "token", "key"); err == nil || len(api.zones) != 0 { t.Fatal("实际 Provider 必须零调用拒绝越界") } if err := p.Present(t.Context(), "www.sub.example.com", "token", "key"); err != nil { t.Fatal(err) } if len(api.zones) != 1 || api.zones[0] != "sub.example.com" || api.rrs[0] != "_acme-challenge.www" { t.Fatal("实际写入没有锁定指定托管区") } if err := p.Present(t.Context(), "www.sub.example.com", "token", "key"); err == nil { t.Fatal("重复挑战不能覆盖待清理 ID") } _ = p.CleanUp(t.Context(), "www.sub.example.com", "other", "key") if len(api.deleted) != 0 { t.Fatal("不能清理其他挑战") } ctx, cancel := context.WithCancel(t.Context()) cancel() if err := p.CleanUp(ctx, "www.sub.example.com", "token", "key"); err != nil || api.canceledCleanup || len(api.deleted) != 1 { t.Fatal("请求取消后仍需有界清理本次 ID") } p.challengeInfo = func(context.Context, string, string) (string, string, string) { return "_acme-challenge.www.sub.example.com.", "_acme-challenge.outside.net.", "value" } if err := p.Present(t.Context(), "www.sub.example.com", "new", "key"); err == nil || len(api.zones) != 1 { t.Fatal("CNAME 越界不能调用云 API") } }