feat(provider): no need zoneId in acme dns-01 provider tencentcloud edgeone

This commit is contained in:
Fu Diwei 2025-11-15 20:23:05 +08:00
parent 99961aab46
commit 392fcd1972
14 changed files with 24 additions and 286 deletions

5
go.mod
View File

@ -45,6 +45,7 @@ require (
github.com/go-cmd/cmd v1.4.3
github.com/go-resty/resty/v2 v2.16.5
github.com/go-viper/mapstructure/v2 v2.4.0
github.com/google/go-querystring v1.1.0
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.175
github.com/jdcloud-api/jdcloud-sdk-go v1.64.0
github.com/kong/go-kong v0.69.0
@ -80,6 +81,7 @@ require (
golang.org/x/crypto v0.43.0
golang.org/x/sync v0.18.0
golang.org/x/sys v0.38.0
golang.org/x/text v0.30.0
k8s.io/api v0.34.1
k8s.io/apimachinery v0.34.1
k8s.io/client-go v0.34.1
@ -198,9 +200,9 @@ require (
github.com/fatih/color v1.18.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/ganigeorgiev/fexpr v0.5.0 // indirect
github.com/go-acme/tencentedgdeone v1.1.48 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
@ -221,7 +223,6 @@ require (
golang.org/x/net v0.46.0 // indirect
golang.org/x/oauth2 v0.32.0 // indirect
golang.org/x/term v0.36.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.38.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect

3
go.sum
View File

@ -340,6 +340,8 @@ github.com/go-acme/lego/v4 v4.28.0 h1:URKsCcybo7SjqqZckeBcDN9Vl29/bKS///75tcNkMH
github.com/go-acme/lego/v4 v4.28.0/go.mod h1:bzjilr03IgbaOwlH396hq5W56Bi0/uoRwW/JM8hP7m4=
github.com/go-acme/tencentclouddnspod v1.1.10 h1:ERVJ4mc3cT4Nb3+n6H/c1AwZnChGBqLoymE0NVYscKI=
github.com/go-acme/tencentclouddnspod v1.1.10/go.mod h1:Bo/0YQJ/99FM+44HmCQkByuptX1tJsJ9V14MGV/2Qco=
github.com/go-acme/tencentedgdeone v1.1.48 h1:WLyLBsRVhSLFmtbEFXk0naLODSQn7X6J0Fc/qR8xVUk=
github.com/go-acme/tencentedgdeone v1.1.48/go.mod h1:mu6tA+bPhlSd+CKUfzRikE0mfxmTlBI6dVTn9LY9dRI=
github.com/go-cmd/cmd v1.4.3 h1:6y3G+3UqPerXvPcXvj+5QNPHT02BUw7p6PsqRxLNA7Y=
github.com/go-cmd/cmd v1.4.3/go.mod h1:u3hxg/ry+D5kwh8WvUkHLAMe2zQCaXd00t35WfQaOFk=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@ -837,6 +839,7 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.10/go.mod h
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.13/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.28/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.36/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.49/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.52 h1:GtExKpiqbmmOq9ojeBYR6M1vgVL27s14GIDkjmgAX8A=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.52/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=

View File

@ -20,7 +20,6 @@ func init() {
provider, err := teo.NewChallengeProvider(&teo.ChallengeProviderConfig{
SecretId: credentials.SecretId,
SecretKey: credentials.SecretKey,
ZoneId: xmaps.GetString(options.ProviderExtendedConfig, "zoneId"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})

View File

@ -51,7 +51,7 @@ type DNSProvider struct {
func NewDefaultConfig() *Config {
return &Config{
TTL: env.GetOrDefaultInt(EnvTTL, 300),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
}

View File

@ -46,7 +46,7 @@ type DNSProvider struct {
func NewDefaultConfig() *Config {
return &Config{
TTL: env.GetOrDefaultInt(EnvTTL, 300),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
}

View File

@ -1,64 +0,0 @@
package internal
import (
"context"
"errors"
"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"
)
// This is a partial copy of https://github.com/TencentCloud/tencentcloud-sdk-go/blob/master/tencentcloud/teo/v20220901/client.go
// to lightweight the vendor packages in the built binary.
type TeoClient struct {
common.Client
}
func NewTeoClient(credential common.CredentialIface, region string, clientProfile *profile.ClientProfile) (client *TeoClient, err error) {
client = &TeoClient{}
client.Init(region).
WithCredential(credential).
WithProfile(clientProfile)
return
}
func (c *TeoClient) CreateDnsRecord(request *tcteo.CreateDnsRecordRequest) (response *tcteo.CreateDnsRecordResponse, err error) {
return c.CreateDnsRecordWithContext(context.Background(), request)
}
func (c *TeoClient) CreateDnsRecordWithContext(ctx context.Context, request *tcteo.CreateDnsRecordRequest) (response *tcteo.CreateDnsRecordResponse, err error) {
if request == nil {
request = tcteo.NewCreateDnsRecordRequest()
}
c.InitBaseRequest(&request.BaseRequest, "teo", tcteo.APIVersion, "CreateDnsRecord")
if c.GetCredential() == nil {
return nil, errors.New("CreateDnsRecord require credential")
}
request.SetContext(ctx)
response = tcteo.NewCreateDnsRecordResponse()
err = c.Send(request, response)
return
}
func (c *TeoClient) DeleteDnsRecords(request *tcteo.DeleteDnsRecordsRequest) (response *tcteo.DeleteDnsRecordsResponse, err error) {
return c.DeleteDnsRecordsWithContext(context.Background(), request)
}
func (c *TeoClient) DeleteDnsRecordsWithContext(ctx context.Context, request *tcteo.DeleteDnsRecordsRequest) (response *tcteo.DeleteDnsRecordsResponse, err error) {
if request == nil {
request = tcteo.NewDeleteDnsRecordsRequest()
}
c.InitBaseRequest(&request.BaseRequest, "teo", tcteo.APIVersion, "DeleteDnsRecords")
if c.GetCredential() == nil {
return nil, errors.New("DeleteDnsRecords require credential")
}
request.SetContext(ctx)
response = tcteo.NewDeleteDnsRecordsResponse()
err = c.Send(request, response)
return
}

View File

@ -1,148 +0,0 @@
package internal
import (
"errors"
"fmt"
"math"
"sync"
"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"
"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 (
envNamespace = "TENCENTCLOUDEO_"
EnvSecretID = envNamespace + "SECRET_ID"
EnvSecretKey = envNamespace + "SECRET_KEY"
EnvZoneID = envNamespace + "ZONE_ID"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
)
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
type Config struct {
SecretID string
SecretKey string
ZoneID string
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int
HTTPTimeout time.Duration
}
type DNSProvider struct {
client *TeoClient
config *Config
recordIDs map[string]*string
recordIDsMu sync.Mutex
}
func NewDefaultConfig() *Config {
return &Config{
TTL: 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(EnvSecretID, EnvSecretKey, EnvZoneID)
if err != nil {
return nil, fmt.Errorf("tencentcloud-eo: %w", err)
}
config := NewDefaultConfig()
config.SecretID = values[EnvSecretID]
config.SecretKey = values[EnvSecretKey]
config.ZoneID = values[EnvSecretKey]
return NewDNSProviderConfig(config)
}
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("tencentcloud-eo: the configuration of the DNS provider is nil")
}
credential := common.NewCredential(config.SecretID, config.SecretKey)
cpf := profile.NewClientProfile()
cpf.HttpProfile.ReqTimeout = int(math.Round(config.HTTPTimeout.Seconds()))
client, err := NewTeoClient(credential, "", cpf)
if err != nil {
return nil, err
}
return &DNSProvider{
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)
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)
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
}
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}

View File

@ -4,14 +4,14 @@ import (
"errors"
"time"
"github.com/go-acme/lego/v4/providers/dns/edgeone"
"github.com/certimate-go/certimate/pkg/core"
"github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/tencentcloud-eo/internal"
)
type ChallengeProviderConfig struct {
SecretId string `json:"secretId"`
SecretKey string `json:"secretKey"`
ZoneId string `json:"zoneId"`
DnsPropagationTimeout int `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int `json:"dnsTTL,omitempty"`
}
@ -21,12 +21,9 @@ 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 := edgeone.NewDefaultConfig()
providerConfig.SecretID = config.SecretId
providerConfig.SecretKey = config.SecretKey
providerConfig.ZoneID = config.ZoneId
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
@ -34,7 +31,7 @@ func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger,
providerConfig.TTL = config.DnsTTL
}
provider, err := internal.NewDNSProviderConfig(providerConfig)
provider, err := edgeone.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}

View File

@ -8,7 +8,6 @@ import BizApplyNodeConfigFieldsProviderHuaweiCloudDNS from "./BizApplyNodeConfig
import BizApplyNodeConfigFieldsProviderJDCloudDNS from "./BizApplyNodeConfigFieldsProviderJDCloudDNS";
import BizApplyNodeConfigFieldsProviderLocal from "./BizApplyNodeConfigFieldsProviderLocal";
import BizApplyNodeConfigFieldsProviderSSH from "./BizApplyNodeConfigFieldsProviderSSH";
import BizApplyNodeConfigFieldsProviderTencentCloudEO from "./BizApplyNodeConfigFieldsProviderTencentCloudEO";
const acmeDns01ProviderComponentMap: Partial<Record<ACMEDns01ProviderType, React.ComponentType<any>>> = {
/*
@ -22,7 +21,6 @@ const acmeDns01ProviderComponentMap: Partial<Record<ACMEDns01ProviderType, React
[ACME_DNS01_PROVIDERS.HUAWEICLOUD_DNS]: BizApplyNodeConfigFieldsProviderHuaweiCloudDNS,
[ACME_DNS01_PROVIDERS.JDCLOUD]: BizApplyNodeConfigFieldsProviderJDCloudDNS,
[ACME_DNS01_PROVIDERS.JDCLOUD_DNS]: BizApplyNodeConfigFieldsProviderJDCloudDNS,
[ACME_DNS01_PROVIDERS.TENCENTCLOUD_EO]: BizApplyNodeConfigFieldsProviderTencentCloudEO,
};
const acmeHttp01ProviderComponentMap: Partial<Record<ACMEHttp01ProviderType, React.ComponentType<any>>> = {

View File

@ -40,7 +40,12 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
const { t } = i18n;
return z.object({
webRootPath: z.string().nonempty(t("workflow_node.apply.form.local_webroot_path.placeholder")),
webRootPath: z
.string()
.nonempty(t("workflow_node.apply.form.local_webroot_path.placeholder"))
.refine((v) => !!v && (v.endsWith("/") || v.endsWith("\\")), {
error: t("workflow_node.apply.form.local_webroot_path.placeholder"),
}),
});
};

View File

@ -40,7 +40,12 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
const { t } = i18n;
return z.object({
webRootPath: z.string().nonempty(t("workflow_node.apply.form.ssh_webroot_path.placeholder")),
webRootPath: z
.string()
.nonempty(t("workflow_node.apply.form.ssh_webroot_path.placeholder"))
.refine((v) => !!v && (v.endsWith("/") || v.endsWith("\\")), {
error: t("workflow_node.apply.form.ssh_webroot_path.placeholder"),
}),
});
};

View File

@ -1,52 +0,0 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { useFormNestedFieldsContext } from "./_context";
const BizApplyNodeConfigFieldsProviderTencentCloudEO = () => {
const { i18n, t } = useTranslation();
const { parentNamePath } = useFormNestedFieldsContext();
const formSchema = z.object({
[parentNamePath]: getSchema({ i18n }),
});
const formRule = createSchemaFieldRule(formSchema);
const initialValues = getInitialValues();
return (
<>
<Form.Item
name={[parentNamePath, "zoneId"]}
initialValue={initialValues.zoneId}
label={t("workflow_node.apply.form.tencentcloud_eo_zone_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.apply.form.tencentcloud_eo_zone_id.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.apply.form.tencentcloud_eo_zone_id.placeholder")} />
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
zoneId: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
const { t } = i18n;
return z.object({
zoneId: z.string().nonempty(t("workflow_node.apply.form.tencentcloud_eo_zone_id.placeholder")),
});
};
const _default = Object.assign(BizApplyNodeConfigFieldsProviderTencentCloudEO, {
getInitialValues,
getSchema,
});
export default _default;

View File

@ -75,9 +75,6 @@
"workflow_node.apply.form.ssh_webroot_path.label": "Web root path",
"workflow_node.apply.form.ssh_webroot_path.placeholder": "Please enter web root path",
"workflow_node.apply.form.ssh_webroot_path.tooltip": "It's the main directory where the website's files are stored on the server.",
"workflow_node.apply.form.tencentcloud_eo_zone_id.label": "Tencent Cloud EdgeOne zone ID",
"workflow_node.apply.form.tencentcloud_eo_zone_id.placeholder": "Please enter Tencent Cloud EdgeOne zone ID",
"workflow_node.apply.form.tencentcloud_eo_zone_id.tooltip": "For more information, see <a href=\"https://console.tencentcloud.com/edgeone\" target=\"_blank\">https://console.tencentcloud.com/edgeone</a>",
"workflow_node.apply.form.key_source.label": "Key source",
"workflow_node.apply.form.key_source.placeholder": "Please select key source",
"workflow_node.apply.form.key_source.option.auto.label": "Auto",

View File

@ -75,9 +75,6 @@
"workflow_node.apply.form.ssh_webroot_path.label": "网站根目录",
"workflow_node.apply.form.ssh_webroot_path.placeholder": "请输入网站根目录",
"workflow_node.apply.form.ssh_webroot_path.tooltip": "即服务器上存储网站文件的主文件夹。",
"workflow_node.apply.form.tencentcloud_eo_zone_id.label": "腾讯云 EdgeOne 站点 ID",
"workflow_node.apply.form.tencentcloud_eo_zone_id.placeholder": "请输入腾讯云 EdgeOne 站点 ID",
"workflow_node.apply.form.tencentcloud_eo_zone_id.tooltip": "这是什么?请参阅 <a href=\"https://console.cloud.tencent.com/edgeone\" target=\"_blank\">https://console.cloud.tencent.com/edgeone</a>",
"workflow_node.apply.form.key_source.label": "私钥来源",
"workflow_node.apply.form.key_source.placeholder": "请选择私钥来源",
"workflow_node.apply.form.key_source.option.auto.label": "随机生成",