From 4d1fb385f6a0925b9f545d9aa1ef1e0c736da30b Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 27 May 2026 23:47:14 +0800 Subject: [PATCH] refactor(provider): use lego to implement dns-01 challenger of xinnet --- .../challengers/dns01/xinnet/internal/lego.go | 149 ----------------- .../challengers/dns01/xinnet/xinnet.go | 9 +- pkg/sdk3rd/xinnet/api_dns_create.go | 42 ----- pkg/sdk3rd/xinnet/api_dns_delete.go | 35 ---- pkg/sdk3rd/xinnet/client.go | 150 ------------------ pkg/sdk3rd/xinnet/types.go | 30 ---- 6 files changed, 5 insertions(+), 410 deletions(-) delete mode 100644 pkg/core/certifier/challengers/dns01/xinnet/internal/lego.go delete mode 100644 pkg/sdk3rd/xinnet/api_dns_create.go delete mode 100644 pkg/sdk3rd/xinnet/api_dns_delete.go delete mode 100644 pkg/sdk3rd/xinnet/client.go delete mode 100644 pkg/sdk3rd/xinnet/types.go diff --git a/pkg/core/certifier/challengers/dns01/xinnet/internal/lego.go b/pkg/core/certifier/challengers/dns01/xinnet/internal/lego.go deleted file mode 100644 index bc90e47d0..000000000 --- a/pkg/core/certifier/challengers/dns01/xinnet/internal/lego.go +++ /dev/null @@ -1,149 +0,0 @@ -package internal - -import ( - "context" - "fmt" - "sync" - "time" - - "github.com/go-acme/lego/v5/challenge" - "github.com/go-acme/lego/v5/challenge/dns01" - "github.com/go-acme/lego/v5/platform/env" - "github.com/samber/lo" - - xinnetsdk "github.com/certimate-go/certimate/pkg/sdk3rd/xinnet" -) - -const ( - envNamespace = "XINNET_" - - EnvAgentId = envNamespace + "AGENT_ID" - EnvAppSecret = envNamespace + "APP_SECRET" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -type Config struct { - AgentID string - AppSecret string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPTimeout time.Duration -} - -type DNSProvider struct { - config *Config - client *xinnetsdk.Client - - recordIDs map[string]*int64 // Key: ChallengeToken; Value: RecordID - recordIDsMu sync.Mutex -} - -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, 600), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 10*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - } -} - -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAgentId, EnvAppSecret) - if err != nil { - return nil, fmt.Errorf("xinnet: %w", err) - } - - config := NewDefaultConfig() - config.AgentID = values[EnvAgentId] - config.AppSecret = values[EnvAppSecret] - - return NewDNSProviderConfig(config) -} - -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, fmt.Errorf("xinnet: the configuration of the DNS provider is nil") - } - - client, err := xinnetsdk.NewClient(config.AgentID, config.AppSecret) - if err != nil { - return nil, fmt.Errorf("xinnet: %w", err) - } else { - client.SetTimeout(config.HTTPTimeout) - } - - return &DNSProvider{ - config: config, - client: client, - recordIDs: make(map[string]*int64), - recordIDsMu: sync.Mutex{}, - }, nil -} - -func (d *DNSProvider) Present(ctx context.Context, domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(ctx, domain, keyAuth) - - authZone, err := dns01.DefaultClient().FindZoneByFqdn(ctx, info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("xinnet: could not find zone for domain %q: %w", domain, err) - } - - // REF: https://apidoc.xin.cn/doc-7283900 - request := &xinnetsdk.DnsCreateRequest{ - DomainName: lo.ToPtr(dns01.UnFqdn(authZone)), - RecordName: lo.ToPtr(dns01.UnFqdn(info.EffectiveFQDN)), - Type: lo.ToPtr("TXT"), - Value: lo.ToPtr(info.Value), - Line: lo.ToPtr("默认"), - Ttl: lo.ToPtr(int32(d.config.TTL)), - } - response, err := d.client.DnsCreateWithContext(ctx, request) - if err != nil { - return fmt.Errorf("xinnet: error when create record: %w", err) - } - - d.recordIDsMu.Lock() - d.recordIDs[token] = response.Data - d.recordIDsMu.Unlock() - - return nil -} - -func (d *DNSProvider) CleanUp(ctx context.Context, domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(ctx, domain, keyAuth) - - authZone, err := dns01.DefaultClient().FindZoneByFqdn(ctx, info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("xinnet: could not find zone for domain %q: %w", domain, err) - } - - d.recordIDsMu.Lock() - recordID, ok := d.recordIDs[token] - d.recordIDsMu.Unlock() - if !ok { - return fmt.Errorf("xinnet: unknown record ID for '%s'", info.EffectiveFQDN) - } - - // REF: https://apidoc.xin.cn/doc-7283901 - request := &xinnetsdk.DnsDeleteRequest{ - DomainName: lo.ToPtr(dns01.UnFqdn(authZone)), - RecordId: recordID, - } - if _, err := d.client.DnsDeleteWithContext(ctx, request); err != nil { - return fmt.Errorf("xinnet: error when delete record: %w", err) - } - - return nil -} - -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} diff --git a/pkg/core/certifier/challengers/dns01/xinnet/xinnet.go b/pkg/core/certifier/challengers/dns01/xinnet/xinnet.go index a6ca60cab..7436f9c7e 100644 --- a/pkg/core/certifier/challengers/dns01/xinnet/xinnet.go +++ b/pkg/core/certifier/challengers/dns01/xinnet/xinnet.go @@ -4,8 +4,9 @@ import ( "fmt" "time" + "github.com/go-acme/lego/v5/providers/dns/xinnet" + "github.com/certimate-go/certimate/pkg/core/certifier" - "github.com/certimate-go/certimate/pkg/core/certifier/challengers/dns01/xinnet/internal" ) type ChallengerConfig struct { @@ -20,9 +21,9 @@ func NewChallenger(config *ChallengerConfig) (certifier.ACMEChallenger, error) { return nil, fmt.Errorf("the configuration of the acme challenge provider is nil") } - providerConfig := internal.NewDefaultConfig() + providerConfig := xinnet.NewDefaultConfig() providerConfig.AgentID = config.AgentId - providerConfig.AppSecret = config.ApiPassword + providerConfig.Secret = config.ApiPassword if config.DnsPropagationTimeout != 0 { providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second } @@ -30,7 +31,7 @@ func NewChallenger(config *ChallengerConfig) (certifier.ACMEChallenger, error) { providerConfig.TTL = config.DnsTTL } - provider, err := internal.NewDNSProviderConfig(providerConfig) + provider, err := xinnet.NewDNSProviderConfig(providerConfig) if err != nil { return nil, err } diff --git a/pkg/sdk3rd/xinnet/api_dns_create.go b/pkg/sdk3rd/xinnet/api_dns_create.go deleted file mode 100644 index 69d9047b5..000000000 --- a/pkg/sdk3rd/xinnet/api_dns_create.go +++ /dev/null @@ -1,42 +0,0 @@ -package xinnet - -import ( - "context" -) - -type DnsCreateRequest struct { - DomainName *string `json:"domainName,omitempty"` - RecordName *string `json:"recordName,omitempty"` - Type *string `json:"type,omitempty"` - Value *string `json:"value,omitempty"` - Line *string `json:"line,omitempty"` - Ttl *int32 `json:"ttl,omitempty"` - Mx *int32 `json:"mx,omitempty"` - Status *int32 `json:"status,omitempty"` -} - -type DnsCreateResponse struct { - sdkResponseBase - Data *int64 `json:"data,omitempty"` -} - -func (c *Client) DnsCreate(req *DnsCreateRequest) (*DnsCreateResponse, error) { - return c.DnsCreateWithContext(context.Background(), req) -} - -func (c *Client) DnsCreateWithContext(ctx context.Context, req *DnsCreateRequest) (*DnsCreateResponse, error) { - httpreq, err := c.newRequest("/dns/create/") - if err != nil { - return nil, err - } else { - httpreq.SetBody(req) - httpreq.SetContext(ctx) - } - - result := &DnsCreateResponse{} - if _, err := c.doRequestWithResult(httpreq, result); err != nil { - return result, err - } - - return result, nil -} diff --git a/pkg/sdk3rd/xinnet/api_dns_delete.go b/pkg/sdk3rd/xinnet/api_dns_delete.go deleted file mode 100644 index 6cb51fed0..000000000 --- a/pkg/sdk3rd/xinnet/api_dns_delete.go +++ /dev/null @@ -1,35 +0,0 @@ -package xinnet - -import ( - "context" -) - -type DnsDeleteRequest struct { - DomainName *string `json:"domainName,omitempty"` - RecordId *int64 `json:"recordId,omitempty"` -} - -type DnsDeleteResponse struct { - sdkResponseBase -} - -func (c *Client) DnsDelete(req *DnsDeleteRequest) (*DnsDeleteResponse, error) { - return c.DnsDeleteWithContext(context.Background(), req) -} - -func (c *Client) DnsDeleteWithContext(ctx context.Context, req *DnsDeleteRequest) (*DnsDeleteResponse, error) { - httpreq, err := c.newRequest("/dns/delete/") - if err != nil { - return nil, err - } else { - httpreq.SetBody(req) - httpreq.SetContext(ctx) - } - - result := &DnsDeleteResponse{} - if _, err := c.doRequestWithResult(httpreq, result); err != nil { - return result, err - } - - return result, nil -} diff --git a/pkg/sdk3rd/xinnet/client.go b/pkg/sdk3rd/xinnet/client.go deleted file mode 100644 index 5f64d3029..000000000 --- a/pkg/sdk3rd/xinnet/client.go +++ /dev/null @@ -1,150 +0,0 @@ -package xinnet - -import ( - "crypto/hmac" - "crypto/sha256" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "net/http" - "strings" - "time" - - "github.com/go-resty/resty/v2" - - "github.com/certimate-go/certimate/internal/app" -) - -type Client struct { - client *resty.Client -} - -func NewClient(agentId, appSecret string) (*Client, error) { - if agentId == "" { - return nil, fmt.Errorf("sdkerr: unset agentId") - } - if appSecret == "" { - return nil, fmt.Errorf("sdkerr: unset appSecret") - } - - client := resty.New(). - SetBaseURL("https://apiv2.xinnet.com/api"). - SetHeader("Accept", "application/json"). - SetHeader("Content-Type", "application/json"). - SetHeader("User-Agent", app.AppUserAgent). - SetPreRequestHook(func(c *resty.Client, req *http.Request) error { - // 生成时间戳 - timestamp := time.Now().UTC().Format("20060102T150405Z") - - // 获取请求路径,注意结尾必须是 "/" - urlPath := "/" - if req.URL != nil { - urlPath = req.URL.Path - - if !strings.HasSuffix(urlPath, "/") { - urlPath += "/" - } - } - - // 获取请求方法 - requestMethod := req.Method - - // 获取请求体 - requestBody := "" - if req.Body != nil { - reader, err := req.GetBody() - if err != nil { - return err - } - - defer reader.Close() - - payloadb, err := io.ReadAll(reader) - if err != nil { - return err - } - - requestBody = string(payloadb) - } - - // 计算签名 - algorithm := "HMAC-SHA256" - stringToSign := algorithm + "\n" + - timestamp + "\n" + - requestMethod + "\n" + - urlPath + "\n" + - requestBody - h := hmac.New(sha256.New, []byte(appSecret)) - h.Write([]byte(stringToSign)) - signature := hex.EncodeToString(h.Sum(nil)) - - // 设置请求头 - req.Header.Set("timestamp", timestamp) - req.Header.Set("authorization", fmt.Sprintf("%s Access=%s, Signature=%s", algorithm, agentId, signature)) - - return nil - }) - - return &Client{client}, nil -} - -func (c *Client) SetTimeout(timeout time.Duration) *Client { - c.client.SetTimeout(timeout) - return c -} - -func (c *Client) newRequest(path string) (*resty.Request, error) { - if path == "" { - return nil, fmt.Errorf("sdkerr: unset path") - } - - req := c.client.R() - req.Method = http.MethodPost - req.URL = path - return req, nil -} - -func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) { - if req == nil { - return nil, fmt.Errorf("sdkerr: nil request") - } - - // WARN: - // PLEASE DO NOT USE `req.SetResult` or `req.SetError` HERE! USE `doRequestWithResult` INSTEAD. - - resp, err := req.Send() - if err != nil { - return resp, fmt.Errorf("sdkerr: failed to send request: %w", err) - } else if resp.IsError() { - return resp, fmt.Errorf("sdkerr: unexpected status code: %d (resp: %s)", resp.StatusCode(), resp.String()) - } - - return resp, nil -} - -func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*resty.Response, error) { - if req == nil { - return nil, fmt.Errorf("sdkerr: nil request") - } - - resp, err := c.doRequest(req) - if err != nil { - if resp != nil { - json.Unmarshal(resp.Body(), &res) - } - return resp, err - } - - if len(resp.Body()) != 0 { - if err := json.Unmarshal(resp.Body(), &res); err != nil { - return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String()) - } else { - if tcode := res.GetCode(); tcode != "0" { - return resp, fmt.Errorf("sdkerr: code='%s', msg='%s'", tcode, res.GetMessage()) - } - } - } - - return resp, nil -} diff --git a/pkg/sdk3rd/xinnet/types.go b/pkg/sdk3rd/xinnet/types.go deleted file mode 100644 index f3d6c3b15..000000000 --- a/pkg/sdk3rd/xinnet/types.go +++ /dev/null @@ -1,30 +0,0 @@ -package xinnet - -type sdkResponse interface { - GetCode() string - GetMessage() string -} - -type sdkResponseBase struct { - Code *string `json:"code,omitempty"` - Message *string `json:"message,omitempty"` - RequestId *string `json:"requestId,omitempty"` -} - -func (r *sdkResponseBase) GetCode() string { - if r.Code == nil { - return "" - } - - return *r.Code -} - -func (r *sdkResponseBase) GetMessage() string { - if r.Message == nil { - return "" - } - - return *r.Message -} - -var _ sdkResponse = (*sdkResponseBase)(nil)