Scert/backend/internal/dnsprovider/aliyun_scope_test.go

162 lines
5.8 KiB
Go
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.

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")
}
}