From 0ede75091745c71ccf63d3062f5562a046f0d2bb 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 gname --- .../challengers/dns01/gname/gname.go | 7 +- .../challengers/dns01/gname/internal/lego.go | 154 ------------------ pkg/sdk3rd/gname/api_add_domain_resolution.go | 42 ----- .../gname/api_delete_domain_resolution.go | 35 ---- .../gname/api_list_domain_resolution.go | 41 ----- .../gname/api_modify_domain_resolution.go | 40 ----- pkg/sdk3rd/gname/client.go | 150 ----------------- pkg/sdk3rd/gname/types.go | 32 ---- 8 files changed, 4 insertions(+), 497 deletions(-) delete mode 100644 pkg/core/certifier/challengers/dns01/gname/internal/lego.go delete mode 100644 pkg/sdk3rd/gname/api_add_domain_resolution.go delete mode 100644 pkg/sdk3rd/gname/api_delete_domain_resolution.go delete mode 100644 pkg/sdk3rd/gname/api_list_domain_resolution.go delete mode 100644 pkg/sdk3rd/gname/api_modify_domain_resolution.go delete mode 100644 pkg/sdk3rd/gname/client.go delete mode 100644 pkg/sdk3rd/gname/types.go diff --git a/pkg/core/certifier/challengers/dns01/gname/gname.go b/pkg/core/certifier/challengers/dns01/gname/gname.go index cd8a3fc14..b766ab14f 100644 --- a/pkg/core/certifier/challengers/dns01/gname/gname.go +++ b/pkg/core/certifier/challengers/dns01/gname/gname.go @@ -4,8 +4,9 @@ import ( "fmt" "time" + "github.com/go-acme/lego/v5/providers/dns/gname" + "github.com/certimate-go/certimate/pkg/core/certifier" - "github.com/certimate-go/certimate/pkg/core/certifier/challengers/dns01/gname/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 := gname.NewDefaultConfig() providerConfig.AppID = config.AppId providerConfig.AppKey = config.AppKey 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 := gname.NewDNSProviderConfig(providerConfig) if err != nil { return nil, err } diff --git a/pkg/core/certifier/challengers/dns01/gname/internal/lego.go b/pkg/core/certifier/challengers/dns01/gname/internal/lego.go deleted file mode 100644 index b82793c0b..000000000 --- a/pkg/core/certifier/challengers/dns01/gname/internal/lego.go +++ /dev/null @@ -1,154 +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" - - gnamesdk "github.com/certimate-go/certimate/pkg/sdk3rd/gname" -) - -const ( - envNamespace = "GNAME_" - - EnvAppID = envNamespace + "APP_ID" - EnvAppKey = envNamespace + "APP_KEY" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -type Config struct { - AppID string - AppKey string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPTimeout time.Duration -} - -type DNSProvider struct { - config *Config - client *gnamesdk.Client - - recordIDs map[string]int64 // Key: ChallengeToken; Value: RecordID - recordIDsMu sync.Mutex -} - -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, 300), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - } -} - -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAppID, EnvAppKey) - if err != nil { - return nil, fmt.Errorf("gname: %w", err) - } - - config := NewDefaultConfig() - config.AppID = values[EnvAppID] - config.AppKey = values[EnvAppKey] - - return NewDNSProviderConfig(config) -} - -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, fmt.Errorf("gname: the configuration of the DNS provider is nil") - } - - client, err := gnamesdk.NewClient(config.AppID, config.AppKey) - if err != nil { - return nil, fmt.Errorf("gname: %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("gname: could not find zone for domain %q: %w", domain, err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("gname: %w", err) - } - - // REF: https://www.gname.vip/domain/api/dns/add - request := &gnamesdk.AddDomainResolutionRequest{ - ZoneName: lo.ToPtr(dns01.UnFqdn(authZone)), - RecordType: lo.ToPtr("TXT"), - RecordName: lo.ToPtr(subDomain), - RecordValue: lo.ToPtr(info.Value), - TTL: lo.ToPtr(int32(d.config.TTL)), - } - response, err := d.client.AddDomainResolutionWithContext(ctx, request) - if err != nil { - return fmt.Errorf("gname: error when create record: %w", err) - } - - d.recordIDsMu.Lock() - d.recordIDs[token], _ = response.Data.Int64() - 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("gname: 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("gname: unknown record ID for '%s'", info.EffectiveFQDN) - } - - // REF: https://www.gname.vip/domain/api/dns/del - request := &gnamesdk.DeleteDomainResolutionRequest{ - ZoneName: lo.ToPtr(dns01.UnFqdn(authZone)), - RecordID: lo.ToPtr(recordID), - } - _, err = d.client.DeleteDomainResolutionWithContext(ctx, request) - if err != nil { - return fmt.Errorf("gname: 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/sdk3rd/gname/api_add_domain_resolution.go b/pkg/sdk3rd/gname/api_add_domain_resolution.go deleted file mode 100644 index 8146b68f1..000000000 --- a/pkg/sdk3rd/gname/api_add_domain_resolution.go +++ /dev/null @@ -1,42 +0,0 @@ -package gname - -import ( - "context" - "encoding/json" - "net/http" -) - -type AddDomainResolutionRequest struct { - ZoneName *string `json:"ym,omitempty"` - RecordType *string `json:"lx,omitempty"` - RecordName *string `json:"zj,omitempty"` - RecordValue *string `json:"jlz,omitempty"` - MX *int32 `json:"mx,omitempty"` - TTL *int32 `json:"ttl,omitempty"` -} - -type AddDomainResolutionResponse struct { - sdkResponseBase - - Data json.Number `json:"data"` -} - -func (c *Client) AddDomainResolution(req *AddDomainResolutionRequest) (*AddDomainResolutionResponse, error) { - return c.AddDomainResolutionWithContext(context.Background(), req) -} - -func (c *Client) AddDomainResolutionWithContext(ctx context.Context, req *AddDomainResolutionRequest) (*AddDomainResolutionResponse, error) { - httpreq, err := c.newRequest(http.MethodPost, "/api/resolution/add", req) - if err != nil { - return nil, err - } else { - httpreq.SetContext(ctx) - } - - result := &AddDomainResolutionResponse{} - if _, err := c.doRequestWithResult(httpreq, result); err != nil { - return result, err - } - - return result, nil -} diff --git a/pkg/sdk3rd/gname/api_delete_domain_resolution.go b/pkg/sdk3rd/gname/api_delete_domain_resolution.go deleted file mode 100644 index 3d8f623b3..000000000 --- a/pkg/sdk3rd/gname/api_delete_domain_resolution.go +++ /dev/null @@ -1,35 +0,0 @@ -package gname - -import ( - "context" - "net/http" -) - -type DeleteDomainResolutionRequest struct { - ZoneName *string `json:"ym,omitempty"` - RecordID *int64 `json:"jxid,omitempty"` -} - -type DeleteDomainResolutionResponse struct { - sdkResponseBase -} - -func (c *Client) DeleteDomainResolution(req *DeleteDomainResolutionRequest) (*DeleteDomainResolutionResponse, error) { - return c.DeleteDomainResolutionWithContext(context.Background(), req) -} - -func (c *Client) DeleteDomainResolutionWithContext(ctx context.Context, req *DeleteDomainResolutionRequest) (*DeleteDomainResolutionResponse, error) { - httpreq, err := c.newRequest(http.MethodPost, "/api/resolution/delete", req) - if err != nil { - return nil, err - } else { - httpreq.SetContext(ctx) - } - - result := &DeleteDomainResolutionResponse{} - if _, err := c.doRequestWithResult(httpreq, result); err != nil { - return result, err - } - - return result, nil -} diff --git a/pkg/sdk3rd/gname/api_list_domain_resolution.go b/pkg/sdk3rd/gname/api_list_domain_resolution.go deleted file mode 100644 index d075b430c..000000000 --- a/pkg/sdk3rd/gname/api_list_domain_resolution.go +++ /dev/null @@ -1,41 +0,0 @@ -package gname - -import ( - "context" - "net/http" -) - -type ListDomainResolutionRequest struct { - ZoneName *string `json:"ym,omitempty"` - Page *int32 `json:"page,omitempty"` - PageSize *int32 `json:"limit,omitempty"` -} - -type ListDomainResolutionResponse struct { - sdkResponseBase - - Count int32 `json:"count"` - Data []*DomainResolutionRecordord `json:"data"` - Page int32 `json:"page"` - PageSize int32 `json:"pagesize"` -} - -func (c *Client) ListDomainResolution(req *ListDomainResolutionRequest) (*ListDomainResolutionResponse, error) { - return c.ListDomainResolutionWithContext(context.Background(), req) -} - -func (c *Client) ListDomainResolutionWithContext(ctx context.Context, req *ListDomainResolutionRequest) (*ListDomainResolutionResponse, error) { - httpreq, err := c.newRequest(http.MethodPost, "/api/resolution/list", req) - if err != nil { - return nil, err - } else { - httpreq.SetContext(ctx) - } - - result := &ListDomainResolutionResponse{} - if _, err := c.doRequestWithResult(httpreq, result); err != nil { - return result, err - } - - return result, nil -} diff --git a/pkg/sdk3rd/gname/api_modify_domain_resolution.go b/pkg/sdk3rd/gname/api_modify_domain_resolution.go deleted file mode 100644 index 1ac7415b5..000000000 --- a/pkg/sdk3rd/gname/api_modify_domain_resolution.go +++ /dev/null @@ -1,40 +0,0 @@ -package gname - -import ( - "context" - "net/http" -) - -type ModifyDomainResolutionRequest struct { - ID *int64 `json:"jxid,omitempty"` - ZoneName *string `json:"ym,omitempty"` - RecordType *string `json:"lx,omitempty"` - RecordName *string `json:"zj,omitempty"` - RecordValue *string `json:"jlz,omitempty"` - MX *int32 `json:"mx,omitempty"` - TTL *int32 `json:"ttl,omitempty"` -} - -type ModifyDomainResolutionResponse struct { - sdkResponseBase -} - -func (c *Client) ModifyDomainResolution(req *ModifyDomainResolutionRequest) (*ModifyDomainResolutionResponse, error) { - return c.ModifyDomainResolutionWithContext(context.Background(), req) -} - -func (c *Client) ModifyDomainResolutionWithContext(ctx context.Context, req *ModifyDomainResolutionRequest) (*ModifyDomainResolutionResponse, error) { - httpreq, err := c.newRequest(http.MethodPost, "/api/resolution/edit", req) - if err != nil { - return nil, err - } else { - httpreq.SetContext(ctx) - } - - result := &ModifyDomainResolutionResponse{} - if _, err := c.doRequestWithResult(httpreq, result); err != nil { - return result, err - } - - return result, nil -} diff --git a/pkg/sdk3rd/gname/client.go b/pkg/sdk3rd/gname/client.go deleted file mode 100644 index 22009f680..000000000 --- a/pkg/sdk3rd/gname/client.go +++ /dev/null @@ -1,150 +0,0 @@ -package gname - -import ( - "crypto/md5" - "encoding/json" - "fmt" - "net/url" - "sort" - "strings" - "time" - - "github.com/go-resty/resty/v2" - - "github.com/certimate-go/certimate/internal/app" -) - -type Client struct { - appId string - appKey string - - client *resty.Client -} - -func NewClient(appId, appKey string) (*Client, error) { - if appId == "" { - return nil, fmt.Errorf("sdkerr: unset appId") - } - if appKey == "" { - return nil, fmt.Errorf("sdkerr: unset appKey") - } - - client := resty.New(). - SetBaseURL("https://api.gname.com"). - SetHeader("Accept", "application/json"). - SetHeader("Content-Type", "application/x-www-form-urlencoded"). - SetHeader("User-Agent", app.AppUserAgent) - - return &Client{ - appId: appId, - appKey: appKey, - 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["appid"] = c.appId - data["gntime"] = fmt.Sprintf("%d", time.Now().Unix()) - data["gntoken"] = generateSignature(data, c.appKey) - - req := c.client.R() - req.Method = method - req.URL = path - req.SetFormData(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 != 1 { - return resp, fmt.Errorf("sdkerr: api error: code='%d', message='%s'", tcode, res.GetMessage()) - } - } - } - - return resp, nil -} - -func generateSignature(params map[string]string, appKey string) string { - // Step 1: Sort parameters by ASCII order - var keys []string - for k := range params { - keys = append(keys, k) - } - sort.Strings(keys) - - // Step 2: Create string A with URL-encoded values - var pairs []string - for _, k := range keys { - encodedValue := url.QueryEscape(params[k]) - pairs = append(pairs, fmt.Sprintf("%s=%s", k, encodedValue)) - } - stringA := strings.Join(pairs, "&") - - // Step 3: Append appkey to create string B - stringB := stringA + appKey - - // Step 4: Calculate MD5 and convert to uppercase - hash := md5.Sum([]byte(stringB)) - return strings.ToUpper(fmt.Sprintf("%x", hash)) -} diff --git a/pkg/sdk3rd/gname/types.go b/pkg/sdk3rd/gname/types.go deleted file mode 100644 index 8cc5d6761..000000000 --- a/pkg/sdk3rd/gname/types.go +++ /dev/null @@ -1,32 +0,0 @@ -package gname - -import "encoding/json" - -type sdkResponse interface { - GetCode() int - GetMessage() string -} - -type sdkResponseBase struct { - Code int `json:"code"` - Message string `json:"msg"` -} - -func (r *sdkResponseBase) GetCode() int { - return r.Code -} - -func (r *sdkResponseBase) GetMessage() string { - return r.Message -} - -var _ sdkResponse = (*sdkResponseBase)(nil) - -type DomainResolutionRecordord struct { - ID json.Number `json:"id"` - ZoneName string `json:"ym"` - RecordType string `json:"lx"` - RecordName string `json:"zjt"` - RecordValue string `json:"jxz"` - MX int32 `json:"mx"` -}