From 469d4b35c195c436b157dbf5ac14b803df6640fe Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 24 Jan 2025 01:38:06 +0800 Subject: [PATCH] feat: implement gname api sdk --- .../acme-dns-01/lego-providers/gname/gname.go | 39 ----- internal/pkg/vendors/gname-sdk/api.go | 102 +++++++++++ internal/pkg/vendors/gname-sdk/client.go | 162 ++++++++++++++++++ 3 files changed, 264 insertions(+), 39 deletions(-) delete mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go create mode 100644 internal/pkg/vendors/gname-sdk/api.go create mode 100644 internal/pkg/vendors/gname-sdk/client.go diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go deleted file mode 100644 index a4045997..00000000 --- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go +++ /dev/null @@ -1,39 +0,0 @@ -package gname - -import ( - "errors" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/providers/dns/westcn" -) - -type GnameApplicantConfig struct { - Username string `json:"username"` - ApiPassword string `json:"apiPassword"` - DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` - DnsTTL int32 `json:"dnsTTL,omitempty"` -} - -func NewChallengeProvider(config *GnameApplicantConfig) (challenge.Provider, error) { - if config == nil { - return nil, errors.New("config is nil") - } - - providerConfig := westcn.NewDefaultConfig() - providerConfig.Username = config.Username - providerConfig.Password = config.ApiPassword - if config.DnsPropagationTimeout != 0 { - providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second - } - if config.DnsTTL != 0 { - providerConfig.TTL = int(config.DnsTTL) - } - - provider, err := westcn.NewDNSProviderConfig(providerConfig) - if err != nil { - return nil, err - } - - return provider, nil -} diff --git a/internal/pkg/vendors/gname-sdk/api.go b/internal/pkg/vendors/gname-sdk/api.go new file mode 100644 index 00000000..bb35f2e5 --- /dev/null +++ b/internal/pkg/vendors/gname-sdk/api.go @@ -0,0 +1,102 @@ +package gnamesdk + +type BaseResponse interface { + GetCode() int + GetMsg() string +} + +type AddDNSRecordRequest struct { + ZoneName string `json:"ym"` + RecordType string `json:"lx"` + RecordName string `json:"zj"` + RecordValue string `json:"jlz"` + MX int `json:"mx"` + TTL int `json:"ttl"` +} + +type AddDNSRecordResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data int `json:"data"` +} + +func (r *AddDNSRecordResponse) GetCode() int { + return r.Code +} + +func (r *AddDNSRecordResponse) GetMsg() string { + return r.Msg +} + +type EditDNSRecordRequest struct { + ID string `json:"jxid"` + ZoneName string `json:"ym"` + RecordType string `json:"lx"` + RecordName string `json:"zj"` + RecordValue string `json:"jlz"` + MX int `json:"mx"` + TTL int `json:"ttl"` +} + +type EditDNSRecordResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` +} + +func (r *EditDNSRecordResponse) GetCode() int { + return r.Code +} + +func (r *EditDNSRecordResponse) GetMsg() string { + return r.Msg +} + +type DeleteDNSRecordRequest struct { + ZoneName string `json:"ym"` + RecordId int `json:"jxid"` +} + +type DeleteDNSRecordResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` +} + +func (r *DeleteDNSRecordResponse) GetCode() int { + return r.Code +} + +func (r *DeleteDNSRecordResponse) GetMsg() string { + return r.Msg +} + +type ListDNSRecordRequest struct { + ZoneName string `json:"ym"` + Page *int `json:"page,omitempty"` + PageSize *int `json:"limit,omitempty"` +} + +type ListDNSRecordResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Count int `json:"count"` + Data []*DNSRecord `json:"data"` + Page int `json:"page"` + PageSize int `json:"pagesize"` +} + +type DNSRecord struct { + ID string `json:"id"` + ZoneName string `json:"ym"` + RecordType string `json:"lx"` + RecordName string `json:"zjt"` + RecordValue string `json:"jxz"` + MX int `json:"mx"` +} + +func (r *ListDNSRecordResponse) GetCode() int { + return r.Code +} + +func (r *ListDNSRecordResponse) GetMsg() string { + return r.Msg +} diff --git a/internal/pkg/vendors/gname-sdk/client.go b/internal/pkg/vendors/gname-sdk/client.go new file mode 100644 index 00000000..81812682 --- /dev/null +++ b/internal/pkg/vendors/gname-sdk/client.go @@ -0,0 +1,162 @@ +package gnamesdk + +import ( + "crypto/md5" + "encoding/json" + "fmt" + "net/url" + "sort" + "strings" + "time" + + "github.com/go-resty/resty/v2" + + "github.com/usual2970/certimate/internal/pkg/utils/maps" +) + +type GnameClient struct { + appId string + appKey string + client *resty.Client +} + +func NewGnameClient(appId, appKey string) *GnameClient { + client := resty.New() + + return &GnameClient{ + appId: appId, + appKey: appKey, + client: client, + } +} + +func (c *GnameClient) WithTimeout(timeout time.Duration) *GnameClient { + c.client.SetTimeout(timeout) + return c +} + +func (c *GnameClient) AddDNSRecord(req *AddDNSRecordRequest) (*AddDNSRecordResponse, error) { + params := make(map[string]any) + jsonData, _ := json.Marshal(req) + json.Unmarshal(jsonData, ¶ms) + + result := AddDNSRecordResponse{} + err := c.sendRequestWithResult("/api/resolution/add", params, &result) + if err != nil { + return nil, err + } + return &result, nil +} + +func (c *GnameClient) EditDNSRecord(req *EditDNSRecordRequest) (*EditDNSRecordResponse, error) { + params := make(map[string]any) + jsonData, _ := json.Marshal(req) + json.Unmarshal(jsonData, ¶ms) + + result := EditDNSRecordResponse{} + err := c.sendRequestWithResult("/api/resolution/edit", params, &result) + if err != nil { + return nil, err + } + return &result, nil +} + +func (c *GnameClient) DeleteDNSRecord(req *DeleteDNSRecordRequest) (*DeleteDNSRecordResponse, error) { + params := make(map[string]any) + jsonData, _ := json.Marshal(req) + json.Unmarshal(jsonData, ¶ms) + + result := DeleteDNSRecordResponse{} + err := c.sendRequestWithResult("/api/resolution/delete", params, &result) + if err != nil { + return nil, err + } + return &result, nil +} + +func (c *GnameClient) ListDNSRecord(req *ListDNSRecordRequest) (*ListDNSRecordResponse, error) { + params := make(map[string]any) + jsonData, _ := json.Marshal(req) + json.Unmarshal(jsonData, ¶ms) + + result := ListDNSRecordResponse{} + err := c.sendRequestWithResult("/api/resolution/list", params, &result) + if err != nil { + return nil, err + } + return &result, nil +} + +func (c *GnameClient) generateSignature(params map[string]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 + c.appKey + + // Step 4: Calculate MD5 and convert to uppercase + hash := md5.Sum([]byte(stringB)) + return strings.ToUpper(fmt.Sprintf("%x", hash)) +} + +func (c *GnameClient) sendRequest(path string, params map[string]any) (*resty.Response, error) { + if params == nil { + params = make(map[string]any) + } + + data := make(map[string]string) + for k, v := range params { + data[k] = fmt.Sprintf("%v", v) + } + data["appid"] = c.appId + data["gntime"] = fmt.Sprintf("%d", time.Now().Unix()) + data["gntoken"] = c.generateSignature(data) + + url := "https://api.gname.com" + path + req := c.client.R(). + SetHeader("Content-Type", "application/x-www-form-urlencoded"). + SetFormData(data) + resp, err := req.Post(url) + if err != nil { + return nil, fmt.Errorf("failed to send request: %w", err) + } + + if resp.IsError() { + return nil, fmt.Errorf("unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) + } + + return resp, nil +} + +func (c *GnameClient) sendRequestWithResult(path string, params map[string]any, result BaseResponse) error { + resp, err := c.sendRequest(path, params) + if err != nil { + return err + } + + jsonResp := make(map[string]any) + if err := json.Unmarshal(resp.Body(), &jsonResp); err != nil { + return fmt.Errorf("failed to parse response: %w", err) + } + if err := maps.Decode(jsonResp, &result); err != nil { + return fmt.Errorf("failed to parse response: %w", err) + } + + if result.GetCode() != 1 { + return fmt.Errorf("API error: %s", result.GetMsg()) + } + + return nil +}