feat: add gname applicant

This commit is contained in:
Fu Diwei
2025-01-24 03:42:34 +08:00
parent 469d4b35c1
commit 0e1a964e7c
19 changed files with 400 additions and 43 deletions

View File

@@ -0,0 +1,40 @@
package gname
import (
"errors"
"time"
"github.com/go-acme/lego/v4/challenge"
internal "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal"
)
type GnameApplicantConfig struct {
AppId string `json:"appId"`
AppKey string `json:"appKey"`
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 := internal.NewDefaultConfig()
providerConfig.AppID = config.AppId
providerConfig.AppKey = config.AppKey
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := internal.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,196 @@
package lego_gname
import (
"errors"
"fmt"
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
gnamesdk "github.com/usual2970/certimate/internal/pkg/vendors/gname-sdk"
)
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 {
client *gnamesdk.GnameClient
config *Config
}
func NewDefaultConfig() *Config {
return &Config{
TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*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, errors.New("gname: the configuration of the DNS provider is nil")
}
client := gnamesdk.NewGnameClient(config.AppID, config.AppKey).
WithTimeout(config.HTTPTimeout)
return &DNSProvider{
client: client,
config: config,
}, nil
}
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
zoneName, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("gname: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zoneName)
if err != nil {
return fmt.Errorf("gname: %w", err)
}
if err := d.addOrUpdateDNSRecord(domain, subDomain, info.Value); err != nil {
return fmt.Errorf("gname: %w", err)
}
return nil
}
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)
subDomain := dns01.UnFqdn(fqdn)
if err := d.removeDNSRecord(domain, subDomain, value); err != nil {
return fmt.Errorf("gname: %w", err)
}
return nil
}
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
func (d *DNSProvider) getDNSRecord(domain, subDomain string) (*gnamesdk.ResolutionRecord, error) {
page := 1
pageSize := 20
for {
request := &gnamesdk.ListDomainResolutionRequest{}
request.ZoneName = domain
request.Page = &page
request.PageSize = &pageSize
response, err := d.client.ListDomainResolution(request)
if err != nil {
return nil, err
}
for _, record := range response.Data {
if record.RecordType == "TXT" && record.RecordName == subDomain {
return record, nil
}
}
if len(response.Data) == 0 {
break
}
if response.Page*response.PageSize >= response.Count {
break
}
page++
}
return nil, nil
}
func (d *DNSProvider) addOrUpdateDNSRecord(domain, subDomain, value string) error {
record, err := d.getDNSRecord(domain, subDomain)
if err != nil {
return err
}
if record == nil {
request := &gnamesdk.AddDomainResolutionRequest{
ZoneName: domain,
RecordType: "TXT",
RecordName: subDomain,
RecordValue: value,
TTL: d.config.TTL,
}
_, err := d.client.AddDomainResolution(request)
return err
} else {
request := &gnamesdk.ModifyDomainResolutionRequest{
ID: record.ID,
ZoneName: domain,
RecordType: "TXT",
RecordName: subDomain,
RecordValue: value,
TTL: d.config.TTL,
}
_, err := d.client.ModifyDomainResolution(request)
return err
}
return nil
}
func (d *DNSProvider) removeDNSRecord(domain, subDomain, value string) error {
record, err := d.getDNSRecord(domain, subDomain)
if err != nil {
return err
}
if record == nil {
return nil
}
request := &gnamesdk.DeleteDomainResolutionRequest{
ZoneName: domain,
RecordID: record.ID,
}
_, err = d.client.DeleteDomainResolution(request)
return err
}

View File

