feat: add baiducloud dns-01 applicant

This commit is contained in:
Fu Diwei 2025-02-18 17:31:42 +08:00
parent c451bf5e03
commit 1bac6174ad
13 changed files with 269 additions and 7 deletions

View File

@ -90,6 +90,7 @@ make local.run
| :----------------------------------------------------------------- | :-------------------------------------- |
| [阿里云](https://www.aliyun.com/) | |
| [腾讯云](https://cloud.tencent.com/) | |
| [百度智能云](https://cloud.baidu.com/) | |
| [华为云](https://www.huaweicloud.com/) | |
| [火山引擎](https://www.volcengine.com/) | |
| [AWS Route53](https://aws.amazon.com/route53/) | |

View File

@ -89,6 +89,7 @@ The following DNS providers are supported:
| :----------------------------------------------------------- | :------------------------------------ |
| [Alibaba Cloud](https://www.alibabacloud.com/) | |
| [Tencent Cloud](https://www.tencentcloud.com/) | |
| [Baidu AI Cloud](https://intl.cloud.baidu.com/) | |
| [Huawei Cloud](https://www.huaweicloud.com/) | |
| [Volcengine](https://www.volcengine.com/) | |
| [AWS Route53](https://aws.amazon.com/route53/) | |

2
go.mod
View File

@ -19,7 +19,7 @@ require (
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/aws/aws-sdk-go-v2/service/acm v1.30.18
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.10
github.com/baidubce/bce-sdk-go v0.9.216
github.com/baidubce/bce-sdk-go v0.9.217
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.41
github.com/go-acme/lego/v4 v4.21.0
github.com/go-resty/resty/v2 v2.16.5

2
go.sum
View File

@ -261,6 +261,8 @@ github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/baidubce/bce-sdk-go v0.9.216 h1:jRq4C1UGYcvHo6Gst2kuUzhWwJM6EqXCmhIsTKQvf4k=
github.com/baidubce/bce-sdk-go v0.9.216/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
github.com/baidubce/bce-sdk-go v0.9.217 h1:dbMeVzpr9BGItTFHB1s2KSrpz0ayJC1y366VUMmaF0k=
github.com/baidubce/bce-sdk-go v0.9.217/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=

View File

@ -10,6 +10,7 @@ import (
pAliyun "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun"
pAWSRoute53 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/aws-route53"
pAzureDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/azure-dns"
pBaiduCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/baiducloud"
pCloudflare "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudflare"
pClouDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns"
pGcore "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gcore"
@ -102,6 +103,22 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeBaiduCloud, domain.ApplyDNSProviderTypeBaiduCloudDNS:
{
access := domain.AccessConfigForBaiduCloud{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pBaiduCloud.NewChallengeProvider(&pBaiduCloud.BaiduCloudApplicantConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeCloudflare:
{
access := domain.AccessConfigForCloudflare{}

View File

@ -67,6 +67,8 @@ const (
ApplyDNSProviderTypeAWS = ApplyDNSProviderType("aws") // 兼容旧值,等同于 [ApplyDNSProviderTypeAWSRoute53]
ApplyDNSProviderTypeAWSRoute53 = ApplyDNSProviderType("aws-route53")
ApplyDNSProviderTypeAzureDNS = ApplyDNSProviderType("azure-dns")
ApplyDNSProviderTypeBaiduCloud = ApplyDNSProviderType("baiducloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeBaiduCloudDNS]
ApplyDNSProviderTypeBaiduCloudDNS = ApplyDNSProviderType("baiducloud-dns")
ApplyDNSProviderTypeCloudflare = ApplyDNSProviderType("cloudflare")
ApplyDNSProviderTypeClouDNS = ApplyDNSProviderType("cloudns")
ApplyDNSProviderTypeGcore = ApplyDNSProviderType("gcore")

View File

@ -0,0 +1,39 @@
package baiducloud
import (
"time"
"github.com/go-acme/lego/v4/challenge"
internal "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/baiducloud/internal"
)
type BaiduCloudApplicantConfig struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *BaiduCloudApplicantConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := internal.NewDefaultConfig()
providerConfig.AccessKeyID = config.AccessKeyId
providerConfig.SecretAccessKey = config.SecretAccessKey
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = config.DnsTTL
}
provider, err := internal.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@ -0,0 +1,195 @@
package lego_baiducloud
import (
"errors"
"fmt"
"strings"
"time"
bceDns "github.com/baidubce/bce-sdk-go/services/dns"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/google/uuid"
)
const (
envNamespace = "BAIDUCLOUD_"
EnvAccessKeyID = envNamespace + "ACCESS_KEY_ID"
EnvSecretAccessKey = envNamespace + "SECRET_ACCESS_KEY"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
)
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
type Config struct {
AccessKeyID string
SecretAccessKey string
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int32
HTTPTimeout time.Duration
}
type DNSProvider struct {
client *bceDns.Client
config *Config
}
func NewDefaultConfig() *Config {
return &Config{
TTL: int32(env.GetOrDefaultInt(EnvTTL, 300)),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
}
}
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvAccessKeyID, EnvSecretAccessKey)
if err != nil {
return nil, fmt.Errorf("baiducloud: %w", err)
}
config := NewDefaultConfig()
config.AccessKeyID = values[EnvAccessKeyID]
config.SecretAccessKey = values[EnvSecretAccessKey]
return NewDNSProviderConfig(config)
}
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("baiducloud: the configuration of the DNS provider is nil")
}
client, err := bceDns.NewClient(config.AccessKeyID, config.SecretAccessKey, "")
if err != nil {
return nil, err
} else {
if client.Config != nil {
client.Config.ConnectionTimeoutInMillis = int(config.HTTPTimeout.Milliseconds())
}
}
return &DNSProvider{
client: client,
config: config,
}, nil
}
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
zoneName, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("baiducloud: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zoneName)
if err != nil {
return fmt.Errorf("baiducloud: %w", err)
}
if err := d.addOrUpdateDNSRecord(domain, subDomain, info.Value); err != nil {
return fmt.Errorf("baiducloud: %w", err)
}
return nil
}
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)
subDomain := dns01.UnFqdn(fqdn)
if err := d.removeDNSRecord(domain, subDomain, value); err != nil {
return fmt.Errorf("baiducloud: %w", err)
}
return nil
}
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
func (d *DNSProvider) getDNSRecord(domain, subDomain string) (*bceDns.Record, error) {
pageMarker := ""
pageSize := 1000
for {
request := &bceDns.ListRecordRequest{}
request.Rr = domain
request.Marker = pageMarker
request.MaxKeys = pageSize
response, err := d.client.ListRecord(domain, request)
if err != nil {
return nil, err
}
for _, record := range response.Records {
if record.Type == "TXT" && record.Rr == subDomain {
return &record, nil
}
}
if len(response.Records) < pageSize {
break
}
pageMarker = response.NextMarker
}
return nil, nil
}
func (d *DNSProvider) addOrUpdateDNSRecord(domain, subDomain, value string) error {
record, err := d.getDNSRecord(domain, subDomain)
if err != nil {
return err
}
if record == nil {
request := &bceDns.CreateRecordRequest{
Type: "TXT",
Rr: subDomain,
Value: value,
Ttl: &d.config.TTL,
}
err := d.client.CreateRecord(domain, request, d.generateClientToken())
return err
} else {
request := &bceDns.UpdateRecordRequest{
Type: "TXT",
Rr: subDomain,
Value: value,
Ttl: &d.config.TTL,
}
err := d.client.UpdateRecord(domain, record.Id, request, d.generateClientToken())
return err
}
}
func (d *DNSProvider) removeDNSRecord(domain, subDomain, value string) error {
record, err := d.getDNSRecord(domain, subDomain)
if err != nil {
return err
}
if record == nil {
return nil
}
err = d.client.DeleteRecord(domain, record.Id, d.generateClientToken())
return err
}
func (d *DNSProvider) generateClientToken() string {
return strings.ReplaceAll(uuid.New().String(), "-", "")
}

View File

@ -207,8 +207,3 @@ func Populate(dict map[string]any, output any) error {
return decoder.Decode(dict)
}
// Deprecated: Use [Populate] instead.
func Decode(dict map[string]any, output any) error {
return Populate(dict, output)
}

View File

@ -29,6 +29,11 @@ func main() {
var flagDir string
flag.StringVar(&flagHttp, "http", "127.0.0.1:8090", "HTTP server address")
flag.StringVar(&flagDir, "dir", "/pb_data/database", "Pocketbase data directory")
if len(os.Args) < 2 {
slog.Error("[CERTIMATE] missing exec args")
os.Exit(1)
return
}
_ = flag.CommandLine.Parse(os.Args[2:]) // skip the first two arguments: "main.go serve"
migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{

View File

@ -67,11 +67,11 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.KUBERNETES, "provider.kubernetes", "/imgs/providers/kubernetes.svg", [ACCESS_USAGES.DEPLOY]],
[ACCESS_PROVIDERS.ALIYUN, "provider.aliyun", "/imgs/providers/aliyun.svg", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
[ACCESS_PROVIDERS.TENCENTCLOUD, "provider.tencentcloud", "/imgs/providers/tencentcloud.svg", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
[ACCESS_PROVIDERS.BAIDUCLOUD, "provider.baiducloud", "/imgs/providers/baiducloud.svg", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
[ACCESS_PROVIDERS.HUAWEICLOUD, "provider.huaweicloud", "/imgs/providers/huaweicloud.svg", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
[ACCESS_PROVIDERS.VOLCENGINE, "provider.volcengine", "/imgs/providers/volcengine.svg", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
[ACCESS_PROVIDERS.AWS, "provider.aws", "/imgs/providers/aws.svg", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
[ACCESS_PROVIDERS.GCORE, "provider.gcore", "/imgs/providers/gcore.png", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
[ACCESS_PROVIDERS.BAIDUCLOUD, "provider.baiducloud", "/imgs/providers/baiducloud.svg", [ACCESS_USAGES.DEPLOY]],
[ACCESS_PROVIDERS.QINIU, "provider.qiniu", "/imgs/providers/qiniu.svg", [ACCESS_USAGES.DEPLOY]],
[ACCESS_PROVIDERS.BAISHAN, "provider.baishan", "/imgs/providers/baishan.png", [ACCESS_USAGES.DEPLOY]],
[ACCESS_PROVIDERS.DOGECLOUD, "provider.dogecloud", "/imgs/providers/dogecloud.png", [ACCESS_USAGES.DEPLOY]],
@ -118,6 +118,8 @@ export const APPLY_DNS_PROVIDERS = Object.freeze({
AWS: `${ACCESS_PROVIDERS.AWS}`, // 兼容旧值,等同于 `AWS_ROUTE53`
AWS_ROUTE53: `${ACCESS_PROVIDERS.AWS}-route53`,
AZURE_DNS: `${ACCESS_PROVIDERS.AZURE}-dns`,
BAIDUCLOUD: `${ACCESS_PROVIDERS.BAIDUCLOUD}`, // 兼容旧值,等同于 `BAIDUCLOUD_DNS`
BAIDUCLOUD_DNS: `${ACCESS_PROVIDERS.BAIDUCLOUD}-dns`,
CLOUDFLARE: `${ACCESS_PROVIDERS.CLOUDFLARE}`,
CLOUDNS: `${ACCESS_PROVIDERS.CLOUDNS}`,
GCORE: `${ACCESS_PROVIDERS.GCORE}`,
@ -154,6 +156,7 @@ export const applyDNSProvidersMap: Map<ApplyDNSProvider["type"] | string, ApplyD
[
[APPLY_DNS_PROVIDERS.ALIYUN_DNS, "provider.aliyun.dns"],
[APPLY_DNS_PROVIDERS.TENCENTCLOUD_DNS, "provider.tencentcloud.dns"],
[APPLY_DNS_PROVIDERS.BAIDUCLOUD_DNS, "provider.baiducloud.dns"],
[APPLY_DNS_PROVIDERS.HUAWEICLOUD_DNS, "provider.huaweicloud.dns"],
[APPLY_DNS_PROVIDERS.VOLCENGINE_DNS, "provider.volcengine.dns"],
[APPLY_DNS_PROVIDERS.AWS_ROUTE53, "provider.aws.route53"],

View File

@ -22,6 +22,7 @@
"provider.azure.dns": "Azure - DNS",
"provider.baiducloud": "Baidu Cloud",
"provider.baiducloud.cdn": "Baidu Cloud - CDN (Content Delivery Network)",
"provider.baiducloud.dns": "Baidu Cloud - DNS (Domain Name Service)",
"provider.baishan": "Baishan",
"provider.baishan.cdn": "Baishan - CDN (Content Delivery Network)",
"provider.baotapanel": "BaoTa Panel",

View File

@ -22,6 +22,7 @@
"provider.azure.dns": "Azure - DNS",
"provider.baiducloud": "百度智能云",
"provider.baiducloud.cdn": "百度智能云 - 内容分发网络 CDN",
"provider.baiducloud.dns": "百度智能云 - 智能云解析 DNS",
"provider.baishan": "白山云",
"provider.baishan.cdn": "白山云 - 内容分发网络 CDN",
"provider.baotapanel": "宝塔面板",