refactor: improve lego providers

This commit is contained in:
Fu Diwei
2025-11-12 13:19:55 +08:00
parent 9625b8441a
commit 6dad585b07
14 changed files with 369 additions and 928 deletions
@@ -177,47 +177,6 @@ func (client *EsaClient) DeleteRecord(request *aliesa.DeleteRecordRequest) (_res
return _result, _err
}
func (client *EsaClient) ListRecordsWithOptions(request *aliesa.ListRecordsRequest, runtime *dara.RuntimeOptions) (_result *aliesa.ListRecordsResponse, _err error) {
_err = request.Validate()
if _err != nil {
return _result, _err
}
query := openapiutil.Query(dara.ToMap(request))
req := &openapiutil.OpenApiRequest{
Query: openapiutil.Query(query),
}
params := &openapiutil.Params{
Action: dara.String("ListRecords"),
Version: dara.String("2024-09-10"),
Protocol: dara.String("HTTPS"),
Pathname: dara.String("/"),
Method: dara.String("GET"),
AuthType: dara.String("AK"),
Style: dara.String("RPC"),
ReqBodyType: dara.String("formData"),
BodyType: dara.String("json"),
}
_result = &aliesa.ListRecordsResponse{}
_body, _err := client.CallApi(params, req, runtime)
if _err != nil {
return _result, _err
}
_err = dara.Convert(_body, &_result)
return _result, _err
}
func (client *EsaClient) ListRecords(request *aliesa.ListRecordsRequest) (_result *aliesa.ListRecordsResponse, _err error) {
runtime := &dara.RuntimeOptions{}
_result = &aliesa.ListRecordsResponse{}
_body, _err := client.ListRecordsWithOptions(request, runtime)
if _err != nil {
return _result, _err
}
_result = _body
return _result, _err
}
func (client *EsaClient) ListSitesWithOptions(tmpReq *aliesa.ListSitesRequest, runtime *dara.RuntimeOptions) (_result *aliesa.ListSitesResponse, _err error) {
_err = tmpReq.Validate()
if _err != nil {
@@ -44,8 +44,8 @@ type DNSProvider struct {
client *EsaClient
config *Config
siteIDs map[string]int64
siteIDsMtx sync.Mutex
recordIDs map[string]int64
recordIDsMu sync.Mutex
}
func NewDefaultConfig() *Config {
@@ -76,6 +76,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
return nil, errors.New("alicloud-esa: the configuration of the DNS provider is nil")
}
if config.RegionID == "" {
config.RegionID = "cn-hangzhou"
}
client, err := NewEsaClient(&aliopen.Config{
AccessKeyId: tea.String(config.SecretID),
AccessKeySecret: tea.String(config.SecretKey),
@@ -86,10 +90,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
}
return &DNSProvider{
client: client,
config: config,
siteIDs: make(map[string]int64),
client: client,
config: config,
recordIDs: make(map[string]int64),
recordIDsMu: sync.Mutex{},
}, nil
}
@@ -102,14 +106,29 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
}
siteName := dns01.UnFqdn(authZone)
siteId, err := d.getSiteId(siteName)
siteID, err := d.getSiteId(siteName)
if err != nil {
return fmt.Errorf("alicloud-esa: could not find site for zone %q: %w", siteName, err)
}
if err := d.addOrUpdateDNSRecord(siteId, dns01.UnFqdn(info.EffectiveFQDN), info.Value); err != nil {
return fmt.Errorf("alicloud-esa: %w", err)
// REF: https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-createrecord
aliCreateRecordReq := &aliesa.CreateRecordRequest{
SiteId: tea.Int64(siteID),
Type: tea.String("TXT"),
RecordName: tea.String(dns01.UnFqdn(info.EffectiveFQDN)),
Data: &aliesa.CreateRecordRequestData{
Value: tea.String(info.Value),
},
Ttl: tea.Int32(int32(d.config.TTL)),
}
aliCreateRecordResp, err := d.client.CreateRecord(aliCreateRecordReq)
if err != nil {
return fmt.Errorf("alicloud-esa: error when create record: %w", err)
}
d.recordIDsMu.Lock()
d.recordIDs[token] = *aliCreateRecordResp.Body.GetRecordId()
d.recordIDsMu.Unlock()
return nil
}
@@ -117,19 +136,19 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("alicloud-esa: 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("alicloud-esa: unknown record ID for '%s'", info.EffectiveFQDN)
}
siteName := dns01.UnFqdn(authZone)
siteId, err := d.getSiteId(siteName)
if err != nil {
return fmt.Errorf("alicloud-esa: could not find site for zone %q: %w", siteName, err)
// REF: https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-deleterecord
aliDeleteRecordReq := &aliesa.DeleteRecordRequest{
RecordId: &recordID,
}
if err := d.removeDNSRecord(siteId, dns01.UnFqdn(info.EffectiveFQDN), info.Value); err != nil {
return fmt.Errorf("alicloud-esa: %w", err)
if _, err := d.client.DeleteRecord(aliDeleteRecordReq); err != nil {
return fmt.Errorf("alicloud-esa: error when delete record %w", err)
}
return nil
@@ -140,41 +159,32 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
}
func (d *DNSProvider) getSiteId(siteName string) (int64, error) {
d.siteIDsMtx.Lock()
siteID, ok := d.siteIDs[siteName]
d.siteIDsMtx.Unlock()
if ok {
return siteID, nil
}
pageNumber := 1
pageSize := 500
for {
request := &aliesa.ListSitesRequest{
// REF: https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-listsites
aliListSitesReq := &aliesa.ListSitesRequest{
SiteName: tea.String(siteName),
SiteSearchType: tea.String("exact"),
PageNumber: tea.Int32(int32(pageNumber)),
PageSize: tea.Int32(int32(pageSize)),
AccessType: tea.String("NS"),
}
response, err := d.client.ListSites(request)
aliListSitesResp, err := d.client.ListSites(aliListSitesReq)
if err != nil {
return 0, err
}
if response.Body == nil {
if aliListSitesResp.Body == nil {
break
} else {
for _, record := range response.Body.Sites {
if tea.StringValue(record.SiteName) == siteName {
d.siteIDsMtx.Lock()
d.siteIDs[siteName] = *record.SiteId
d.siteIDsMtx.Unlock()
return *record.SiteId, nil
for _, site := range aliListSitesResp.Body.Sites {
if *site.GetSiteName() == siteName {
return *site.GetSiteId(), nil
}
}
if len(response.Body.Sites) < pageSize {
if len(aliListSitesResp.Body.Sites) < pageSize {
break
}
@@ -184,80 +194,3 @@ func (d *DNSProvider) getSiteId(siteName string) (int64, error) {
return 0, errors.New("site not found")
}
func (d *DNSProvider) findDNSRecord(siteId int64, effectiveFQDN string, value string) (*aliesa.ListRecordsResponseBodyRecords, error) {
pageNumber := 1
pageSize := 500
for {
request := &aliesa.ListRecordsRequest{
SiteId: tea.Int64(siteId),
Type: tea.String("TXT"),
RecordName: tea.String(effectiveFQDN),
RecordMatchType: tea.String("exact"),
PageNumber: tea.Int32(int32(pageNumber)),
PageSize: tea.Int32(int32(pageSize)),
}
response, err := d.client.ListRecords(request)
if err != nil {
return nil, err
}
if response.Body == nil {
break
} else {
for _, record := range response.Body.Records {
if tea.StringValue(record.RecordName) == effectiveFQDN && tea.StringValue(record.Data.Value) == value {
return record, nil
}
}
if len(response.Body.Records) < pageSize {
break
}
pageNumber++
}
}
return nil, nil
}
func (d *DNSProvider) addOrUpdateDNSRecord(siteId int64, effectiveFQDN, value string) error {
record, err := d.findDNSRecord(siteId, effectiveFQDN, value)
if err != nil {
return err
}
if record != nil {
return nil
}
request := &aliesa.CreateRecordRequest{
SiteId: tea.Int64(siteId),
Type: tea.String("TXT"),
RecordName: tea.String(effectiveFQDN),
Data: &aliesa.CreateRecordRequestData{
Value: tea.String(value),
},
Ttl: tea.Int32(int32(d.config.TTL)),
}
_, err = d.client.CreateRecord(request)
return err
}
func (d *DNSProvider) removeDNSRecord(siteId int64, effectiveFQDN, value string) error {
record, err := d.findDNSRecord(siteId, effectiveFQDN, value)
if err != nil {
return err
}
if record == nil {
return nil
} else {
request := &aliesa.DeleteRecordRequest{
RecordId: record.RecordId,
}
_, err = d.client.DeleteRecord(request)
return err
}
}
@@ -20,6 +20,8 @@ func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger,
return nil, errors.New("the configuration of the acme challenge provider is nil")
}
// 没有使用 github.com/go-acme/lego/v4/providers/dns/baiducloud
// 因为该实现存在一些问题
providerConfig := internal.NewDefaultConfig()
providerConfig.AccessKeyID = config.AccessKeyId
providerConfig.SecretAccessKey = config.SecretAccessKey
@@ -99,8 +99,16 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
return fmt.Errorf("baiducloud: %w", err)
}
if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil {
return fmt.Errorf("baiducloud: %w", err)
// REF: https://cloud.baidu.com/doc/DNS/s/El4s7lssr#%E6%B7%BB%E5%8A%A0%E8%A7%A3%E6%9E%90%E8%AE%B0%E5%BD%95
bceCreateRecordReq := &bcedns.CreateRecordRequest{
Type: "TXT",
Rr: subDomain,
Value: info.Value,
Description: lo.ToPtr("certimate acme"),
Ttl: lo.ToPtr(int32(d.config.TTL)),
}
if err := d.client.CreateRecord(dns01.UnFqdn(authZone), bceCreateRecordReq, security.RandomString(32)); err != nil {
return fmt.Errorf("baiducloud: error when create record: %w", err)
}
return nil
@@ -119,7 +127,13 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return fmt.Errorf("baiducloud: %w", err)
}
if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); err != nil {
record, err := d.findDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value)
if err != nil {
return fmt.Errorf("baiducloud: error when find record: %q: %w", domain, err)
}
// REF: https://cloud.baidu.com/doc/DNS/s/El4s7lssr#%E5%88%A0%E9%99%A4%E8%A7%A3%E6%9E%90%E8%AE%B0%E5%BD%95
if err := d.client.DeleteRecord(dns01.UnFqdn(authZone), record.Id, security.RandomString(32)); err != nil {
return fmt.Errorf("baiducloud: %w", err)
}
@@ -130,73 +144,32 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
func (d *DNSProvider) findDNSRecord(zoneName, subDomain string) (*bcedns.Record, error) {
func (d *DNSProvider) findDNSRecord(zoneName, subDomain, tokenValue string) (*bcedns.Record, error) {
pageMarker := ""
pageSize := 1000
for {
request := &bcedns.ListRecordRequest{}
request.Rr = subDomain
request.Marker = pageMarker
request.MaxKeys = pageSize
// REF: https://cloud.baidu.com/doc/DNS/s/El4s7lssr#%E6%9F%A5%E8%AF%A2%E8%A7%A3%E6%9E%90%E8%AE%B0%E5%BD%95%E5%88%97%E8%A1%A8
bceListRecordReq := &bcedns.ListRecordRequest{}
bceListRecordReq.Rr = subDomain
bceListRecordReq.Marker = pageMarker
bceListRecordReq.MaxKeys = pageSize
response, err := d.client.ListRecord(zoneName, request)
ceListRecordResp, err := d.client.ListRecord(zoneName, bceListRecordReq)
if err != nil {
return nil, err
}
for _, record := range response.Records {
if record.Type == "TXT" && record.Rr == subDomain {
for _, record := range ceListRecordResp.Records {
if record.Type == "TXT" && record.Rr == subDomain && record.Value == tokenValue {
return &record, nil
}
}
if !response.IsTruncated {
pageMarker = ceListRecordResp.NextMarker
if pageMarker == "" {
break
}
pageMarker = response.NextMarker
}
return nil, nil
}
func (d *DNSProvider) addOrUpdateDNSRecord(zoneName, subDomain, value string) error {
record, err := d.findDNSRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
request := &bcedns.CreateRecordRequest{
Type: "TXT",
Rr: subDomain,
Value: value,
Ttl: lo.ToPtr(int32(d.config.TTL)),
}
err := d.client.CreateRecord(zoneName, request, security.RandomString(32))
return err
} else {
request := &bcedns.UpdateRecordRequest{
Type: "TXT",
Rr: subDomain,
Value: value,
Ttl: lo.ToPtr(int32(d.config.TTL)),
}
err := d.client.UpdateRecord(zoneName, record.Id, request, security.RandomString(32))
return err
}
}
func (d *DNSProvider) removeDNSRecord(zoneName, subDomain string) error {
record, err := d.findDNSRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
return nil
} else {
err = d.client.DeleteRecord(zoneName, record.Id, security.RandomString(32))
return err
}
return nil, errors.New("record not found")
}
@@ -1,10 +1,9 @@
package internal
import (
"encoding/json"
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/go-acme/lego/v4/challenge"
@@ -25,7 +24,7 @@ const (
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvReadTimeOut = envNamespace + "READ_TIMEOUT"
EnvReadTimeout = envNamespace + "READ_TIMEOUT"
EnvConnectTimeout = envNamespace + "CONNECT_TIMEOUT"
)
@@ -38,18 +37,21 @@ type Config struct {
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int
ReadTimeOut int
ReadTimeout int
ConnectTimeout int
}
type DNSProvider struct {
client *ecloudsdkclouddns.Client
config *Config
recordIDs map[string]string
recordIDsMu sync.Mutex
}
func NewDefaultConfig() *Config {
return &Config{
ReadTimeOut: env.GetOrDefaultInt(EnvReadTimeOut, 30),
ReadTimeout: env.GetOrDefaultInt(EnvReadTimeout, 30),
ConnectTimeout: env.GetOrDefaultInt(EnvConnectTimeout, 30),
TTL: env.GetOrDefaultInt(EnvTTL, 600),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
@@ -81,13 +83,15 @@ func NewDNSProviderConfig(cfg *Config) (*DNSProvider, error) {
// 资源池常量见: https://ecloud.10086.cn/op-help-center/doc/article/54462
// 默认全局
PoolId: "CIDC-CORE-00",
ReadTimeOut: cfg.ReadTimeOut,
ReadTimeOut: cfg.ReadTimeout,
ConnectTimeout: cfg.ConnectTimeout,
})
return &DNSProvider{
client: client,
config: cfg,
client: client,
config: cfg,
recordIDs: make(map[string]string),
recordIDsMu: sync.Mutex{},
}, nil
}
@@ -104,91 +108,51 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
return fmt.Errorf("cmccecloud: %w", err)
}
readDomain := strings.Trim(zoneName, ".")
record, err := d.getDomainRecord(readDomain, subDomain)
cmccCreateRecordReq := &model.CreateRecordOpenapiRequest{
CreateRecordOpenapiBody: &model.CreateRecordOpenapiBody{
LineId: "0", // 默认线路
Rr: subDomain,
DomainName: dns01.UnFqdn(zoneName),
Description: "certimate acme",
Type: model.CreateRecordOpenapiBodyTypeEnumTxt,
Value: info.Value,
Ttl: lo.ToPtr(int32(d.config.TTL)),
},
}
cmccCreateRecordResp, err := d.client.CreateRecordOpenapi(cmccCreateRecordReq)
if err != nil {
return err
return fmt.Errorf("cmccecloud: error when create record: %w", err)
} else if cmccCreateRecordResp.State != model.CreateRecordOpenapiResponseStateEnumOk {
return fmt.Errorf("cmccecloud: failed to create record: unexpected response state: '%s', errcode: '%s', errmsg: '%s'", cmccCreateRecordResp.State, cmccCreateRecordResp.ErrorCode, cmccCreateRecordResp.ErrorMessage)
}
if record == nil {
resp, err := d.client.CreateRecordOpenapi(&model.CreateRecordOpenapiRequest{
CreateRecordOpenapiBody: &model.CreateRecordOpenapiBody{
LineId: "0", // 默认线路
Rr: subDomain,
DomainName: readDomain,
Description: "certimate acme",
Type: model.CreateRecordOpenapiBodyTypeEnumTxt,
Value: info.Value,
Ttl: lo.ToPtr(int32(d.config.TTL)),
},
})
if err != nil {
return fmt.Errorf("cmccecloud: %w", err)
}
d.recordIDsMu.Lock()
d.recordIDs[token] = cmccCreateRecordResp.Body.RecordId
d.recordIDsMu.Unlock()
if resp.State != model.CreateRecordOpenapiResponseStateEnumOk {
return fmt.Errorf("cmccecloud: create record failed, response state: %s, message: %s, code: %s", resp.State, resp.ErrorMessage, resp.ErrorCode)
}
return nil
} else {
resp, err := d.client.ModifyRecordOpenapi(&model.ModifyRecordOpenapiRequest{
ModifyRecordOpenapiBody: &model.ModifyRecordOpenapiBody{
RecordId: record.RecordId,
Rr: subDomain,
DomainName: readDomain,
Description: "certmate acme",
LineId: "0",
Type: model.ModifyRecordOpenapiBodyTypeEnumTxt,
Value: info.Value,
Ttl: lo.ToPtr(int32(d.config.TTL)),
},
})
if err != nil {
return fmt.Errorf("cmccecloud: %w", err)
}
if resp.State != model.ModifyRecordOpenapiResponseStateEnumOk {
return fmt.Errorf("cmccecloud: create record failed, response state: %s", resp.State)
}
return nil
}
return nil
}
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
challengeInfo := dns01.GetChallengeInfo(domain, keyAuth)
info := dns01.GetChallengeInfo(domain, keyAuth)
zoneName, err := dns01.FindZoneByFqdn(challengeInfo.FQDN)
if err != nil {
return fmt.Errorf("cmccecloud: 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("cmccecloud: unknown record ID for '%s'", info.EffectiveFQDN)
}
subDomain, err := dns01.ExtractSubDomain(challengeInfo.FQDN, zoneName)
if err != nil {
return fmt.Errorf("cmccecloud: %w", err)
cmccDeleteRecordReq := &model.DeleteRecordOpenapiRequest{
DeleteRecordOpenapiBody: &model.DeleteRecordOpenapiBody{
RecordIdList: []string{recordID},
},
}
readDomain := strings.Trim(zoneName, ".")
record, err := d.getDomainRecord(readDomain, subDomain)
cmccDeleteRecordResp, err := d.client.DeleteRecordOpenapi(cmccDeleteRecordReq)
if err != nil {
return err
}
if record == nil {
return nil
} else {
resp, err := d.client.DeleteRecordOpenapi(&model.DeleteRecordOpenapiRequest{
DeleteRecordOpenapiBody: &model.DeleteRecordOpenapiBody{
RecordIdList: []string{record.RecordId},
},
})
if err != nil {
return fmt.Errorf("cmccecloud: %w", err)
}
if resp.State != model.DeleteRecordOpenapiResponseStateEnumOk {
return fmt.Errorf("cmccecloud: delete record failed, unexpected response state: %s", resp.State)
}
return fmt.Errorf("cmccecloud: error when delete record: %w", err)
} else if cmccDeleteRecordResp.State != model.DeleteRecordOpenapiResponseStateEnumOk {
return fmt.Errorf("cmccecloud: failed to delete record, unexpected response state: '%s', errcode: '%s', errmsg: '%s'", cmccDeleteRecordResp.State, cmccDeleteRecordResp.ErrorCode, cmccDeleteRecordResp.ErrorMessage)
}
return nil
@@ -197,40 +161,3 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
func (d *DNSProvider) getDomainRecord(domain string, rr string) (*model.ListRecordOpenapiResponseData, error) {
pageSize := int32(50)
page := int32(1)
for {
resp, err := d.client.ListRecordOpenapi(&model.ListRecordOpenapiRequest{
ListRecordOpenapiBody: &model.ListRecordOpenapiBody{
DomainName: domain,
},
ListRecordOpenapiQuery: &model.ListRecordOpenapiQuery{
PageSize: &pageSize,
Page: &page,
},
})
if err != nil {
return nil, err
}
if resp.State != model.ListRecordOpenapiResponseStateEnumOk {
respStr, _ := json.Marshal(resp)
return nil, fmt.Errorf("cmccecloud: request error: %s", string(respStr))
}
if resp.Body.Data != nil {
for _, item := range *resp.Body.Data {
if item.Rr == rr {
return &item, nil
}
}
}
if resp.Body.TotalPages == nil || page >= *resp.Body.TotalPages {
return nil, nil
}
page++
}
}
@@ -3,6 +3,7 @@ package internal
import (
"errors"
"fmt"
"sync"
"time"
"github.com/go-acme/lego/v4/challenge"
@@ -40,6 +41,9 @@ type Config struct {
type DNSProvider struct {
client *ctyundns.Client
config *Config
recordIDs map[string]int32
recordIDsMu sync.Mutex
}
func NewDefaultConfig() *Config {
@@ -76,8 +80,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
}
return &DNSProvider{
client: client,
config: config,
client: client,
config: config,
recordIDs: make(map[string]int32),
recordIDsMu: sync.Mutex{},
}, nil
}
@@ -94,9 +100,24 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
return fmt.Errorf("ctyun: %w", err)
}
if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil {
return fmt.Errorf("ctyun: %w", err)
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=122&api=11259&data=181&isNormal=1&vid=259
ctyunAddRecordReq := &ctyundns.AddRecordRequest{
Domain: lo.ToPtr(dns01.UnFqdn(authZone)),
Host: lo.ToPtr(subDomain),
Type: lo.ToPtr("TXT"),
LineCode: lo.ToPtr("Default"),
Value: lo.ToPtr(info.Value),
State: lo.ToPtr(int32(1)),
TTL: lo.ToPtr(int32(d.config.TTL)),
}
ctyunAddRecordResp, err := d.client.AddRecord(ctyunAddRecordReq)
if err != nil {
return fmt.Errorf("ctyun: error when create record: %w", err)
}
d.recordIDsMu.Lock()
d.recordIDs[token] = ctyunAddRecordResp.ReturnObj.RecordId
d.recordIDsMu.Unlock()
return nil
}
@@ -104,18 +125,19 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("ctyun: 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("tencentcloud-eo: unknown record ID for '%s'", info.EffectiveFQDN)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("ctyun: %w", err)
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=122&api=11262&data=181&isNormal=1&vid=259
ctyunDeleteRecordReq := &ctyundns.DeleteRecordRequest{
RecordId: lo.ToPtr(recordID),
}
if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); err != nil {
return fmt.Errorf("ctyun: %w", err)
if _, err := d.client.DeleteRecord(ctyunDeleteRecordReq); err != nil {
return fmt.Errorf("ctyun: error when delete record: %w", err)
}
return nil
@@ -124,80 +146,3 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
func (d *DNSProvider) findDNSRecordId(zoneName, subDomain string) (int32, error) {
// 查询解析记录列表
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=122&api=11264&data=181&isNormal=1&vid=259
request := &ctyundns.QueryRecordListRequest{}
request.Domain = lo.ToPtr(zoneName)
request.Host = lo.ToPtr(subDomain)
request.Type = lo.ToPtr("TXT")
response, err := d.client.QueryRecordList(request)
if err != nil {
return 0, err
}
if response.ReturnObj == nil || response.ReturnObj.Records == nil || len(response.ReturnObj.Records) == 0 {
return 0, nil
}
return response.ReturnObj.Records[0].RecordId, nil
}
func (d *DNSProvider) addOrUpdateDNSRecord(zoneName, subDomain, value string) error {
recordId, err := d.findDNSRecordId(zoneName, subDomain)
if err != nil {
return err
}
if recordId == 0 {
// 新增解析记录
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=122&api=11259&data=181&isNormal=1&vid=259
request := &ctyundns.AddRecordRequest{
Domain: lo.ToPtr(zoneName),
Host: lo.ToPtr(subDomain),
Type: lo.ToPtr("TXT"),
LineCode: lo.ToPtr("Default"),
Value: lo.ToPtr(value),
State: lo.ToPtr(int32(1)),
TTL: lo.ToPtr(int32(d.config.TTL)),
}
_, err := d.client.AddRecord(request)
return err
} else {
// 修改解析记录
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=122&api=11261&data=181&isNormal=1&vid=259
request := &ctyundns.UpdateRecordRequest{
RecordId: lo.ToPtr(recordId),
Domain: lo.ToPtr(zoneName),
Host: lo.ToPtr(subDomain),
Type: lo.ToPtr("TXT"),
LineCode: lo.ToPtr("Default"),
Value: lo.ToPtr(value),
State: lo.ToPtr(int32(1)),
TTL: lo.ToPtr(int32(d.config.TTL)),
}
_, err := d.client.UpdateRecord(request)
return err
}
}
func (d *DNSProvider) removeDNSRecord(zoneName, subDomain string) error {
recordId, err := d.findDNSRecordId(zoneName, subDomain)
if err != nil {
return err
}
if recordId == 0 {
return nil
} else {
// 删除解析记录
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=122&api=11262&data=181&isNormal=1&vid=259
request := &ctyundns.DeleteRecordRequest{
RecordId: lo.ToPtr(recordId),
}
_, err = d.client.DeleteRecord(request)
return err
}
}
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/go-acme/lego/v4/challenge"
@@ -41,6 +42,9 @@ type Config struct {
type DNSProvider struct {
client *dnslasdk.Client
config *Config
recordIDs map[string]string
recordIDsMu sync.Mutex
}
func NewDefaultConfig() *Config {
@@ -78,8 +82,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
}
return &DNSProvider{
client: client,
config: config,
client: client,
config: config,
recordIDs: make(map[string]string),
recordIDsMu: sync.Mutex{},
}, nil
}
@@ -96,28 +102,44 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
return fmt.Errorf("dnsla: %w", err)
}
if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil {
return fmt.Errorf("dnsla: %w", err)
zone, err := d.getDNSZone(dns01.UnFqdn(authZone))
if err != nil {
return fmt.Errorf("dnsla: error when list zones: %w", err)
}
// REF: https://www.dnsla.cn/docs/ApiDoc
dnslaCreateRecordReq := &dnslasdk.CreateRecordRequest{
DomainId: lo.ToPtr(zone.Id),
Type: lo.ToPtr(int32(16)),
Host: lo.ToPtr(subDomain),
Data: lo.ToPtr(info.Value),
Ttl: lo.ToPtr(int32(d.config.TTL)),
}
dnslaCreateRecordResp, err := d.client.CreateRecord(dnslaCreateRecordReq)
if err != nil {
return fmt.Errorf("dnsla: error when create record: %w", err)
}
d.recordIDsMu.Lock()
d.recordIDs[token] = dnslaCreateRecordResp.Data.Id
d.recordIDsMu.Unlock()
return nil
}
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("dnsla: 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("dnsla: unknown record ID for '%s'", info.EffectiveFQDN)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("dnsla: %w", err)
}
if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); err != nil {
return fmt.Errorf("dnsla: %w", err)
// REF: https://www.dnsla.cn/docs/ApiDoc
if _, err := d.client.DeleteRecord(recordID); err != nil {
return fmt.Errorf("dnsla: error when delete record: %w", err)
}
return nil
@@ -131,111 +153,30 @@ func (d *DNSProvider) getDNSZone(zoneName string) (*dnslasdk.DomainRecord, error
pageIndex := int32(1)
pageSize := int32(100)
for {
request := &dnslasdk.ListDomainsRequest{
// REF: https://www.dnsla.cn/docs/ApiDoc
dnslaListDomainsReq := &dnslasdk.ListDomainsRequest{
PageIndex: &pageIndex,
PageSize: &pageSize,
}
response, err := d.client.ListDomains(request)
dnslaListDomainsResp, err := d.client.ListDomains(dnslaListDomainsReq)
if err != nil {
return nil, err
}
if response.Data != nil {
for _, item := range response.Data.Results {
if dnslaListDomainsResp.Data != nil {
for _, item := range dnslaListDomainsResp.Data.Results {
if strings.TrimRight(item.Domain, ".") == zoneName || strings.TrimRight(item.DisplayDomain, ".") == zoneName {
return item, nil
}
}
}
if response.Data == nil || len(response.Data.Results) < int(pageSize) {
if dnslaListDomainsResp.Data == nil || len(dnslaListDomainsResp.Data.Results) < int(pageSize) {
break
}
pageIndex++
}
return nil, fmt.Errorf("dnsla: zone %s not found", zoneName)
}
func (d *DNSProvider) getDNSZoneAndRecord(zoneName, subDomain string) (*dnslasdk.DomainRecord, *dnslasdk.DnsRecord, error) {
zone, err := d.getDNSZone(zoneName)
if err != nil {
return nil, nil, err
}
pageIndex := int32(1)
pageSize := int32(100)
for {
request := &dnslasdk.ListRecordsRequest{
DomainId: &zone.Id,
Host: &subDomain,
PageIndex: &pageIndex,
PageSize: &pageSize,
}
response, err := d.client.ListRecords(request)
if err != nil {
return zone, nil, err
}
if response.Data != nil {
for _, record := range response.Data.Results {
if record.Type == 16 && (record.Host == subDomain || record.DisplayHost == subDomain) {
return zone, record, nil
}
}
}
if response.Data == nil || len(response.Data.Results) < int(pageSize) {
break
}
pageIndex++
}
return zone, nil, nil
}
func (d *DNSProvider) addOrUpdateDNSRecord(zoneName, subDomain, value string) error {
zone, record, err := d.getDNSZoneAndRecord(zoneName, subDomain)
if err != nil {
return err
}
const recordTypeTXT = 16
if record == nil {
request := &dnslasdk.CreateRecordRequest{
DomainId: lo.ToPtr(zone.Id),
Type: lo.ToPtr(int32(recordTypeTXT)),
Host: lo.ToPtr(subDomain),
Data: lo.ToPtr(value),
Ttl: lo.ToPtr(int32(d.config.TTL)),
}
_, err := d.client.CreateRecord(request)
return err
} else {
request := &dnslasdk.UpdateRecordRequest{
Id: lo.ToPtr(record.Id),
Type: lo.ToPtr(int32(recordTypeTXT)),
Host: lo.ToPtr(subDomain),
Data: lo.ToPtr(value),
Ttl: lo.ToPtr(int32(d.config.TTL)),
}
_, err := d.client.UpdateRecord(request)
return err
}
}
func (d *DNSProvider) removeDNSRecord(zoneName, subDomain string) error {
_, record, err := d.getDNSZoneAndRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
return nil
} else {
_, err = d.client.DeleteRecord(record.Id)
return err
}
return nil, fmt.Errorf("zone '%s' not found", zoneName)
}
@@ -84,8 +84,12 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
return fmt.Errorf("dynv6: %w", err)
}
if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil {
return fmt.Errorf("dynv6: %w", err)
if _, err := d.client.AppendRecords(context.Background(), dns01.UnFqdn(authZone), []libdns.Record{&libdns.TXT{
Name: subDomain,
Text: info.Value,
TTL: time.Duration(d.config.TTL),
}}); err != nil {
return fmt.Errorf("dynv6: error when create record: %w", err)
}
return nil
@@ -104,8 +108,13 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return fmt.Errorf("dynv6: %w", err)
}
if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); err != nil {
return fmt.Errorf("dynv6: %w", err)
record, err := d.findDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value)
if err != nil {
return fmt.Errorf("dynv6: error when find record: %w", err)
}
if _, err := d.client.DeleteRecords(context.Background(), dns01.UnFqdn(authZone), []libdns.Record{record}); err != nil {
return fmt.Errorf("dynv6: error when delete record: %w", err)
}
return nil
@@ -115,7 +124,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
func (d *DNSProvider) findDNSRecord(zoneName, subDomain string) (libdns.Record, error) {
func (d *DNSProvider) findDNSRecord(zoneName, subDomain, tokenValue string) (libdns.Record, error) {
records, err := d.client.GetRecords(context.Background(), zoneName)
if err != nil {
return nil, err
@@ -123,49 +132,10 @@ func (d *DNSProvider) findDNSRecord(zoneName, subDomain string) (libdns.Record,
for _, record := range records {
rr := record.RR()
if rr.Type == "TXT" && rr.Name == subDomain {
if rr.Type == "TXT" && rr.Name == subDomain && rr.Data == tokenValue {
return record, nil
}
}
return nil, nil
}
func (d *DNSProvider) addOrUpdateDNSRecord(zoneName, subDomain, value string) error {
record, err := d.findDNSRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
record = &libdns.TXT{
Name: subDomain,
Text: value,
TTL: time.Duration(d.config.TTL),
}
_, err := d.client.AppendRecords(context.Background(), zoneName, []libdns.Record{record})
return err
} else {
record = &libdns.TXT{
Name: subDomain,
Text: value,
TTL: time.Duration(d.config.TTL),
}
_, err := d.client.SetRecords(context.Background(), zoneName, []libdns.Record{record})
return err
}
}
func (d *DNSProvider) removeDNSRecord(zoneName, subDomain string) error {
record, err := d.findDNSRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
return nil
} else {
_, err = d.client.DeleteRecords(context.Background(), zoneName, []libdns.Record{record})
return err
}
return nil, errors.New("record not found")
}
@@ -3,6 +3,7 @@ package internal
import (
"errors"
"fmt"
"sync"
"time"
"github.com/go-acme/lego/v4/challenge"
@@ -40,6 +41,9 @@ type Config struct {
type DNSProvider struct {
client *gnamesdk.Client
config *Config
recordIDs map[string]int64
recordIDsMu sync.Mutex
}
func NewDefaultConfig() *Config {
@@ -77,8 +81,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
}
return &DNSProvider{
client: client,
config: config,
client: client,
config: config,
recordIDs: make(map[string]int64),
recordIDsMu: sync.Mutex{},
}, nil
}
@@ -95,9 +101,22 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
return fmt.Errorf("gname: %w", err)
}
if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil {
return fmt.Errorf("gname: %w", err)
// REF: https://www.gname.vip/domain/api/dns/add
gnameAddDomainResolutionReq := &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)),
}
gnameAddDomainResolutionResp, err := d.client.AddDomainResolution(gnameAddDomainResolutionReq)
if err != nil {
return fmt.Errorf("gname: error when create record: %w", err)
}
d.recordIDsMu.Lock()
d.recordIDs[token], _ = gnameAddDomainResolutionResp.Data.Int64()
d.recordIDsMu.Unlock()
return nil
}
@@ -110,13 +129,21 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
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)
d.recordIDsMu.Lock()
recordID, ok := d.recordIDs[token]
d.recordIDsMu.Unlock()
if !ok {
return fmt.Errorf("gname: unknown record ID for '%s'", info.EffectiveFQDN)
}
if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); err != nil {
return fmt.Errorf("gname: %w", err)
// REF: https://www.gname.vip/domain/api/dns/del
gnameDeleteDomainResolutionReq := &gnamesdk.DeleteDomainResolutionRequest{
ZoneName: lo.ToPtr(dns01.UnFqdn(authZone)),
RecordID: lo.ToPtr(recordID),
}
_, err = d.client.DeleteDomainResolution(gnameDeleteDomainResolutionReq)
if err != nil {
return fmt.Errorf("gname: error when delete record: %w", err)
}
return nil
@@ -125,87 +152,3 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
func (d *DNSProvider) findDNSRecord(zoneName, subDomain string) (*gnamesdk.DomainResolutionRecordord, error) {
page := int32(1)
pageSize := int32(20)
for {
request := &gnamesdk.ListDomainResolutionRequest{
ZoneName: lo.ToPtr(zoneName),
Page: lo.ToPtr(page),
PageSize: lo.ToPtr(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(zoneName, subDomain, value string) error {
record, err := d.findDNSRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
request := &gnamesdk.AddDomainResolutionRequest{
ZoneName: lo.ToPtr(zoneName),
RecordType: lo.ToPtr("TXT"),
RecordName: lo.ToPtr(subDomain),
RecordValue: lo.ToPtr(value),
TTL: lo.ToPtr(int32(d.config.TTL)),
}
_, err := d.client.AddDomainResolution(request)
return err
} else {
recordId, _ := record.ID.Int64()
request := &gnamesdk.ModifyDomainResolutionRequest{
ID: lo.ToPtr(recordId),
ZoneName: lo.ToPtr(zoneName),
RecordType: lo.ToPtr("TXT"),
RecordName: lo.ToPtr(subDomain),
RecordValue: lo.ToPtr(value),
TTL: lo.ToPtr(int32(d.config.TTL)),
}
_, err := d.client.ModifyDomainResolution(request)
return err
}
}
func (d *DNSProvider) removeDNSRecord(zoneName, subDomain string) error {
record, err := d.findDNSRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
return nil
}
recordId, _ := record.ID.Int64()
request := &gnamesdk.DeleteDomainResolutionRequest{
ZoneName: lo.ToPtr(zoneName),
RecordID: lo.ToPtr(recordId),
}
_, err = d.client.DeleteDomainResolution(request)
return err
}
@@ -3,6 +3,7 @@ package internal
import (
"errors"
"fmt"
"sync"
"time"
"github.com/go-acme/lego/v4/challenge"
@@ -43,6 +44,9 @@ type Config struct {
type DNSProvider struct {
client *jddnsclient.DomainserviceClient
config *Config
recordIDs map[string]int
recordIDsMu sync.Mutex
}
func NewDefaultConfig() *Config {
@@ -81,8 +85,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client.DisableLogger()
return &DNSProvider{
client: client,
config: config,
client: client,
config: config,
recordIDs: make(map[string]int),
recordIDsMu: sync.Mutex{},
}, nil
}
@@ -99,10 +105,28 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
return fmt.Errorf("jdcloud: %w", err)
}
if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil {
return fmt.Errorf("jdcloud: %w", err)
zone, err := d.getDNSZone(dns01.UnFqdn(authZone))
if err != nil {
return fmt.Errorf("jdcloud: error when list zones: %w", err)
}
// REF: https://docs.jdcloud.com/cn/jd-cloud-dns/api/createresourcerecord
jddnsCreateResourceRecordReq := jddnsapi.NewCreateResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", zone.Id), &jddnsmodel.AddRR{
Type: "TXT",
HostRecord: subDomain,
HostValue: info.Value,
Ttl: int(d.config.TTL),
ViewValue: -1,
})
jddnsCreateResourceRecordResp, err := d.client.CreateResourceRecord(jddnsCreateResourceRecordReq)
if err != nil {
return fmt.Errorf("jdcloud: error when create record: %w", err)
}
d.recordIDsMu.Lock()
d.recordIDs[token] = jddnsCreateResourceRecordResp.Result.DataList.Id
d.recordIDsMu.Unlock()
return nil
}
@@ -114,13 +138,23 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return fmt.Errorf("jdcloud: could not find zone for domain %q: %w", domain, err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("jdcloud: %w", err)
d.recordIDsMu.Lock()
recordID, ok := d.recordIDs[token]
d.recordIDsMu.Unlock()
if !ok {
return fmt.Errorf("jdcloud: unknown record ID for '%s'", info.EffectiveFQDN)
}
if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); err != nil {
return fmt.Errorf("jdcloud: %w", err)
zone, err := d.getDNSZone(dns01.UnFqdn(authZone))
if err != nil {
return fmt.Errorf("jdcloud: error when list zones: %w", err)
}
// REF: https://docs.jdcloud.com/cn/jd-cloud-dns/api/deleteresourcerecord
jddnsDeleteResourceRecordReq := jddnsapi.NewDeleteResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", zone.Id), fmt.Sprintf("%d", recordID))
_, err = d.client.DeleteResourceRecord(jddnsDeleteResourceRecordReq)
if err != nil {
return fmt.Errorf("jdcloud: error when delete record: %w", err)
}
return nil
@@ -134,21 +168,22 @@ func (d *DNSProvider) getDNSZone(zoneName string) (*jddnsmodel.DomainInfo, error
pageNumber := 1
pageSize := 10
for {
request := jddnsapi.NewDescribeDomainsRequest(d.config.RegionId, pageNumber, pageSize)
request.SetDomainName(zoneName)
// REF: https://docs.jdcloud.com/cn/jd-cloud-dns/api/describedomains
jddnsDescribeDomainsReq := jddnsapi.NewDescribeDomainsRequest(d.config.RegionId, pageNumber, pageSize)
jddnsDescribeDomainsReq.SetDomainName(zoneName)
response, err := d.client.DescribeDomains(request)
jddnsDescribeDomainsResp, err := d.client.DescribeDomains(jddnsDescribeDomainsReq)
if err != nil {
return nil, err
}
for _, item := range response.Result.DataList {
for _, item := range jddnsDescribeDomainsResp.Result.DataList {
if item.DomainName == zoneName {
return &item, nil
}
}
if len(response.Result.DataList) < pageSize {
if len(jddnsDescribeDomainsResp.Result.DataList) < pageSize {
break
}
@@ -157,82 +192,3 @@ func (d *DNSProvider) getDNSZone(zoneName string) (*jddnsmodel.DomainInfo, error
return nil, fmt.Errorf("jdcloud: zone %s not found", zoneName)
}
func (d *DNSProvider) getDNSZoneAndRecord(zoneName, subDomain string) (*jddnsmodel.DomainInfo, *jddnsmodel.RRInfo, error) {
zone, err := d.getDNSZone(zoneName)
if err != nil {
return nil, nil, err
}
pageNumber := 1
pageSize := 10
for {
request := jddnsapi.NewDescribeResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", zone.Id))
request.SetSearch(subDomain)
request.SetPageNumber(pageNumber)
request.SetPageSize(pageSize)
response, err := d.client.DescribeResourceRecord(request)
if err != nil {
return zone, nil, err
}
for _, record := range response.Result.DataList {
if record.Type == "TXT" && record.HostRecord == subDomain {
return zone, &record, nil
}
}
if len(response.Result.DataList) < pageSize {
break
}
pageNumber++
}
return zone, nil, nil
}
func (d *DNSProvider) addOrUpdateDNSRecord(zoneName, subDomain, value string) error {
zone, record, err := d.getDNSZoneAndRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
request := jddnsapi.NewCreateResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", zone.Id), &jddnsmodel.AddRR{
Type: "TXT",
HostRecord: subDomain,
HostValue: value,
Ttl: int(d.config.TTL),
ViewValue: -1,
})
_, err := d.client.CreateResourceRecord(request)
return err
} else {
request := jddnsapi.NewModifyResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", zone.Id), fmt.Sprintf("%d", record.Id), &jddnsmodel.UpdateRR{
Type: "TXT",
HostRecord: subDomain,
HostValue: value,
Ttl: int(d.config.TTL),
ViewValue: -1,
})
_, err := d.client.ModifyResourceRecord(request)
return err
}
}
func (d *DNSProvider) removeDNSRecord(zoneName, subDomain string) error {
zone, record, err := d.getDNSZoneAndRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
return nil
} else {
request := jddnsapi.NewDeleteResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", zone.Id), fmt.Sprintf("%d", record.Id))
_, err = d.client.DeleteResourceRecord(request)
return err
}
}
@@ -62,43 +62,3 @@ func (c *TeoClient) DeleteDnsRecordsWithContext(ctx context.Context, request *tc
err = c.Send(request, response)
return
}
func (c *TeoClient) DescribeDnsRecords(request *tcteo.DescribeDnsRecordsRequest) (response *tcteo.DescribeDnsRecordsResponse, err error) {
return c.DescribeDnsRecordsWithContext(context.Background(), request)
}
func (c *TeoClient) DescribeDnsRecordsWithContext(ctx context.Context, request *tcteo.DescribeDnsRecordsRequest) (response *tcteo.DescribeDnsRecordsResponse, err error) {
if request == nil {
request = tcteo.NewDescribeDnsRecordsRequest()
}
c.InitBaseRequest(&request.BaseRequest, "teo", tcteo.APIVersion, "DescribeDnsRecords")
if c.GetCredential() == nil {
return nil, errors.New("DescribeDnsRecords require credential")
}
request.SetContext(ctx)
response = tcteo.NewDescribeDnsRecordsResponse()
err = c.Send(request, response)
return
}
func (c *TeoClient) ModifyDnsRecordsStatus(request *tcteo.ModifyDnsRecordsStatusRequest) (response *tcteo.ModifyDnsRecordsStatusResponse, err error) {
return c.ModifyDnsRecordsStatusWithContext(context.Background(), request)
}
func (c *TeoClient) ModifyDnsRecordsStatusWithContext(ctx context.Context, request *tcteo.ModifyDnsRecordsStatusRequest) (response *tcteo.ModifyDnsRecordsStatusResponse, err error) {
if request == nil {
request = tcteo.NewModifyDnsRecordsStatusRequest()
}
c.InitBaseRequest(&request.BaseRequest, "teo", tcteo.APIVersion, "ModifyDnsRecordsStatus")
if c.GetCredential() == nil {
return nil, errors.New("ModifyDnsRecordsStatus require credential")
}
request.SetContext(ctx)
response = tcteo.NewModifyDnsRecordsStatusResponse()
err = c.Send(request, response)
return
}
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"math"
"sync"
"time"
"github.com/go-acme/lego/v4/challenge"
@@ -12,6 +13,7 @@ import (
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
tcteo "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo/v20220901"
"golang.org/x/net/idna"
)
const (
@@ -43,6 +45,9 @@ type Config struct {
type DNSProvider struct {
client *TeoClient
config *Config
recordIDs map[string]*string
recordIDsMu sync.Mutex
}
func NewDefaultConfig() *Config {
@@ -82,26 +87,57 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
}
return &DNSProvider{
client: client,
config: config,
client: client,
config: config,
recordIDs: make(map[string]*string),
recordIDsMu: sync.Mutex{},
}, nil
}
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
if err := d.addDNSRecord(dns01.UnFqdn(info.EffectiveFQDN), info.Value); err != nil {
return fmt.Errorf("tencentcloud-eo: %w", err)
punnyCoded, err := idna.ToASCII(dns01.UnFqdn(info.EffectiveFQDN))
if err != nil {
return fmt.Errorf("tencentcloud-eo: fail to convert punycode: %w", err)
}
// REF: https://cloud.tencent.com/document/product/1552/80720
teoCreateDnsRecordReq := tcteo.NewCreateDnsRecordRequest()
teoCreateDnsRecordReq.ZoneId = common.StringPtr(d.config.ZoneID)
teoCreateDnsRecordReq.Name = common.StringPtr(punnyCoded)
teoCreateDnsRecordReq.Type = common.StringPtr("TXT")
teoCreateDnsRecordReq.Content = common.StringPtr(info.Value)
teoCreateDnsRecordReq.TTL = common.Int64Ptr(int64(d.config.TTL))
teoCreateDnsRecordResp, err := d.client.CreateDnsRecord(teoCreateDnsRecordReq)
if err != nil {
return fmt.Errorf("tencentcloud-eo: error when create record: %w", err)
}
d.recordIDsMu.Lock()
d.recordIDs[token] = teoCreateDnsRecordResp.Response.RecordId
d.recordIDsMu.Unlock()
return nil
}
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
if err := d.removeDNSRecord(dns01.UnFqdn(info.EffectiveFQDN), info.Value); err != nil {
return fmt.Errorf("tencentcloud-eo: %w", err)
d.recordIDsMu.Lock()
recordID, ok := d.recordIDs[token]
d.recordIDsMu.Unlock()
if !ok {
return fmt.Errorf("tencentcloud-eo: unknown record ID for '%s'", info.EffectiveFQDN)
}
// REF: https://cloud.tencent.com/document/product/1552/80718
teoDeleteDnsRecordReq := tcteo.NewDeleteDnsRecordsRequest()
teoDeleteDnsRecordReq.ZoneId = common.StringPtr(d.config.ZoneID)
teoDeleteDnsRecordReq.RecordIds = []*string{recordID}
_, err := d.client.DeleteDnsRecords(teoDeleteDnsRecordReq)
if err != nil {
return fmt.Errorf("tencentcloud-eo: error when delete record: %w", err)
}
return nil
@@ -110,88 +146,3 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
func (d *DNSProvider) findDNSRecord(effectiveFQDN, value string) (*tcteo.DnsRecord, error) {
pageOffset := 0
pageLimit := 1000
for {
request := tcteo.NewDescribeDnsRecordsRequest()
request.ZoneId = common.StringPtr(d.config.ZoneID)
request.Offset = common.Int64Ptr(int64(pageOffset))
request.Limit = common.Int64Ptr(int64(pageLimit))
request.Filters = []*tcteo.AdvancedFilter{
{
Name: common.StringPtr("type"),
Values: []*string{common.StringPtr("TXT")},
},
}
response, err := d.client.DescribeDnsRecords(request)
if err != nil {
return nil, err
}
if response.Response == nil {
break
} else {
for _, record := range response.Response.DnsRecords {
if *record.Name == effectiveFQDN && *record.Content == value {
return record, nil
}
}
if len(response.Response.DnsRecords) < pageLimit {
break
}
pageOffset += len(response.Response.DnsRecords)
}
}
return nil, nil
}
func (d *DNSProvider) addDNSRecord(effectiveFQDN, value string) error {
record, err := d.findDNSRecord(effectiveFQDN, value)
if err != nil {
return err
}
if record == nil {
request := tcteo.NewCreateDnsRecordRequest()
request.ZoneId = common.StringPtr(d.config.ZoneID)
request.Name = common.StringPtr(effectiveFQDN)
request.Type = common.StringPtr("TXT")
request.Content = common.StringPtr(value)
request.TTL = common.Int64Ptr(int64(d.config.TTL))
_, err := d.client.CreateDnsRecord(request)
return err
} else {
if *record.Status == "disable" {
request := tcteo.NewModifyDnsRecordsStatusRequest()
request.ZoneId = common.StringPtr(d.config.ZoneID)
request.RecordsToEnable = []*string{record.RecordId}
if _, err = d.client.ModifyDnsRecordsStatus(request); err != nil {
return err
}
}
return nil
}
}
func (d *DNSProvider) removeDNSRecord(effectiveFQDN, value string) error {
record, err := d.findDNSRecord(effectiveFQDN, value)
if err != nil {
return err
}
if record == nil {
return nil
} else {
request := tcteo.NewDeleteDnsRecordsRequest()
request.ZoneId = common.StringPtr(d.config.ZoneID)
request.RecordIds = []*string{record.RecordId}
_, err = d.client.DeleteDnsRecords(request)
return err
}
}
@@ -21,6 +21,8 @@ func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger,
return nil, errors.New("the configuration of the acme challenge provider is nil")
}
// 没有使用 github.com/go-acme/lego/v4/providers/dns/edgeone
// 因为该实现存在一些问题
providerConfig := internal.NewDefaultConfig()
providerConfig.SecretID = config.SecretId
providerConfig.SecretKey = config.SecretKey
@@ -94,43 +94,18 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
return fmt.Errorf("ucloud-udnr: could not find zone for domain %q: %w", domain, err)
}
recordName := dns01.UnFqdn(info.EffectiveFQDN)
udnrDomainDNSQueryReq := d.client.NewQueryDomainDNSRequest()
udnrDomainDNSQueryReq.Dn = ucloud.String(authZone)
if d.config.ProjectId != "" {
udnrDomainDNSQueryReq.SetProjectId(d.config.ProjectId)
}
if udnrDomainDNSQueryResp, err := d.client.QueryDomainDNS(udnrDomainDNSQueryReq); err != nil {
return fmt.Errorf("ucloud-udnr: %w", err)
} else {
for _, record := range udnrDomainDNSQueryResp.Data {
if record.DnsType == "TXT" && record.RecordName == recordName {
udnrDomainDNSDeleteReq := d.client.NewDeleteDomainDNSRequest()
udnrDomainDNSDeleteReq.Dn = ucloud.String(authZone)
udnrDomainDNSDeleteReq.DnsType = ucloud.String(record.DnsType)
udnrDomainDNSDeleteReq.RecordName = ucloud.String(record.RecordName)
udnrDomainDNSDeleteReq.Content = ucloud.String(record.Content)
if d.config.ProjectId != "" {
udnrDomainDNSDeleteReq.SetProjectId(d.config.ProjectId)
}
d.client.DeleteDomainDNS(udnrDomainDNSDeleteReq)
break
}
}
}
// REF: https://docs.ucloud.cn/api/udnr-api/udnr_domain_dns_add
udnrDomainDNSAddReq := d.client.NewAddDomainDNSRequest()
udnrDomainDNSAddReq.Dn = ucloud.String(authZone)
udnrDomainDNSAddReq.DnsType = ucloud.String("TXT")
udnrDomainDNSAddReq.RecordName = ucloud.String(recordName)
udnrDomainDNSAddReq.RecordName = ucloud.String(dns01.UnFqdn(info.EffectiveFQDN))
udnrDomainDNSAddReq.Content = ucloud.String(info.Value)
udnrDomainDNSAddReq.TTL = ucloud.String(fmt.Sprintf("%d", d.config.TTL))
if d.config.ProjectId != "" {
udnrDomainDNSAddReq.SetProjectId(d.config.ProjectId)
}
if _, err := d.client.AddDomainDNS(udnrDomainDNSAddReq); err != nil {
return fmt.Errorf("ucloud-udnr: %w", err)
return fmt.Errorf("ucloud-udnr: error when create record: %w", err)
}
return nil
@@ -144,29 +119,33 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return fmt.Errorf("ucloud-udnr: could not find zone for domain %q: %w", domain, err)
}
recordName := dns01.UnFqdn(info.EffectiveFQDN)
// REF: https://docs.ucloud.cn/api/udnr-api/udnr_domain_dns_query
udnrDomainDNSQueryReq := d.client.NewQueryDomainDNSRequest()
udnrDomainDNSQueryReq.Dn = ucloud.String(authZone)
if d.config.ProjectId != "" {
udnrDomainDNSQueryReq.SetProjectId(d.config.ProjectId)
}
if udnrDomainDNSQueryResp, err := d.client.QueryDomainDNS(udnrDomainDNSQueryReq); err != nil {
return fmt.Errorf("ucloud-udnr: %w", err)
} else {
for _, record := range udnrDomainDNSQueryResp.Data {
if record.DnsType == "TXT" && record.RecordName == recordName {
udnrDomainDNSDeleteReq := d.client.NewDeleteDomainDNSRequest()
udnrDomainDNSDeleteReq.Dn = ucloud.String(authZone)
udnrDomainDNSDeleteReq.DnsType = ucloud.String(record.DnsType)
udnrDomainDNSDeleteReq.RecordName = ucloud.String(record.RecordName)
udnrDomainDNSDeleteReq.Content = ucloud.String(record.Content)
if d.config.ProjectId != "" {
udnrDomainDNSDeleteReq.SetProjectId(d.config.ProjectId)
}
d.client.DeleteDomainDNS(udnrDomainDNSDeleteReq)
break
udnrDomainDNSQueryResp, err := d.client.QueryDomainDNS(udnrDomainDNSQueryReq)
if err != nil {
return fmt.Errorf("ucloud-udnr: error when list records: %w", err)
}
// REF: https://docs.ucloud.cn/api/udnr-api/udnr_delete_dns_record
for _, record := range udnrDomainDNSQueryResp.Data {
if record.DnsType == "TXT" && record.RecordName == dns01.UnFqdn(info.EffectiveFQDN) && record.Content == info.Value {
udnrDomainDNSDeleteReq := d.client.NewDeleteDomainDNSRequest()
udnrDomainDNSDeleteReq.Dn = ucloud.String(authZone)
udnrDomainDNSDeleteReq.DnsType = ucloud.String(record.DnsType)
udnrDomainDNSDeleteReq.RecordName = ucloud.String(record.RecordName)
udnrDomainDNSDeleteReq.Content = ucloud.String(record.Content)
if d.config.ProjectId != "" {
udnrDomainDNSDeleteReq.SetProjectId(d.config.ProjectId)
}
_, err := d.client.DeleteDomainDNS(udnrDomainDNSDeleteReq)
if err != nil {
return fmt.Errorf("ucloud-udnr: error when delete record: %w", err)
}
break
}
}