完善阿里云授权动态刷新与失效处理
This commit is contained in:
parent
b5733b0e7c
commit
fce03798b5
@ -33,6 +33,10 @@ type DNSAuthorizationChecker interface {
|
||||
VerifyDomain(ctx context.Context, providerID, requestedDomain string, credentials map[string]string) error
|
||||
}
|
||||
|
||||
type DNSAuthorizationCacheInvalidator interface {
|
||||
InvalidateDomain(providerID, requestedDomain string, credentials map[string]string)
|
||||
}
|
||||
|
||||
type AccountManager interface {
|
||||
LoadOrCreate(ctx context.Context, environment, email string) (*AccountUser, error)
|
||||
Save(ctx context.Context, environment string, user *AccountUser) error
|
||||
@ -160,6 +164,9 @@ func (s *Service) Issue(ctx context.Context, domainID string) (IssueResult, erro
|
||||
resource, issueErr := issuer.Obtain(ctx, ObtainRequest{
|
||||
Domains: domains, AccountEmail: email, Environment: item.ACMEEnvironment, PreferredChain: s.preferredChain,
|
||||
})
|
||||
if issueErr != nil {
|
||||
s.invalidateDNSAuthorizationOnFailure(providerID, domains, credentials, issueErr)
|
||||
}
|
||||
if saveErr := s.accounts.Save(ctx, item.ACMEEnvironment, user); saveErr != nil {
|
||||
if issueErr != nil {
|
||||
return fail(fmt.Errorf("%v;保存 ACME 账户状态失败:%w", issueErr, saveErr))
|
||||
@ -234,6 +241,9 @@ func (s *Service) IssueForSetup(ctx context.Context, request SetupCertificateReq
|
||||
resource, issueErr := issuer.Obtain(ctx, ObtainRequest{
|
||||
Domains: domains, AccountEmail: accountEmail, Environment: environment, PreferredChain: s.preferredChain,
|
||||
})
|
||||
if issueErr != nil {
|
||||
s.invalidateDNSAuthorizationOnFailure(providerID, domains, credentials, issueErr)
|
||||
}
|
||||
if saveErr := s.accounts.Save(ctx, environment, user); saveErr != nil {
|
||||
if issueErr != nil {
|
||||
return SavedResource{}, fmt.Errorf("%v;保存 ACME 账户状态失败:%w", issueErr, saveErr)
|
||||
@ -270,6 +280,39 @@ func (s *Service) verifyDNSAuthorization(ctx context.Context, providerID string,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) invalidateDNSAuthorization(providerID string, domains []string, credentials map[string]string) {
|
||||
invalidator, ok := s.authorization.(DNSAuthorizationCacheInvalidator)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for _, requestedDomain := range domains {
|
||||
invalidator.InvalidateDomain(providerID, requestedDomain, credentials)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) invalidateDNSAuthorizationOnFailure(providerID string, domains []string, credentials map[string]string, issueErr error) {
|
||||
if !looksLikeDNSAuthorizationFailure(issueErr) {
|
||||
return
|
||||
}
|
||||
s.invalidateDNSAuthorization(providerID, domains, credentials)
|
||||
}
|
||||
|
||||
func looksLikeDNSAuthorizationFailure(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
message := strings.ToLower(err.Error())
|
||||
for _, marker := range []string{
|
||||
"dns-01", "dns challenge", "txt record", "present", "cleanup", "clean up",
|
||||
"unauthorized", "forbidden", "permission", "accessdenied", "权限", "无权", "授权",
|
||||
} {
|
||||
if strings.Contains(message, marker) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Service) Import(ctx context.Context, request ImportRequest) (IssueResult, error) {
|
||||
if s == nil || s.repository == nil {
|
||||
return IssueResult{}, errors.New("证书导入服务未初始化")
|
||||
|
||||
@ -58,6 +58,20 @@ func (serviceProviderFactory) Create(string, map[string]string) (challenge.Provi
|
||||
return serviceProvider{}, nil
|
||||
}
|
||||
|
||||
type serviceAuthorizationChecker struct {
|
||||
verifyCalls int
|
||||
invalidateCalls int
|
||||
}
|
||||
|
||||
func (c *serviceAuthorizationChecker) VerifyDomain(context.Context, string, string, map[string]string) error {
|
||||
c.verifyCalls++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *serviceAuthorizationChecker) InvalidateDomain(string, string, map[string]string) {
|
||||
c.invalidateCalls++
|
||||
}
|
||||
|
||||
type recordingProviderFactory struct {
|
||||
providerID string
|
||||
credentials map[string]string
|
||||
@ -157,6 +171,50 @@ func TestServiceFailureKeepsExistingCertificate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceInvalidatesDNSAuthorizationAfterACMEFailure(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
cfg := certificateTestConfig(root)
|
||||
item, err := domain.NewDomainWithOptions("example.com", domain.CertificateSingle, nil, true, domain.ACMEEnvironmentStaging, "cloudflare")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
repository := &serviceRepository{item: item}
|
||||
issuer := &serviceIssuer{err: errors.New("模拟 DNS 权限已被收回")}
|
||||
authorization := &serviceAuthorizationChecker{}
|
||||
service := NewService(cfg, repository, serviceCredentials{}, serviceProviderFactory{}, serviceAccounts{})
|
||||
service.SetDNSAuthorizationChecker(authorization)
|
||||
service.newIssuer = func(*AccountUser, challenge.Provider, string) Issuer { return issuer }
|
||||
|
||||
if _, err := service.Issue(context.Background(), item.ID); err == nil {
|
||||
t.Fatal("ACME 申请失败时应返回错误")
|
||||
}
|
||||
if authorization.verifyCalls != 1 || authorization.invalidateCalls != 1 {
|
||||
t.Fatalf("ACME 失败后的授权缓存处理错误:verify=%d invalidate=%d", authorization.verifyCalls, authorization.invalidateCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceDoesNotInvalidateDNSAuthorizationForUnrelatedACMEFailure(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
cfg := certificateTestConfig(root)
|
||||
item, err := domain.NewDomainWithOptions("example.com", domain.CertificateSingle, nil, true, domain.ACMEEnvironmentStaging, "cloudflare")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
repository := &serviceRepository{item: item}
|
||||
issuer := &serviceIssuer{err: errors.New("ACME 服务暂时不可用")}
|
||||
authorization := &serviceAuthorizationChecker{}
|
||||
service := NewService(cfg, repository, serviceCredentials{}, serviceProviderFactory{}, serviceAccounts{})
|
||||
service.SetDNSAuthorizationChecker(authorization)
|
||||
service.newIssuer = func(*AccountUser, challenge.Provider, string) Issuer { return issuer }
|
||||
|
||||
if _, err := service.Issue(context.Background(), item.ID); err == nil {
|
||||
t.Fatal("ACME 申请失败时应返回错误")
|
||||
}
|
||||
if authorization.invalidateCalls != 0 {
|
||||
t.Fatalf("无关的 ACME 错误不应清除授权缓存:invalidate=%d", authorization.invalidateCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceImportsOwnedCertificate(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
cfg := certificateTestConfig(root)
|
||||
|
||||
@ -3,11 +3,13 @@ package dnsprovider
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.pcux.cn/xuans/Scert/backend/internal/domain"
|
||||
@ -17,9 +19,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
aliyunDefaultRegion = "cn-hangzhou"
|
||||
aliyunAuthorizationTTL = 60
|
||||
aliyunAuthorizationTimeout = 30 * time.Second
|
||||
aliyunDefaultRegion = "cn-hangzhou"
|
||||
aliyunAuthorizationTTL = 60
|
||||
aliyunAuthorizationTimeout = 30 * time.Second
|
||||
aliyunAuthorizationCacheTTL = 10 * time.Minute
|
||||
)
|
||||
|
||||
// AuthorizationDomain 描述一个阿里云 DNS 根域名的授权探测结果。
|
||||
@ -44,11 +47,14 @@ type AuthorizationReport struct {
|
||||
|
||||
// AliyunAuthorizationChecker 负责发现并证明 RAM 账号对阿里云 DNS 根域名的实际写权限。
|
||||
type AliyunAuthorizationChecker struct {
|
||||
now func() time.Time
|
||||
mu sync.Mutex
|
||||
now func() time.Time
|
||||
cache map[string]time.Time
|
||||
verifyDomainFunc func(context.Context, string, map[string]string) error
|
||||
}
|
||||
|
||||
func NewAliyunAuthorizationChecker() *AliyunAuthorizationChecker {
|
||||
return &AliyunAuthorizationChecker{now: time.Now}
|
||||
return &AliyunAuthorizationChecker{now: time.Now, cache: make(map[string]time.Time)}
|
||||
}
|
||||
|
||||
func (c *AliyunAuthorizationChecker) Supports(providerID string) bool {
|
||||
@ -74,57 +80,51 @@ func (c *AliyunAuthorizationChecker) Discover(ctx context.Context, credentials m
|
||||
return report, nil
|
||||
}
|
||||
|
||||
// Verify 对目标域名所属的最长托管根域名执行最小写入和精确清理。
|
||||
// Verify 从目标域名到父级托管区逐级执行最小写入和精确清理。
|
||||
func (c *AliyunAuthorizationChecker) Verify(ctx context.Context, requestedDomain string, credentials map[string]string) (AuthorizationReport, error) {
|
||||
requestedDomain = normalizeAuthorizationDomain(requestedDomain)
|
||||
report, err := c.Discover(ctx, credentials)
|
||||
report.RequestedDomain = requestedDomain
|
||||
if err != nil {
|
||||
return report, err
|
||||
}
|
||||
report := AuthorizationReport{Provider: Aliyun, RequestedDomain: requestedDomain, CheckedAt: c.currentTime().UTC()}
|
||||
if !domain.IsValidName(requestedDomain) {
|
||||
return report, fmt.Errorf("待验证域名格式无效:%s", requestedDomain)
|
||||
}
|
||||
matched := matchManagedDomain(requestedDomain, report.Domains)
|
||||
if matched == "" {
|
||||
return report, fmt.Errorf("阿里云 RAM 账号未发现域名 %s 所属的托管根域名", requestedDomain)
|
||||
for _, candidate := range authorizationZoneCandidates(requestedDomain) {
|
||||
report.Domains = append(report.Domains, AuthorizationDomain{
|
||||
Domain: candidate, Status: "待验证",
|
||||
})
|
||||
}
|
||||
report.MatchedDomain = matched
|
||||
for index := range report.Domains {
|
||||
if report.Domains[index].Domain == matched {
|
||||
report.Domains[index].Status = "正在证明写入权限"
|
||||
}
|
||||
}
|
||||
|
||||
client, err := newAliyunClient(credentials)
|
||||
if err != nil {
|
||||
return report, err
|
||||
}
|
||||
recordID, err := addAuthorizationRecord(ctx, client, matched)
|
||||
if err != nil {
|
||||
markAuthorizationFailure(&report, matched, fmt.Errorf("创建授权探测 TXT 记录失败:%w", err))
|
||||
return report, err
|
||||
}
|
||||
for index := range report.Domains {
|
||||
if report.Domains[index].Domain == matched {
|
||||
report.Domains[index].Writable = true
|
||||
}
|
||||
}
|
||||
|
||||
cleanupContext, cancel := context.WithTimeout(context.WithoutCancel(ctx), aliyunAuthorizationTimeout)
|
||||
defer cancel()
|
||||
if err := deleteAuthorizationRecord(cleanupContext, client, recordID); err != nil {
|
||||
markAuthorizationFailure(&report, matched, fmt.Errorf("清理授权探测 TXT 记录失败:%w", err))
|
||||
return report, err
|
||||
}
|
||||
for index := range report.Domains {
|
||||
if report.Domains[index].Domain == matched {
|
||||
report.Domains[index].Cleanup = true
|
||||
report.Domains[index].Status = "可操作"
|
||||
var lastErr error
|
||||
for index, candidate := range authorizationZoneCandidates(requestedDomain) {
|
||||
report.Domains[index].Status = "正在证明写入权限"
|
||||
recordID, probeErr := addAuthorizationRecord(ctx, client, candidate)
|
||||
if probeErr != nil {
|
||||
lastErr = probeErr
|
||||
report.Domains[index].Status = "不可操作"
|
||||
report.Domains[index].Error = probeErr.Error()
|
||||
continue
|
||||
}
|
||||
report.Domains[index].Writable = true
|
||||
report.MatchedDomain = candidate
|
||||
cleanupContext, cancel := context.WithTimeout(context.WithoutCancel(ctx), aliyunAuthorizationTimeout)
|
||||
cleanupErr := deleteAuthorizationRecord(cleanupContext, client, recordID)
|
||||
cancel()
|
||||
if cleanupErr != nil {
|
||||
markAuthorizationFailure(&report, candidate, fmt.Errorf("清理授权探测 TXT 记录失败:%w", cleanupErr))
|
||||
return report, cleanupErr
|
||||
}
|
||||
report.Domains[index].Cleanup = true
|
||||
report.Domains[index].Status = "可操作"
|
||||
report.Verified = true
|
||||
return report, nil
|
||||
}
|
||||
report.Verified = true
|
||||
return report, nil
|
||||
if lastErr == nil {
|
||||
lastErr = errors.New("未找到可验证的阿里云 DNS 托管根域名")
|
||||
}
|
||||
return report, fmt.Errorf("阿里云 RAM 账号无法写入域名 %s:%w", requestedDomain, lastErr)
|
||||
}
|
||||
|
||||
// VerifyDomain 实现证书服务使用的最小授权检查接口。
|
||||
@ -132,6 +132,17 @@ func (c *AliyunAuthorizationChecker) VerifyDomain(ctx context.Context, providerI
|
||||
if !c.Supports(providerID) {
|
||||
return nil
|
||||
}
|
||||
requestedDomain = normalizeAuthorizationDomain(requestedDomain)
|
||||
if c.cached(requestedDomain, credentials) {
|
||||
return nil
|
||||
}
|
||||
if c.verifyDomainFunc != nil {
|
||||
if err := c.verifyDomainFunc(ctx, requestedDomain, credentials); err != nil {
|
||||
return err
|
||||
}
|
||||
c.remember(requestedDomain, credentials)
|
||||
return nil
|
||||
}
|
||||
report, err := c.Verify(ctx, requestedDomain, credentials)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -139,9 +150,25 @@ func (c *AliyunAuthorizationChecker) VerifyDomain(ctx context.Context, providerI
|
||||
if !report.Verified {
|
||||
return errors.New("阿里云 DNS 域名授权未通过")
|
||||
}
|
||||
c.remember(requestedDomain, credentials)
|
||||
return nil
|
||||
}
|
||||
|
||||
// InvalidateDomain 在实际 ACME 写入失败后清除短时授权缓存,确保权限收缩能够被重新探测。
|
||||
func (c *AliyunAuthorizationChecker) InvalidateDomain(providerID, requestedDomain string, credentials map[string]string) {
|
||||
if c == nil || !c.Supports(providerID) {
|
||||
return
|
||||
}
|
||||
key := authorizationCacheKey(providerID, requestedDomain, credentials)
|
||||
c.mu.Lock()
|
||||
if c.cache == nil {
|
||||
c.mu.Unlock()
|
||||
return
|
||||
}
|
||||
delete(c.cache, key)
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *AliyunAuthorizationChecker) currentTime() time.Time {
|
||||
if c != nil && c.now != nil {
|
||||
return c.now()
|
||||
@ -250,6 +277,62 @@ func matchManagedDomain(requested string, domains []AuthorizationDomain) string
|
||||
return best
|
||||
}
|
||||
|
||||
func authorizationZoneCandidates(requested string) []string {
|
||||
requested = strings.TrimPrefix(normalizeAuthorizationDomain(requested), "*.")
|
||||
labels := strings.Split(requested, ".")
|
||||
result := make([]string, 0, len(labels)-1)
|
||||
for index := 0; index < len(labels)-1; index++ {
|
||||
candidate := strings.Join(labels[index:], ".")
|
||||
if domain.IsValidName(candidate) {
|
||||
result = append(result, candidate)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *AliyunAuthorizationChecker) cached(requestedDomain string, credentials map[string]string) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
now := c.currentTime().UTC()
|
||||
key := authorizationCacheKey(Aliyun, requestedDomain, credentials)
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
expiresAt, ok := c.cache[key]
|
||||
if !ok || !expiresAt.After(now) {
|
||||
delete(c.cache, key)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *AliyunAuthorizationChecker) remember(requestedDomain string, credentials map[string]string) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
key := authorizationCacheKey(Aliyun, requestedDomain, credentials)
|
||||
c.mu.Lock()
|
||||
if c.cache == nil {
|
||||
c.cache = make(map[string]time.Time)
|
||||
}
|
||||
c.cache[key] = c.currentTime().UTC().Add(aliyunAuthorizationCacheTTL)
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func authorizationCacheKey(providerID, requestedDomain string, credentials map[string]string) string {
|
||||
cleaned, _ := ValidateCredentials(providerID, credentials)
|
||||
keys := make([]string, 0, len(cleaned))
|
||||
for key := range cleaned {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
hash := sha256.New()
|
||||
for _, key := range keys {
|
||||
fmt.Fprintf(hash, "%d:%s=%d:%s\n", len(key), key, len(cleaned[key]), cleaned[key])
|
||||
}
|
||||
return fmt.Sprintf("%s|%s|%x", strings.ToLower(strings.TrimSpace(providerID)), normalizeAuthorizationDomain(requestedDomain), hash.Sum(nil))
|
||||
}
|
||||
|
||||
func markAuthorizationFailure(report *AuthorizationReport, domainName string, err error) {
|
||||
for index := range report.Domains {
|
||||
if report.Domains[index].Domain == domainName {
|
||||
|
||||
123
backend/internal/dnsprovider/aliyun_authorization_test.go
Normal file
123
backend/internal/dnsprovider/aliyun_authorization_test.go
Normal file
@ -0,0 +1,123 @@
|
||||
package dnsprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAuthorizationZoneCandidatesPreferSpecificZones(t *testing.T) {
|
||||
got := authorizationZoneCandidates("*.www.example.com.")
|
||||
want := []string{"www.example.com", "example.com"}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("候选托管区数量错误:got=%v want=%v", got, want)
|
||||
}
|
||||
for index := range want {
|
||||
if got[index] != want[index] {
|
||||
t.Fatalf("候选托管区顺序错误:got=%v want=%v", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAliyunAuthorizationCheckerCachesSuccessfulAuthorization(t *testing.T) {
|
||||
now := time.Date(2026, 9, 14, 12, 0, 0, 0, time.UTC)
|
||||
calls := 0
|
||||
checker := NewAliyunAuthorizationChecker()
|
||||
checker.now = func() time.Time { return now }
|
||||
checker.verifyDomainFunc = func(context.Context, string, map[string]string) error {
|
||||
calls++
|
||||
return nil
|
||||
}
|
||||
credentials := map[string]string{
|
||||
"access_key_id": "test-id",
|
||||
"access_key_secret": "test-secret",
|
||||
}
|
||||
|
||||
if err := checker.VerifyDomain(context.Background(), Aliyun, "www.example.com", credentials); err != nil {
|
||||
t.Fatalf("首次授权检查失败:%v", err)
|
||||
}
|
||||
if err := checker.VerifyDomain(context.Background(), Aliyun, "www.example.com", credentials); err != nil {
|
||||
t.Fatalf("缓存授权检查失败:%v", err)
|
||||
}
|
||||
if calls != 1 {
|
||||
t.Fatalf("短时授权缓存未生效:calls=%d", calls)
|
||||
}
|
||||
|
||||
now = now.Add(aliyunAuthorizationCacheTTL + time.Second)
|
||||
if err := checker.VerifyDomain(context.Background(), Aliyun, "www.example.com", credentials); err != nil {
|
||||
t.Fatalf("缓存过期后的授权检查失败:%v", err)
|
||||
}
|
||||
if calls != 2 {
|
||||
t.Fatalf("授权缓存过期后未重新探测:calls=%d", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAliyunAuthorizationCheckerInvalidatesAfterPermissionFailure(t *testing.T) {
|
||||
calls := 0
|
||||
checker := NewAliyunAuthorizationChecker()
|
||||
checker.verifyDomainFunc = func(context.Context, string, map[string]string) error {
|
||||
calls++
|
||||
return nil
|
||||
}
|
||||
credentials := map[string]string{
|
||||
"access_key_id": "test-id",
|
||||
"access_key_secret": "test-secret",
|
||||
}
|
||||
|
||||
if err := checker.VerifyDomain(context.Background(), Aliyun, "example.com", credentials); err != nil {
|
||||
t.Fatalf("首次授权检查失败:%v", err)
|
||||
}
|
||||
checker.InvalidateDomain(Aliyun, "example.com", credentials)
|
||||
if err := checker.VerifyDomain(context.Background(), Aliyun, "example.com", credentials); err != nil {
|
||||
t.Fatalf("权限失败后的重新授权检查失败:%v", err)
|
||||
}
|
||||
if calls != 2 {
|
||||
t.Fatalf("授权缓存未在权限失败后失效:calls=%d", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAliyunAuthorizationCheckerDoesNotCacheFailedAuthorization(t *testing.T) {
|
||||
calls := 0
|
||||
checker := NewAliyunAuthorizationChecker()
|
||||
checker.verifyDomainFunc = func(context.Context, string, map[string]string) error {
|
||||
calls++
|
||||
return errors.New("权限不足")
|
||||
}
|
||||
credentials := map[string]string{
|
||||
"access_key_id": "test-id",
|
||||
"access_key_secret": "test-secret",
|
||||
}
|
||||
|
||||
if err := checker.VerifyDomain(context.Background(), Aliyun, "example.com", credentials); err == nil {
|
||||
t.Fatal("权限失败时应返回错误")
|
||||
}
|
||||
if err := checker.VerifyDomain(context.Background(), Aliyun, "example.com", credentials); err == nil {
|
||||
t.Fatal("权限失败不应被缓存为成功")
|
||||
}
|
||||
if calls != 2 {
|
||||
t.Fatalf("失败授权检查被错误缓存:calls=%d", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAliyunAuthorizationCheckerCacheIsSafeForConcurrentUse(t *testing.T) {
|
||||
checker := NewAliyunAuthorizationChecker()
|
||||
checker.verifyDomainFunc = func(context.Context, string, map[string]string) error {
|
||||
return nil
|
||||
}
|
||||
credentials := map[string]string{
|
||||
"access_key_id": "test-id",
|
||||
"access_key_secret": "test-secret",
|
||||
}
|
||||
done := make(chan struct{})
|
||||
for index := 0; index < 20; index++ {
|
||||
go func() {
|
||||
defer func() { done <- struct{}{} }()
|
||||
_ = checker.VerifyDomain(context.Background(), Aliyun, "example.com", credentials)
|
||||
checker.InvalidateDomain(Aliyun, "example.com", credentials)
|
||||
}()
|
||||
}
|
||||
for index := 0; index < 20; index++ {
|
||||
<-done
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user