Skip to content

Commit

Permalink
feat: add cmcc dns
Browse files Browse the repository at this point in the history
  • Loading branch information
hujingnb committed Jan 31, 2025
1 parent 57f8db0 commit 6505e01
Show file tree
Hide file tree
Showing 374 changed files with 11,262 additions and 14 deletions.
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ make local.run

<summary>[展开查看]</summary>

| 提供商 | 备注 |
| :----------------------------------------------------------------- | :-------------------------------------- |
| [阿里云](https://www.aliyun.com/) | |
| [腾讯云](https://cloud.tencent.com/) | |
| [华为云](https://www.huaweicloud.com/) | |
| [火山引擎](https://www.volcengine.com/) | |
| 提供商 | 备注 |
|:-------------------------------------------------------------------| :-------------------------------------- |
| [阿里云](https://www.aliyun.com/) | |
| [腾讯云](https://cloud.tencent.com/) | |
| [华为云](https://www.huaweicloud.com/) | |
| [火山引擎](https://www.volcengine.com/) | |
| [AWS Route53](https://aws.amazon.com/route53/) | |
| [Azure](https://azure.microsoft.com/) | |
| [CloudFlare](https://www.cloudflare.com/) | |
Expand All @@ -101,10 +101,11 @@ make local.run
| [Name.com](https://www.name.com/) | |
| [NameSilo](https://www.namesilo.com/) | |
| [IBM NS1 Connect](https://www.ibm.com/cn-zh/products/ns1-connect/) | |
| [雨云](https://www.rainyun.com/) | |
| [西部数码](https://www.west.cn/) | |
| [雨云](https://www.rainyun.com/) | |
| [西部数码](https://www.west.cn/) | |
| [移动云](https://ecloud.10086.cn/) | |
| [PowerDNS](https://www.powerdns.com/) | |
| ACME 代理 HTTP 请求 | 可申请允许通过 HTTP 请求修改 DNS 的域名 |
| ACME 代理 HTTP 请求 | 可申请允许通过 HTTP 请求修改 DNS 的域名 |

</details>

Expand Down
3 changes: 2 additions & 1 deletion README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ The following DNS providers are supported:
<summary>[Fold/Unfold to view ...]</summary>

| Provider | Remarks |
| :----------------------------------------------------------- | :------------------------------------ |
|:-------------------------------------------------------------| :------------------------------------ |
| [Alibaba Cloud](https://www.alibabacloud.com/) | |
| [Tencent Cloud](https://www.tencentcloud.com/) | |
| [Huawei Cloud](https://www.huaweicloud.com/) | |
Expand All @@ -102,6 +102,7 @@ The following DNS providers are supported:
| [IBM NS1 Connect](https://www.ibm.com/products/ns1-connect/) | |
| [Rain Yun](https://www.rainyun.com/) | |
| [West.cn](https://www.west.cn/) | |
| [CMCC](https://ecloud.10086.cn/) | |
| [PowerDNS](https://www.powerdns.com/) | |
| ACME Proxy HTTP Request | Supports managing DNS by HTTP request |

Expand Down
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ require (
github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.8
github.com/volcengine/volc-sdk-golang v1.0.193
github.com/volcengine/volcengine-go-sdk v1.0.178
gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1
golang.org/x/crypto v0.32.0
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
k8s.io/api v0.32.1
Expand Down Expand Up @@ -94,6 +95,7 @@ require (
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
gitlab.ecloud.com/ecloud/ecloudsdkcore v1.0.0 // indirect
go.mongodb.org/mongo-driver v1.17.2 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
Expand Down Expand Up @@ -204,3 +206,7 @@ require (
modernc.org/memory v1.8.2 // indirect
modernc.org/sqlite v1.34.5 // indirect
)

replace gitlab.ecloud.com/ecloud/ecloudsdkcore v1.0.0 => ./internal/pkg/vendors/cmcc-sdk/ecloudsdkcore@v1.0.0

replace gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1 => ./internal/pkg/vendors/cmcc-sdk/ecloudsdkclouddns@v1.0.1
16 changes: 16 additions & 0 deletions internal/applicant/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
providerAzureDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/azure-dns"
providerCloudflare "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudflare"
providerClouDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns"
providerCmcc "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcc"
providerGname "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname"
providerGoDaddy "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/godaddy"
providerHuaweiCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/huaweicloud"
Expand Down Expand Up @@ -131,6 +132,21 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
})
return applicant, err
}
case domain.ApplyDNSProviderTypeCmcc:
{
access := domain.AccessConfigForCmcc{}
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
}

applicant, err := providerCmcc.NewChallengeProvider(&providerCmcc.CmccApplicantConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}

case domain.ApplyDNSProviderTypeGname:
{
Expand Down
4 changes: 4 additions & 0 deletions internal/domain/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ type AccessConfigForClouDNS struct {
AuthPassword string `json:"authPassword"`
}

type AccessConfigForCmcc struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
}
type AccessConfigForDogeCloud struct {
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
Expand Down
4 changes: 3 additions & 1 deletion internal/domain/provider.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package domain
package domain

type AccessProviderType string

Expand All @@ -17,6 +17,7 @@ const (
AccessProviderTypeBytePlus = AccessProviderType("byteplus")
AccessProviderTypeCloudflare = AccessProviderType("cloudflare")
AccessProviderTypeClouDNS = AccessProviderType("cloudns")
AccessProviderTypeCmcc = AccessProviderType("cmcc")
AccessProviderTypeDogeCloud = AccessProviderType("dogecloud")
AccessProviderTypeEdgio = AccessProviderType("edgio")
AccessProviderTypeGname = AccessProviderType("gname")
Expand Down Expand Up @@ -56,6 +57,7 @@ const (
ApplyDNSProviderTypeAzureDNS = ApplyDNSProviderType("azure-dns")
ApplyDNSProviderTypeCloudflare = ApplyDNSProviderType("cloudflare")
ApplyDNSProviderTypeClouDNS = ApplyDNSProviderType("cloudns")
ApplyDNSProviderTypeCmcc = ApplyDNSProviderType("cmcc")
ApplyDNSProviderTypeGname = ApplyDNSProviderType("gname")
ApplyDNSProviderTypeGoDaddy = ApplyDNSProviderType("godaddy")
ApplyDNSProviderTypeHuaweiCloud = ApplyDNSProviderType("huaweicloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeHuaweiCloudDNS]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package cmcc

import (
"errors"
"time"

"github.com/go-acme/lego/v4/challenge"

"github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcc/internal"
)

type CmccApplicantConfig struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}

func NewChallengeProvider(config *CmccApplicantConfig) (challenge.Provider, error) {
if config == nil {
return nil, errors.New("config is nil")
}

providerConfig := internal.NewDefaultConfig()
providerConfig.AccessKeyId = config.AccessKeyId
providerConfig.AccessKeySecret = config.AccessKeySecret
if config.DnsTTL != 0 {
providerConfig.TTL = config.DnsTTL
}
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}

provider, err := internal.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}

return provider, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package internal

import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"

"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"gitlab.ecloud.com/ecloud/ecloudsdkclouddns"
"gitlab.ecloud.com/ecloud/ecloudsdkclouddns/model"
"gitlab.ecloud.com/ecloud/ecloudsdkcore/config"
)

const (
envNamespace = "CMCC_"

EnvAccessKeyId = envNamespace + "ACCESS_KEY_ID"
EnvAccessKeySecret = envNamespace + "ACCESS_KEY_SECRET"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvReadTimeOut = envNamespace + "READ_TIMEOUT"
EnvConnectTimeout = envNamespace + "CONNECT_TIMEOUT"
)

var _ challenge.ProviderTimeout = (*DNSProvider)(nil)

type Config struct {
AccessKeyId string
AccessKeySecret string
ReadTimeOut int
ConnectTimeout int
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int32
}

type DNSProvider struct {
client *ecloudsdkclouddns.Client
config *Config
}

func NewDefaultConfig() *Config {
return &Config{
ReadTimeOut: env.GetOrDefaultInt(EnvReadTimeOut, 30),
ConnectTimeout: env.GetOrDefaultInt(EnvConnectTimeout, 30),
TTL: int32(env.GetOrDefaultInt(EnvTTL, 600)),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
}
}

func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvAccessKeyId, EnvAccessKeySecret)
if err != nil {
return nil, fmt.Errorf("cmcc: %w", err)
}

cfg := NewDefaultConfig()
cfg.AccessKeyId = values[EnvAccessKeyId]
cfg.AccessKeySecret = values[EnvAccessKeySecret]

return NewDNSProviderConfig(cfg)
}

func NewDNSProviderConfig(cfg *Config) (*DNSProvider, error) {
if cfg == nil {
return nil, errors.New("cmcc: the configuration of the DNS provider is nil")
}

client := ecloudsdkclouddns.NewClient(&config.Config{
AccessKey: cfg.AccessKeyId,
SecretKey: cfg.AccessKeySecret,
// 资源池常量见: https://ecloud.10086.cn/op-help-center/doc/article/54462
// 默认全局
PoolId: "CIDC-CORE-00",
ReadTimeOut: cfg.ReadTimeOut,
ConnectTimeout: cfg.ConnectTimeout,
})

return &DNSProvider{
client: client,
config: cfg,
}, nil
}

func (d *DNSProvider) Present(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)

zoneName, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("cmcc: %w", err)
}

subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zoneName)
if err != nil {
return fmt.Errorf("cmcc: %w", err)
}

readDomain := strings.Trim(zoneName, ".")
record, err := d.getDomainRecord(readDomain, subDomain)
if err != nil {
return err
}
if record == nil { // add new record
resp, err := d.client.CreateRecordOpenapi(&model.CreateRecordOpenapiRequest{
CreateRecordOpenapiBody: &model.CreateRecordOpenapiBody{
LineId: "0", // 默认线路
Rr: subDomain,
DomainName: readDomain,
Description: "from certimate",
Type: model.CreateRecordOpenapiBodyTypeEnumTxt,
Value: info.Value,
Ttl: &d.config.TTL,
},
})
if err != nil {
return fmt.Errorf("lego: %w", err)
}
if resp.State != model.CreateRecordOpenapiResponseStateEnumOk {
return fmt.Errorf("lego: create record failed, response state: %s, message: %s, code: %s", resp.State, resp.ErrorMessage, resp.ErrorCode)
}
return nil
} else { // update record
resp, err := d.client.ModifyRecordOpenapi(&model.ModifyRecordOpenapiRequest{
ModifyRecordOpenapiBody: &model.ModifyRecordOpenapiBody{
RecordId: record.RecordId,
Rr: subDomain,
DomainName: readDomain,
Description: "from certmate",
LineId: "0",
Type: model.ModifyRecordOpenapiBodyTypeEnumTxt,
Value: info.Value,
Ttl: &d.config.TTL,
},
})
if err != nil {
return fmt.Errorf("lego: %w", err)
}
if resp.State != model.ModifyRecordOpenapiResponseStateEnumOk {
return fmt.Errorf("lego: create record failed, response state: %s", resp.State)
}
return nil
}
}

func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
challengeInfo := dns01.GetChallengeInfo(domain, keyAuth)
zoneName, err := dns01.FindZoneByFqdn(challengeInfo.FQDN)
if err != nil {
return fmt.Errorf("cmcc: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(challengeInfo.FQDN, zoneName)
if err != nil {
return fmt.Errorf("cmcc: %w", err)
}
readDomain := strings.Trim(zoneName, ".")
record, err := d.getDomainRecord(readDomain, subDomain)
if err != nil {
return err
}
if record == nil {
return nil
}
resp, err := d.client.DeleteRecordOpenapi(&model.DeleteRecordOpenapiRequest{
DeleteRecordOpenapiBody: &model.DeleteRecordOpenapiBody{
RecordIdList: []string{record.RecordId},
},
})
if err != nil {
return fmt.Errorf("lego: %w", err)
}
if resp.State != model.DeleteRecordOpenapiResponseStateEnumOk {
return fmt.Errorf("lego: delete record failed, response state: %s", resp.State)
}
return nil
}

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("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++
}
}
Loading

0 comments on commit 6505e01

Please sign in to comment.