diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/client.go b/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/client.go index 8f8b6e8aa..a0722bd97 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/client.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/client.go @@ -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 { diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/lego.go index 9068d867e..0694634c1 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/lego.go @@ -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 - } -} diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/baiducloud.go b/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/baiducloud.go index dbd8939b0..d67fb86be 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/baiducloud.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/baiducloud.go @@ -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 diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/internal/lego.go index 6c29ccb45..794bbf749 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/internal/lego.go @@ -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") } diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/cmcccloud/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/cmcccloud/internal/lego.go index 7110a54d5..15162a44e 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/cmcccloud/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/cmcccloud/internal/lego.go @@ -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++ - } -} diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/ctcccloud/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/ctcccloud/internal/lego.go index 9a5b61caf..82bf433b9 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/ctcccloud/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/ctcccloud/internal/lego.go @@ -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 - } -} diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/dnsla/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/dnsla/internal/lego.go index 122a87e28..f0707d9e2 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/dnsla/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/dnsla/internal/lego.go @@ -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) } diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/dynv6/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/dynv6/internal/lego.go index 6f96982c9..13590f6e6 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/dynv6/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/dynv6/internal/lego.go @@ -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") } diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/gname/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/gname/internal/lego.go index 2d2252d72..a3cf2c90b 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/gname/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/gname/internal/lego.go @@ -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 -} diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/jdcloud/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/jdcloud/internal/lego.go index d63eb3754..78bc5ac27 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/jdcloud/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/jdcloud/internal/lego.go @@ -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 - } -} diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/tencentcloud-eo/internal/client.go b/pkg/core/ssl-applicator/acme-dns01/providers/tencentcloud-eo/internal/client.go index eb0669e11..f7b875cd4 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/tencentcloud-eo/internal/client.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/tencentcloud-eo/internal/client.go @@ -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 -} diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/tencentcloud-eo/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/tencentcloud-eo/internal/lego.go index fa8459ae0..ceeedd6ea 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/tencentcloud-eo/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/tencentcloud-eo/internal/lego.go @@ -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 - } -} diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/tencentcloud-eo/tencentcloud_eo.go b/pkg/core/ssl-applicator/acme-dns01/providers/tencentcloud-eo/tencentcloud_eo.go index 0fcbc2d77..8380f7058 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/tencentcloud-eo/tencentcloud_eo.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/tencentcloud-eo/tencentcloud_eo.go @@ -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 diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/ucloud-udnr/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/ucloud-udnr/internal/lego.go index 821325336..644f9e64e 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/ucloud-udnr/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/ucloud-udnr/internal/lego.go @@ -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 } }