@@ -5,7 +5,7 @@ type BaseResponse interface {
GetMsg() string
}
type AddDNSRecordRequest struct {
type AddDomainResolutionRequest struct {
ZoneName string `json:"ym"`
RecordType string `json:"lx"`
RecordName string `json:"zj"`
@@ -14,21 +14,21 @@ type AddDNSRecordRequest struct {
TTL int `json:"ttl"`
}
type AddDNSRecordResponse struct {
type AddDomainResolutionResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data int `json:"data"`
}
func (r *AddDNSRecordResponse) GetCode() int {
func (r *AddDomainResolutionResponse) GetCode() int {
return r.Code
}
func (r *AddDNSRecordResponse) GetMsg() string {
func (r *AddDomainResolutionResponse) GetMsg() string {
return r.Msg
}
type EditDNSRecordRequest struct {
type ModifyDomainResolutionRequest struct {
ID string `json:"jxid"`
ZoneName string `json:"ym"`
RecordType string `json:"lx"`
@@ -38,53 +38,53 @@ type EditDNSRecordRequest struct {
TTL int `json:"ttl"`
}
type EditDNSRecordResponse struct {
type ModifyDomainResolutionResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
func (r *EditDNSRecordResponse) GetCode() int {
func (r *ModifyDomainResolutionResponse) GetCode() int {
return r.Code
}
func (r *EditDNSRecordResponse) GetMsg() string {
func (r *ModifyDomainResolutionResponse) GetMsg() string {
return r.Msg
}
type DeleteDNSRecordRequest struct {
type DeleteDomainResolutionRequest struct {
ZoneName string `json:"ym"`
RecordId int `json:"jxid"`
RecordID string `json:"jxid"`
}
type DeleteDNSRecordResponse struct {
type DeleteDomainResolutionResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
func (r *DeleteDNSRecordResponse) GetCode() int {
func (r *DeleteDomainResolutionResponse) GetCode() int {
return r.Code
}
func (r *DeleteDNSRecordResponse) GetMsg() string {
func (r *DeleteDomainResolutionResponse) GetMsg() string {
return r.Msg
}
type ListDNSRecordRequest struct {
type ListDomainResolutionRequest 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 ListDomainResolutionResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Count int `json:"count"`
Data []*ResolutionRecord `json:"data"`
Page int `json:"page"`
PageSize int `json:"pagesize"`
}
type DNSRecord struct {
type ResolutionRecord struct {
ID string `json:"id"`
ZoneName string `json:"ym"`
RecordType string `json:"lx"`
@@ -93,10 +93,10 @@ type DNSRecord struct {
MX int `json:"mx"`
}
func (r *ListDNSRecordResponse) GetCode() int {
func (r *ListDomainResolutionResponse) GetCode() int {
return r.Code
}
func (r *ListDNSRecordResponse) GetMsg() string {
func (r *ListDomainResolutionResponse) GetMsg() string {
return r.Msg
}

View File

@@ -35,12 +35,12 @@ func (c *GnameClient) WithTimeout(timeout time.Duration) *GnameClient {
return c
}
func (c *GnameClient) AddDNSRecord(req *AddDNSRecordRequest) (*AddDNSRecordResponse, error) {
func (c *GnameClient) AddDomainResolution(req *AddDomainResolutionRequest) (*AddDomainResolutionResponse, error) {
params := make(map[string]any)
jsonData, _ := json.Marshal(req)
json.Unmarshal(jsonData, &params)
result := AddDNSRecordResponse{}
result := AddDomainResolutionResponse{}
err := c.sendRequestWithResult("/api/resolution/add", params, &result)
if err != nil {
return nil, err
@@ -48,12 +48,12 @@ func (c *GnameClient) AddDNSRecord(req *AddDNSRecordRequest) (*AddDNSRecordRespo
return &result, nil
}
func (c *GnameClient) EditDNSRecord(req *EditDNSRecordRequest) (*EditDNSRecordResponse, error) {
func (c *GnameClient) ModifyDomainResolution(req *ModifyDomainResolutionRequest) (*ModifyDomainResolutionResponse, error) {
params := make(map[string]any)
jsonData, _ := json.Marshal(req)
json.Unmarshal(jsonData, &params)
result := EditDNSRecordResponse{}
result := ModifyDomainResolutionResponse{}
err := c.sendRequestWithResult("/api/resolution/edit", params, &result)
if err != nil {
return nil, err
@@ -61,12 +61,12 @@ func (c *GnameClient) EditDNSRecord(req *EditDNSRecordRequest) (*EditDNSRecordRe
return &result, nil
}
func (c *GnameClient) DeleteDNSRecord(req *DeleteDNSRecordRequest) (*DeleteDNSRecordResponse, error) {
func (c *GnameClient) DeleteDomainResolution(req *DeleteDomainResolutionRequest) (*DeleteDomainResolutionResponse, error) {
params := make(map[string]any)
jsonData, _ := json.Marshal(req)
json.Unmarshal(jsonData, &params)
result := DeleteDNSRecordResponse{}
result := DeleteDomainResolutionResponse{}
err := c.sendRequestWithResult("/api/resolution/delete", params, &result)
if err != nil {
return nil, err
@@ -74,12 +74,12 @@ func (c *GnameClient) DeleteDNSRecord(req *DeleteDNSRecordRequest) (*DeleteDNSRe
return &result, nil
}
func (c *GnameClient) ListDNSRecord(req *ListDNSRecordRequest) (*ListDNSRecordResponse, error) {
func (c *GnameClient) ListDomainResolution(req *ListDomainResolutionRequest) (*ListDomainResolutionResponse, error) {
params := make(map[string]any)
jsonData, _ := json.Marshal(req)
json.Unmarshal(jsonData, &params)
result := ListDNSRecordResponse{}
result := ListDomainResolutionResponse{}
err := c.sendRequestWithResult("/api/resolution/list", params, &result)
if err != nil {
return nil, err
@@ -124,7 +124,7 @@ func (c *GnameClient) sendRequest(path string, params map[string]any) (*resty.Re
data["gntime"] = fmt.Sprintf("%d", time.Now().Unix())
data["gntoken"] = c.generateSignature(data)
url := "https://api.gname.com" + path
url := "http://api.gname.com" + path
req := c.client.R().
SetHeader("Content-Type", "application/x-www-form-urlencoded").
SetFormData(data)