Scert/backend/internal/certificate/dns_execution.go

118 lines
3.4 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 certificate
import (
"context"
"errors"
"sync"
"time"
"git.pcux.cn/xuans/Scert/backend/internal/domain"
"github.com/go-acme/lego/v5/challenge"
)
type DNSExecutionLeaseStore interface {
AcquireDNSExecutionLease(context.Context, domain.Domain, domain.DNSExecutionIdentity) (domain.DNSExecutionLease, error)
ValidateDNSExecutionLease(context.Context, domain.DNSExecutionLease) error
ReleaseDNSExecutionLease(context.Context, string) error
}
type dnsExecutionContextKey struct{}
func WithDNSExecutionIdentity(ctx context.Context, identity domain.DNSExecutionIdentity) context.Context {
return context.WithValue(ctx, dnsExecutionContextKey{}, identity)
}
func (s *Service) SetDNSExecutionLeaseStore(store DNSExecutionLeaseStore) {
if s != nil {
s.dnsLeases = store
}
}
// 授权探测也会写临时 TXT;错误可能表示远端写入或清理结果未知。
// 不以缺少 ACME 挑战记录作为释放依据,成功探测才确认本次引用可以收尾。
type leasedDNSAuthorization struct {
checker DNSAuthorizationChecker
store DNSExecutionLeaseStore
lease domain.DNSExecutionLease
mu sync.Mutex
pending int
}
func (a *leasedDNSAuthorization) VerifyDomain(ctx context.Context, providerID, name string, credentials map[string]string) error {
if err := ctx.Err(); err != nil {
return err
}
if err := a.store.ValidateDNSExecutionLease(ctx, a.lease); err != nil {
return errors.New("DNS 执行授权已失效,未执行授权探测")
}
a.mu.Lock()
a.pending++
a.mu.Unlock()
if err := a.checker.VerifyDomain(ctx, providerID, name, credentials); err != nil {
return err
}
a.mu.Lock()
a.pending--
a.mu.Unlock()
return nil
}
func (a *leasedDNSAuthorization) clean() bool {
a.mu.Lock()
defer a.mu.Unlock()
return a.pending == 0
}
// 每次写挑战前再次检查任务和权限;清理只能使用原 Provider 和固定凭据。
// 失败的 Present 也可能已写入远端,因此必须等待匹配的 CleanUp 成功才释放引用。
type leasedDNSProvider struct {
challenge.Provider
store DNSExecutionLeaseStore
lease domain.DNSExecutionLease
mu sync.Mutex
pending map[[3]string]bool
}
func (p *leasedDNSProvider) Present(ctx context.Context, name, token, keyAuth string) error {
if err := ctx.Err(); err != nil {
return err
}
if err := p.store.ValidateDNSExecutionLease(ctx, p.lease); err != nil {
return errors.New("DNS 执行授权已失效,未写入挑战记录")
}
key := [3]string{name, token, keyAuth}
p.mu.Lock()
p.pending[key] = true
p.mu.Unlock()
return p.Provider.Present(ctx, name, token, keyAuth)
}
func (p *leasedDNSProvider) CleanUp(ctx context.Context, name, token, keyAuth string) error {
// 任务取消不取消必要的挑战清理;清理自身仍有明确的时间上限。
cleanupCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 2*time.Minute)
defer cancel()
if err := p.Provider.CleanUp(cleanupCtx, name, token, keyAuth); err != nil {
return err
}
p.mu.Lock()
delete(p.pending, [3]string{name, token, keyAuth})
p.mu.Unlock()
return nil
}
func (p *leasedDNSProvider) clean() bool {
p.mu.Lock()
defer p.mu.Unlock()
return len(p.pending) == 0
}
// 不覆盖服务商自己的传播等待参数。
func (p *leasedDNSProvider) Timeout() (time.Duration, time.Duration) {
if timed, ok := p.Provider.(interface {
Timeout() (time.Duration, time.Duration)
}); ok {
return timed.Timeout()
}
return time.Minute, 2 * time.Second
}