feat: new acme dns-01 provider: statecloud smartdns

This commit is contained in:
Fu Diwei
2025-06-12 23:05:18 +08:00
parent fb62f1e105
commit 9c8ab98efb
23 changed files with 837 additions and 65 deletions

View File

@@ -1,7 +1,6 @@
package cmcccloud
import (
"errors"
"time"
"github.com/go-acme/lego/v4/challenge"
@@ -18,7 +17,7 @@ type ChallengeProviderConfig struct {
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
return nil, errors.New("config is nil")
panic("config is nil")
}
providerConfig := internal.NewDefaultConfig()

View File

@@ -18,8 +18,9 @@ import (
const (
envNamespace = "CMCCCLOUD_"
EnvAccessKey = envNamespace + "ACCESS_KEY"
EnvSecretKey = envNamespace + "SECRET_KEY"
EnvAccessKey = envNamespace + "ACCESS_KEY"
EnvSecretKey = envNamespace + "SECRET_KEY"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
@@ -30,13 +31,14 @@ const (
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
type Config struct {
AccessKey string
SecretKey string
ReadTimeOut int
ConnectTimeout int
AccessKey string
SecretKey string
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int32
ReadTimeOut int
ConnectTimeout int
}
type DNSProvider struct {

View File

@@ -0,0 +1,39 @@
package ctcccloud
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ctcccloud/internal"
)
type ChallengeProviderConfig struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := internal.NewDefaultConfig()
providerConfig.AccessKeyId = config.AccessKeyId
providerConfig.SecretAccessKey = config.SecretAccessKey
if config.DnsTTL != 0 {
providerConfig.TTL = int(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
}

View File

@@ -0,0 +1,203 @@
package internal
import (
"errors"
"fmt"
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
ctyundns "github.com/usual2970/certimate/internal/pkg/sdk3rd/ctyun/dns"
typeutil "github.com/usual2970/certimate/internal/pkg/utils/type"
)
const (
envNamespace = "CTYUNSMARTDNS_"
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 int
HTTPTimeout time.Duration
}
type DNSProvider struct {
client *ctyundns.Client
config *Config
}
func NewDefaultConfig() *Config {
return &Config{
TTL: env.GetOrDefaultInt(EnvTTL, 600),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
}
}
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvAccessKeyID, EnvSecretAccessKey)
if err != nil {
return nil, fmt.Errorf("ctyun: %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("ctyun: the configuration of the DNS provider is nil")
}
client, err := ctyundns.NewClient(config.AccessKeyId, config.SecretAccessKey)
if err != nil {
return nil, err
} else {
client.SetTimeout(config.HTTPTimeout)
}
return &DNSProvider{
client: client,
config: config,
}, nil
}
func (d *DNSProvider) Present(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)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("ctyun: %w", err)
}
if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil {
return fmt.Errorf("ctyun: %w", err)
}
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("ctyun: could not find zone for domain %q: %w", domain, err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("ctyun: %w", err)
}
if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); err != nil {
return fmt.Errorf("ctyun: %w", err)
}
return nil
}
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 = typeutil.ToPtr(zoneName)
request.Host = typeutil.ToPtr(subDomain)
request.Type = typeutil.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: typeutil.ToPtr(zoneName),
Host: typeutil.ToPtr(subDomain),
Type: typeutil.ToPtr("TXT"),
LineCode: typeutil.ToPtr("Default"),
Value: typeutil.ToPtr(value),
State: typeutil.ToPtr(int32(1)),
TTL: typeutil.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: typeutil.ToPtr(recordId),
Domain: typeutil.ToPtr(zoneName),
Host: typeutil.ToPtr(subDomain),
Type: typeutil.ToPtr("TXT"),
LineCode: typeutil.ToPtr("Default"),
Value: typeutil.ToPtr(value),
State: typeutil.ToPtr(int32(1)),
TTL: typeutil.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: typeutil.ToPtr(recordId),
}
_, err = d.client.DeleteRecord(request)
return err
}
}