From f4856a4fcdedda7200db60e140921a0293794672 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 5 Nov 2025 23:04:52 +0800 Subject: [PATCH] feat(provider): new acme dns-01 provider: arvancloud --- .../certapply/applicators/sp_arvancloud.go | 29 +++++++++++ internal/domain/access.go | 4 ++ internal/domain/provider.go | 10 ++++ .../providers/arvancloud/arvancloud.go | 38 ++++++++++++++ ui/public/imgs/providers/arvancloud.svg | 1 + .../forms/AccessConfigFieldsProvider.tsx | 2 + .../AccessConfigFieldsProviderArvanCloud.tsx | 52 +++++++++++++++++++ ui/src/domain/provider.ts | 12 +++++ ui/src/i18n/locales/en/nls.access.json | 3 ++ ui/src/i18n/locales/en/nls.provider.json | 7 ++- ui/src/i18n/locales/zh/nls.access.json | 3 ++ ui/src/i18n/locales/zh/nls.provider.json | 5 ++ 12 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 internal/certapply/applicators/sp_arvancloud.go create mode 100644 pkg/core/ssl-applicator/acme-dns01/providers/arvancloud/arvancloud.go create mode 100644 ui/public/imgs/providers/arvancloud.svg create mode 100644 ui/src/components/access/forms/AccessConfigFieldsProviderArvanCloud.tsx diff --git a/internal/certapply/applicators/sp_arvancloud.go b/internal/certapply/applicators/sp_arvancloud.go new file mode 100644 index 000000000..c82f2f05b --- /dev/null +++ b/internal/certapply/applicators/sp_arvancloud.go @@ -0,0 +1,29 @@ +package applicators + +import ( + "fmt" + + "github.com/go-acme/lego/v4/challenge" + + "github.com/certimate-go/certimate/internal/domain" + "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/arvancloud" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" +) + +func init() { + if err := ACMEDns01Registries.Register(domain.ACMEDns01ProviderTypeArvanCloud, func(options *ProviderFactoryOptions) (challenge.Provider, error) { + credentials := domain.AccessConfigForArvanCloud{} + if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + provider, err := arvancloud.NewChallengeProvider(&arvancloud.ChallengeProviderConfig{ + ApiKey: credentials.ApiKey, + DnsPropagationTimeout: options.DnsPropagationTimeout, + DnsTTL: options.DnsTTL, + }) + return provider, err + }); err != nil { + panic(err) + } +} diff --git a/internal/domain/access.go b/internal/domain/access.go index 59e28e6db..0a3cb9985 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -60,6 +60,10 @@ type AccessConfigForAPISIX struct { AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } +type AccessConfigForArvanCloud struct { + ApiKey string `json:"apiKey"` +} + type AccessConfigForAWS struct { AccessKeyId string `json:"accessKeyId"` SecretAccessKey string `json:"secretAccessKey"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 4980be0c8..c8e215ab5 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -17,6 +17,7 @@ const ( AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai(预留) AccessProviderTypeAliyun = AccessProviderType("aliyun") AccessProviderTypeAPISIX = AccessProviderType("apisix") + AccessProviderTypeArvanCloud = AccessProviderType("arvancloud") AccessProviderTypeAWS = AccessProviderType("aws") AccessProviderTypeAzure = AccessProviderType("azure") AccessProviderTypeBaiduCloud = AccessProviderType("baiducloud") @@ -40,8 +41,10 @@ const ( AccessProviderTypeDingTalkBot = AccessProviderType("dingtalkbot") AccessProviderTypeDiscordBot = AccessProviderType("discordbot") AccessProviderTypeDNSLA = AccessProviderType("dnsla") + AccessProviderTypeDNSMadeEasy = AccessProviderType("dnsmadeeasy") // DNS Made Easy(预留) AccessProviderTypeDogeCloud = AccessProviderType("dogecloud") AccessProviderTypeDuckDNS = AccessProviderType("duckdns") + AccessProviderTypeDynu = AccessProviderType("dynu") // Dynu(预留) AccessProviderTypeDynv6 = AccessProviderType("dynv6") AccessProviderTypeEmail = AccessProviderType("email") AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留) @@ -57,6 +60,7 @@ const ( AccessProviderTypeHostingde = AccessProviderType("hostingde") AccessProviderTypeHostinger = AccessProviderType("hostinger") AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud") + AccessProviderTypeInfomaniak = AccessProviderType("infomaniak") // Infomaniak(预留) AccessProviderTypeIONOS = AccessProviderType("ionos") AccessProviderTypeJDCloud = AccessProviderType("jdcloud") AccessProviderTypeKong = AccessProviderType("kong") @@ -75,6 +79,7 @@ const ( AccessProviderTypeNetcup = AccessProviderType("netcup") AccessProviderTypeNetlify = AccessProviderType("netlify") AccessProviderTypeNS1 = AccessProviderType("ns1") + AccessProviderTypeOVHcloud = AccessProviderType("ovhcloud") // OVHcloud(预留) AccessProviderTypePorkbun = AccessProviderType("porkbun") AccessProviderTypePowerDNS = AccessProviderType("powerdns") AccessProviderTypeProxmoxVE = AccessProviderType("proxmoxve") @@ -141,6 +146,7 @@ const ( ACMEDns01ProviderTypeAliyun = ACMEDns01ProviderType(AccessProviderTypeAliyun) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAliyunDNS] ACMEDns01ProviderTypeAliyunDNS = ACMEDns01ProviderType(AccessProviderTypeAliyun + "-dns") ACMEDns01ProviderTypeAliyunESA = ACMEDns01ProviderType(AccessProviderTypeAliyun + "-esa") + ACMEDns01ProviderTypeArvanCloud = ACMEDns01ProviderType(AccessProviderTypeArvanCloud) ACMEDns01ProviderTypeAWS = ACMEDns01ProviderType(AccessProviderTypeAWS) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAWSRoute53] ACMEDns01ProviderTypeAWSRoute53 = ACMEDns01ProviderType(AccessProviderTypeAWS + "-route53") ACMEDns01ProviderTypeAzure = ACMEDns01ProviderType(AccessProviderTypeAzure) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAzure] @@ -159,7 +165,9 @@ const ( ACMEDns01ProviderTypeDeSEC = ACMEDns01ProviderType(AccessProviderTypeDeSEC) ACMEDns01ProviderTypeDigitalOcean = ACMEDns01ProviderType(AccessProviderTypeDigitalOcean) ACMEDns01ProviderTypeDNSLA = ACMEDns01ProviderType(AccessProviderTypeDNSLA) + ACMEDns01ProviderTypeDNSMadeEasy = ACMEDns01ProviderType(AccessProviderTypeDNSMadeEasy) ACMEDns01ProviderTypeDuckDNS = ACMEDns01ProviderType(AccessProviderTypeDuckDNS) + ACMEDns01ProviderTypeDynu = ACMEDns01ProviderType(AccessProviderTypeDynu) ACMEDns01ProviderTypeDynv6 = ACMEDns01ProviderType(AccessProviderTypeDynv6) ACMEDns01ProviderTypeGandinet = ACMEDns01ProviderType(AccessProviderTypeGandinet) ACMEDns01ProviderTypeGcore = ACMEDns01ProviderType(AccessProviderTypeGcore) @@ -170,6 +178,7 @@ const ( ACMEDns01ProviderTypeHostinger = ACMEDns01ProviderType(AccessProviderTypeHostinger) ACMEDns01ProviderTypeHuaweiCloud = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeHuaweiCloudDNS] ACMEDns01ProviderTypeHuaweiCloudDNS = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud + "-dns") + ACMEDns01ProviderTypeInfomaniak = ACMEDns01ProviderType(AccessProviderTypeInfomaniak) ACMEDns01ProviderTypeIONOS = ACMEDns01ProviderType(AccessProviderTypeIONOS) ACMEDns01ProviderTypeJDCloud = ACMEDns01ProviderType(AccessProviderTypeJDCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeJDCloudDNS] ACMEDns01ProviderTypeJDCloudDNS = ACMEDns01ProviderType(AccessProviderTypeJDCloud + "-dns") @@ -180,6 +189,7 @@ const ( ACMEDns01ProviderTypeNetcup = ACMEDns01ProviderType(AccessProviderTypeNetcup) ACMEDns01ProviderTypeNetlify = ACMEDns01ProviderType(AccessProviderTypeNetlify) ACMEDns01ProviderTypeNS1 = ACMEDns01ProviderType(AccessProviderTypeNS1) + ACMEDns01ProviderTypeOVHcloud = ACMEDns01ProviderType(AccessProviderTypeOVHcloud) ACMEDns01ProviderTypePorkbun = ACMEDns01ProviderType(AccessProviderTypePorkbun) ACMEDns01ProviderTypePowerDNS = ACMEDns01ProviderType(AccessProviderTypePowerDNS) ACMEDns01ProviderTypeRainYun = ACMEDns01ProviderType(AccessProviderTypeRainYun) diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/arvancloud/arvancloud.go b/pkg/core/ssl-applicator/acme-dns01/providers/arvancloud/arvancloud.go new file mode 100644 index 000000000..60658885b --- /dev/null +++ b/pkg/core/ssl-applicator/acme-dns01/providers/arvancloud/arvancloud.go @@ -0,0 +1,38 @@ +package arvancloud + +import ( + "errors" + "time" + + "github.com/go-acme/lego/v4/providers/dns/arvancloud" + + "github.com/certimate-go/certimate/pkg/core" +) + +type ChallengeProviderConfig struct { + ApiKey string `json:"apiKey"` + DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` + DnsTTL int32 `json:"dnsTTL,omitempty"` +} + +func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger, error) { + if config == nil { + return nil, errors.New("the configuration of the acme challenge provider is nil") + } + + providerConfig := arvancloud.NewDefaultConfig() + providerConfig.APIKey = config.ApiKey + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = int(config.DnsTTL) + } + + provider, err := arvancloud.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/ui/public/imgs/providers/arvancloud.svg b/ui/public/imgs/providers/arvancloud.svg new file mode 100644 index 000000000..709bea279 --- /dev/null +++ b/ui/public/imgs/providers/arvancloud.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx b/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx index fa5b18009..7afcb7fb1 100644 --- a/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx +++ b/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx @@ -9,6 +9,7 @@ import AccessConfigFieldsProviderACMEHttpReq from "./AccessConfigFieldsProviderA import AccessConfigFieldsProviderActalisSSL from "./AccessConfigFieldsProviderActalisSSL"; import AccessConfigFieldsProviderAliyun from "./AccessConfigFieldsProviderAliyun"; import AccessConfigFieldsProviderAPISIX from "./AccessConfigFieldsProviderAPISIX"; +import AccessConfigFieldsProviderArvanCloud from "./AccessConfigFieldsProviderArvanCloud"; import AccessConfigFieldsProviderAWS from "./AccessConfigFieldsProviderAWS"; import AccessConfigFieldsProviderAzure from "./AccessConfigFieldsProviderAzure"; import AccessConfigFieldsProviderBaiduCloud from "./AccessConfigFieldsProviderBaiduCloud"; @@ -101,6 +102,7 @@ const providerComponentMap: Partial { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + apiKey: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + apiKey: z.string().nonempty(t("access.form.arvancloud_api_key.placeholder")), + }); +}; + +const _default = Object.assign(AccessConfigFormFieldsProviderArvanCloud, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 1e10f03ec..5a3a38623 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -22,6 +22,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ ACTALISSSL: "actalisssl", ALIYUN: "aliyun", APISIX: "apisix", + ARVANCLOUD: "arvancloud", AWS: "aws", AZURE: "azure", BAIDUCLOUD: "baiducloud", @@ -44,8 +45,10 @@ export const ACCESS_PROVIDERS = Object.freeze({ DINGTALKBOT: "dingtalkbot", DISCORDBOT: "discordbot", DNSLA: "dnsla", + DNSMADEEASY: "dnsmadeeasy", DOGECLOUD: "dogecloud", DUCKDNS: "duckdns", + DYNU: "dynu", DYNV6: "dynv6", EMAIL: "email", FLEXCDN: "flexcdn", @@ -60,6 +63,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ HOSTINGDE: "hostingDE", HOSTINGER: "hostinger", HUAWEICLOUD: "huaweicloud", + INFOMANIAK: "infomaniak", IONOS: "ionos", JDCLOUD: "jdcloud", KONG: "kong", @@ -77,6 +81,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ NETCUP: "netcup", NETLIFY: "netlify", NS1: "ns1", + OVHCLOUD: "ovhcloud", PORKBUN: "porkbun", POWERDNS: "powerdns", PROXMOXVE: "proxmoxve", @@ -170,6 +175,7 @@ export const accessProvidersMap: Maphttps://apisix.apache.org/docs/apisix/admin-api/", + "access.form.arvancloud_api_key.label": "ArvanCloud API key", + "access.form.arvancloud_api_key.placeholder": "Please enter ArvanCloud API key", + "access.form.arvancloud_api_key.tooltip": "For more information, see https://docs.arvancloud.ir/en/developer-tools/api/api-key", "access.form.aws_access_key_id.label": "AWS AccessKeyID", "access.form.aws_access_key_id.placeholder": "Please enter AWS AccessKeyID", "access.form.aws_access_key_id.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/IAM/latest/UserGuide/id_credentials_access-keys.html", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 06d0270ec..9207ee4dc 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -27,6 +27,7 @@ "provider.akamai": "Akamai", "provider.akamai.cdn": "Akamai - CDN (Content Delivery Network)", "provider.apisix": "Apache APISIX", + "provider.arvancloud": "ArvanCloud", "provider.aws": "AWS", "provider.aws.acm": "AWS - ACM (Amazon Certificate Manager)", "provider.aws.cloudfront": "AWS - CloudFront", @@ -78,9 +79,11 @@ "provider.dingtalkbot": "DingTalk Bot", "provider.discordbot": "Discord Bot", "provider.dnsla": "DNS.LA", - "provider.dogecloud": "Doge Cloud", + "provider.dnsmadeeasy": "DNS Made Easy", "provider.dogecloud.cdn": "Doge Cloud - CDN (Content Delivery Network)", + "provider.dogecloud": "Doge Cloud", "provider.duckdns": "Duck DNS", + "provider.dynu": "Dynu", "provider.dynv6": "dynv6", "provider.email": "Email (SMTP)", "provider.fastly": "Fastly", @@ -103,6 +106,7 @@ "provider.huaweicloud.obs": "Huawei Cloud - OBS (Object Storage Service)", "provider.huaweicloud.scm_upload": "Huawei Cloud - Upload to SCM (SSL Certificate Manager)", "provider.huaweicloud.waf": "Huawei Cloud - WAF (Web Application Firewall)", + "provider.infomaniak": "Infomaniak", "provider.ionos": "IONOS", "provider.jdcloud": "JD Cloud", "provider.jdcloud.alb": "JD Cloud - ALB (Application Load Balancer)", @@ -126,6 +130,7 @@ "provider.netcup": "netcup", "provider.netlify": "Netlify", "provider.netlify.site": "Netlify - Site", + "provider.ovhcloud": "OVHcloud", "provider.ns1": "NS1 (IBM NS1 Connect)", "provider.porkbun": "Porkbun", "provider.powerdns": "PowerDNS", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 38bef4dae..ca95e968b 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -97,6 +97,9 @@ "access.form.apisix_api_key.label": "APISIX Admin API Key", "access.form.apisix_api_key.placeholder": "请输入 APISIX Admin API Key", "access.form.apisix_api_key.tooltip": "这是什么?请参阅 https://apisix.apache.org/zh/docs/apisix/admin-api/", + "access.form.arvancloud_api_key.label": "ArvanCloud API 密钥", + "access.form.arvancloud_api_key.placeholder": "请输入 ArvanCloud API 密钥", + "access.form.arvancloud_api_key.tooltip": "这是什么?请参阅 https://docs.arvancloud.ir/en/developer-tools/api/api-key", "access.form.aws_access_key_id.label": "AWS AccessKeyID", "access.form.aws_access_key_id.placeholder": "请输入 AWS AccessKeyID", "access.form.aws_access_key_id.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/id_credentials_access-keys.html", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 55d5af7b6..cafb7a867 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -27,6 +27,7 @@ "provider.akamai": "Akamai", "provider.akamai.cdn": "Akamai - 内容分发网络 CDN", "provider.apisix": "Apache APISIX", + "provider.arvancloud": "ArvanCloud", "provider.aws": "AWS", "provider.aws.acm": "AWS - ACM (Amazon Certificate Manager)", "provider.aws.cloudfront": "AWS - CloudFront", @@ -78,9 +79,11 @@ "provider.dingtalkbot": "钉钉群机器人", "provider.discordbot": "Discord 机器人", "provider.dnsla": "DNS.LA", + "provider.dnsmadeeasy": "DNS Made Easy", "provider.dogecloud": "多吉云", "provider.dogecloud.cdn": "多吉云 - 内容分发网络 CDN", "provider.duckdns": "Duck DNS", + "provider.dynu": "Dynu", "provider.dynv6": "dynv6", "provider.email": "邮件(SMTP)", "provider.fastly": "Fastly", @@ -103,6 +106,7 @@ "provider.huaweicloud.obs": "华为云 - 对象储存服务 OBS", "provider.huaweicloud.scm_upload": "华为云 - 上传到云证书管理服务 SCM", "provider.huaweicloud.waf": "华为云 - Web 应用防火墙 WAF", + "provider.infomaniak": "Infomaniak", "provider.ionos": "IONOS", "provider.jdcloud": "京东云", "provider.jdcloud.alb": "京东云 - 应用负载均衡 ALB", @@ -127,6 +131,7 @@ "provider.netlify": "Netlify", "provider.netlify.site": "Netlify - Site", "provider.ns1": "NS1 (IBM NS1 Connect)", + "provider.ovhcloud": "OVHcloud", "provider.porkbun": "Porkbun", "provider.powerdns": "PowerDNS", "provider.proxmoxve": "Proxmox VE",