From 0c3861b8c8215337b565eaf3c0d44ad8520d0c2c Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 27 May 2026 23:47:13 +0800 Subject: [PATCH] refactor(provider): use lego to implement dns-01 challenger of 51dnscom --- .../challengers/dns01/51dnscom/51dnscom.go | 7 +- .../dns01/51dnscom/internal/lego.go | 191 ------------------ pkg/sdk3rd/51dnscom/api_domain_list.go | 43 ---- pkg/sdk3rd/51dnscom/api_record_create.go | 43 ---- pkg/sdk3rd/51dnscom/api_record_remove.go | 35 ---- pkg/sdk3rd/51dnscom/client.go | 147 -------------- pkg/sdk3rd/51dnscom/types.go | 50 ----- 7 files changed, 4 insertions(+), 512 deletions(-) delete mode 100644 pkg/core/certifier/challengers/dns01/51dnscom/internal/lego.go delete mode 100644 pkg/sdk3rd/51dnscom/api_domain_list.go delete mode 100644 pkg/sdk3rd/51dnscom/api_record_create.go delete mode 100644 pkg/sdk3rd/51dnscom/api_record_remove.go delete mode 100644 pkg/sdk3rd/51dnscom/client.go delete mode 100644 pkg/sdk3rd/51dnscom/types.go diff --git a/pkg/core/certifier/challengers/dns01/51dnscom/51dnscom.go b/pkg/core/certifier/challengers/dns01/51dnscom/51dnscom.go index bc32f7e2c..3b83095f8 100644 --- a/pkg/core/certifier/challengers/dns01/51dnscom/51dnscom.go +++ b/pkg/core/certifier/challengers/dns01/51dnscom/51dnscom.go @@ -4,8 +4,9 @@ import ( "fmt" "time" + "github.com/go-acme/lego/v5/providers/dns/dns51" + "github.com/certimate-go/certimate/pkg/core/certifier" - "github.com/certimate-go/certimate/pkg/core/certifier/challengers/dns01/51dnscom/internal" ) type ChallengerConfig struct { @@ -20,7 +21,7 @@ func NewChallenger(config *ChallengerConfig) (certifier.ACMEChallenger, error) { return nil, fmt.Errorf("the configuration of the acme challenge provider is nil") } - providerConfig := internal.NewDefaultConfig() + providerConfig := dns51.NewDefaultConfig() providerConfig.APIKey = config.ApiKey providerConfig.APISecret = config.ApiSecret if config.DnsPropagationTimeout != 0 { @@ -30,7 +31,7 @@ func NewChallenger(config *ChallengerConfig) (certifier.ACMEChallenger, error) { providerConfig.TTL = config.DnsTTL } - provider, err := internal.NewDNSProviderConfig(providerConfig) + provider, err := dns51.NewDNSProviderConfig(providerConfig) if err != nil { return nil, err } diff --git a/pkg/core/certifier/challengers/dns01/51dnscom/internal/lego.go b/pkg/core/certifier/challengers/dns01/51dnscom/internal/lego.go deleted file mode 100644 index 8ff0dbe86..000000000 --- a/pkg/core/certifier/challengers/dns01/51dnscom/internal/lego.go +++ /dev/null @@ -1,191 +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" - - dnscomsdk "github.com/certimate-go/certimate/pkg/sdk3rd/51dnscom" -) - -const ( - envNamespace = "51DNSCOM_" - EnvAPIKey = envNamespace + "API_KEY" - EnvAPISecret = envNamespace + "API_SECRET" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -type Config struct { - APIKey string - APISecret string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPTimeout time.Duration -} - -type DNSProvider struct { - config *Config - client *dnscomsdk.Client - - recordCache map[string]dnsRecordCacheEntry // Key: ChallengeToken - recordCacheMu sync.Mutex -} - -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - } -} - -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIKey, EnvAPISecret) - if err != nil { - return nil, fmt.Errorf("51dnscom: %w", err) - } - - config := NewDefaultConfig() - config.APIKey = values[EnvAPIKey] - config.APISecret = values[EnvAPISecret] - - return NewDNSProviderConfig(config) -} - -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, fmt.Errorf("51dnscom: the configuration of the DNS provider is nil") - } - - client, err := dnscomsdk.NewClient(config.APIKey, config.APISecret) - if err != nil { - return nil, fmt.Errorf("51dnscom: %w", err) - } else { - client.SetTimeout(config.HTTPTimeout) - } - - return &DNSProvider{ - config: config, - client: client, - recordCache: make(map[string]dnsRecordCacheEntry), - recordCacheMu: 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("51dnscom: could not find zone for domain %q: %w", domain, err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("51dnscom: %w", err) - } - - zone, err := d.findZone(ctx, dns01.UnFqdn(authZone)) - if err != nil { - return fmt.Errorf("51dnscom: error when list zones: %w", err) - } - - // REF: https://www.51dns.com/document/api/4/12.html - request := &dnscomsdk.RecordCreateRequest{ - DomainID: lo.ToPtr(zone.DomainID.String()), - Type: lo.ToPtr("TXT"), - Host: lo.ToPtr(subDomain), - Value: lo.ToPtr(info.Value), - TTL: lo.ToPtr(int32(d.config.TTL)), - } - response, err := d.client.RecordCreateWithContext(ctx, request) - if err != nil { - return fmt.Errorf("51dnscom: error when create record: %w", err) - } - - d.recordCacheMu.Lock() - d.recordCache[token] = dnsRecordCacheEntry{DomainID: zone.DomainID.String(), RecordID: response.Data.RecordID.String()} - d.recordCacheMu.Unlock() - - return nil -} - -func (d *DNSProvider) CleanUp(ctx context.Context, domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(ctx, domain, keyAuth) - - d.recordCacheMu.Lock() - record, ok := d.recordCache[token] - d.recordCacheMu.Unlock() - if !ok { - return fmt.Errorf("51dnscom: unknown record ID for '%s'", info.EffectiveFQDN) - } - - // REF: https://www.51dns.com/document/api/4/27.html - request := &dnscomsdk.RecordRemoveRequest{ - DomainID: lo.ToPtr(record.DomainID), - RecordID: lo.ToPtr(record.RecordID), - } - if _, err := d.client.RecordRemoveWithContext(ctx, request); err != nil { - return fmt.Errorf("51dnscom: error when delete record: %w", err) - } - - return nil -} - -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -type dnsRecordCacheEntry struct { - DomainID string - RecordID string -} - -func (d *DNSProvider) findZone(ctx context.Context, zoneName string) (*dnscomsdk.DomainRecord, error) { - page := 1 - pageSize := 10 - for { - // REF: https://www.51dns.com/document/api/74/88.html - request := &dnscomsdk.DomainListRequest{ - Page: lo.ToPtr(int32(page)), - PageSize: lo.ToPtr(int32(pageSize)), - } - response, err := d.client.DomainListWithContext(ctx, request) - if err != nil { - return nil, err - } - - if response.Data == nil { - break - } - - for _, domainItem := range response.Data.Data { - if domainItem.Domain == zoneName { - return domainItem, nil - } - } - - if len(response.Data.Data) < pageSize || response.Data.PageCount <= int32(page) { - break - } - - page++ - } - - return nil, fmt.Errorf("could not find zone '%s'", zoneName) -} diff --git a/pkg/sdk3rd/51dnscom/api_domain_list.go b/pkg/sdk3rd/51dnscom/api_domain_list.go deleted file mode 100644 index 8c2f0ece2..000000000 --- a/pkg/sdk3rd/51dnscom/api_domain_list.go +++ /dev/null @@ -1,43 +0,0 @@ -package dnscom - -import ( - "context" - "net/http" -) - -type DomainListRequest struct { - GroupID *string `json:"groupID,omitempty"` - Page *int32 `json:"page,omitempty"` - PageSize *int32 `json:"pageSize,omitempty"` -} - -type DomainListResponse struct { - sdkResponseBase - - Data *struct { - Data []*DomainRecord `json:"data"` - Page int32 `json:"page"` - PageSize int32 `json:"pageSize"` - PageCount int32 `json:"pageCount"` - } `json:"data"` -} - -func (c *Client) DomainList(req *DomainListRequest) (*DomainListResponse, error) { - return c.DomainListWithContext(context.Background(), req) -} - -func (c *Client) DomainListWithContext(ctx context.Context, req *DomainListRequest) (*DomainListResponse, error) { - httpreq, err := c.newRequest(http.MethodPost, "/domain/list/", req) - if err != nil { - return nil, err - } else { - httpreq.SetContext(ctx) - } - - result := &DomainListResponse{} - if _, err := c.doRequestWithResult(httpreq, result); err != nil { - return result, err - } - - return result, nil -} diff --git a/pkg/sdk3rd/51dnscom/api_record_create.go b/pkg/sdk3rd/51dnscom/api_record_create.go deleted file mode 100644 index 87f2f4b46..000000000 --- a/pkg/sdk3rd/51dnscom/api_record_create.go +++ /dev/null @@ -1,43 +0,0 @@ -package dnscom - -import ( - "context" - "net/http" -) - -type RecordCreateRequest struct { - DomainID *string `json:"domainID,omitempty"` - ViewID *string `json:"viewID,omitempty"` - Type *string `json:"type,omitempty"` - Host *string `json:"host,omitempty"` - Value *string `json:"value,omitempty"` - TTL *int32 `json:"ttl,omitempty"` - MX *int32 `json:"mx,omitempty"` - Remark *string `json:"remark,omitempty"` -} - -type RecordCreateResponse struct { - sdkResponseBase - - Data *DNSRecord `json:"data"` -} - -func (c *Client) RecordCreate(req *RecordCreateRequest) (*RecordCreateResponse, error) { - return c.RecordCreateWithContext(context.Background(), req) -} - -func (c *Client) RecordCreateWithContext(ctx context.Context, req *RecordCreateRequest) (*RecordCreateResponse, error) { - httpreq, err := c.newRequest(http.MethodPost, "/record/create/", req) - if err != nil { - return nil, err - } else { - httpreq.SetContext(ctx) - } - - result := &RecordCreateResponse{} - if _, err := c.doRequestWithResult(httpreq, result); err != nil { - return result, err - } - - return result, nil -} diff --git a/pkg/sdk3rd/51dnscom/api_record_remove.go b/pkg/sdk3rd/51dnscom/api_record_remove.go deleted file mode 100644 index 6b5f85781..000000000 --- a/pkg/sdk3rd/51dnscom/api_record_remove.go +++ /dev/null @@ -1,35 +0,0 @@ -package dnscom - -import ( - "context" - "net/http" -) - -type RecordRemoveRequest struct { - DomainID *string `json:"domainID,omitempty"` - RecordID *string `json:"recordID,omitempty"` -} - -type RecordRemoveResponse struct { - sdkResponseBase -} - -func (c *Client) RecordRemove(req *RecordRemoveRequest) (*RecordRemoveResponse, error) { - return c.RecordRemoveWithContext(context.Background(), req) -} - -func (c *Client) RecordRemoveWithContext(ctx context.Context, req *RecordRemoveRequest) (*RecordRemoveResponse, error) { - httpreq, err := c.newRequest(http.MethodPost, "/record/remove/", req) - if err != nil { - return nil, err - } else { - httpreq.SetContext(ctx) - } - - result := &RecordRemoveResponse{} - if _, err := c.doRequestWithResult(httpreq, result); err != nil { - return result, err - } - - return result, nil -} diff --git a/pkg/sdk3rd/51dnscom/client.go b/pkg/sdk3rd/51dnscom/client.go deleted file mode 100644 index 187b34346..000000000 --- a/pkg/sdk3rd/51dnscom/client.go +++ /dev/null @@ -1,147 +0,0 @@ -package dnscom - -import ( - "crypto/md5" - "encoding/hex" - "encoding/json" - "fmt" - "sort" - "time" - - "github.com/go-resty/resty/v2" - - "github.com/certimate-go/certimate/internal/app" -) - -type Client struct { - apiKey string - apiSecret string - - client *resty.Client -} - -func NewClient(apiKey, apiSecret string) (*Client, error) { - if apiKey == "" { - return nil, fmt.Errorf("sdkerr: unset apiKey") - } - if apiSecret == "" { - return nil, fmt.Errorf("sdkerr: unset apiSecret") - } - - client := resty.New(). - SetBaseURL("https://www.51dns.com/api"). - SetHeader("Accept", "application/json"). - SetHeader("Content-Type", "application/json"). - SetHeader("User-Agent", app.AppUserAgent) - - return &Client{ - apiKey: apiKey, - apiSecret: apiSecret, - client: client, - }, nil -} - -func (c *Client) SetTimeout(timeout time.Duration) *Client { - c.client.SetTimeout(timeout) - return c -} - -func (c *Client) newRequest(method string, path string, params any) (*resty.Request, error) { - if method == "" { - return nil, fmt.Errorf("sdkerr: unset method") - } - if path == "" { - return nil, fmt.Errorf("sdkerr: unset path") - } - - data := make(map[string]string) - if params != nil { - temp := make(map[string]any) - jsonb, _ := json.Marshal(params) - json.Unmarshal(jsonb, &temp) - for k, v := range temp { - if v == nil { - continue - } - - data[k] = fmt.Sprintf("%v", v) - } - } - - data["apiKey"] = c.apiKey - data["timestamp"] = fmt.Sprintf("%d", time.Now().Unix()) - data["hash"] = generateHash(data, c.apiSecret) - - req := c.client.R() - req.Method = method - req.URL = path - req.SetBody(data) - 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.SetBody` or `req.SetFormData` HERE! USE `newRequest` INSTEAD. - // 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: api error: code='%d', message='%s'", tcode, res.GetMessage()) - } - } - } - - return resp, nil -} - -func generateHash(params map[string]string, secert string) string { - var keyList []string - for k := range params { - keyList = append(keyList, k) - } - sort.Strings(keyList) - - var hashString string - for _, key := range keyList { - if hashString == "" { - hashString += key + "=" + params[key] - } else { - hashString += "&" + key + "=" + params[key] - } - } - - m := md5.New() - m.Write([]byte(hashString + secert)) - cipherStr := m.Sum(nil) - return hex.EncodeToString(cipherStr) -} diff --git a/pkg/sdk3rd/51dnscom/types.go b/pkg/sdk3rd/51dnscom/types.go deleted file mode 100644 index 777efd8d9..000000000 --- a/pkg/sdk3rd/51dnscom/types.go +++ /dev/null @@ -1,50 +0,0 @@ -package dnscom - -import ( - "encoding/json" -) - -type sdkResponse interface { - GetCode() int - GetMessage() string -} - -type sdkResponseBase struct { - Code int `json:"code"` - Message string `json:"message"` -} - -func (r *sdkResponseBase) GetCode() int { - return r.Code -} - -func (r *sdkResponseBase) GetMessage() string { - return r.Message -} - -var _ sdkResponse = (*sdkResponseBase)(nil) - -type DomainRecord struct { - GroupID json.Number `json:"groupID"` - DomainID json.Number `json:"domainsID"` - Domain string `json:"domains"` - State int32 `json:"state"` - UserLockState int32 `json:"userLock"` - AdminLockState int32 `json:"adminLock"` - HealthState int32 `json:"healthState"` - ViewType string `json:"view_type"` -} - -type DNSRecord struct { - DomainID json.Number `json:"domainID"` - RecordID json.Number `json:"recordID"` - ViewID json.Number `json:"viewID"` - Record string `json:"record"` - Type string `json:"type"` - Host string `json:"host"` - Value string `json:"value"` - TTL int32 `json:"ttl"` - MX int32 `json:"mx"` - State int32 `json:"state"` - Remark string `json:"remark"` -}