feat: new acme dns-01 provider: constellix

This commit is contained in:
Fu Diwei 2025-06-03 15:54:13 +08:00
parent e6cf4d3e07
commit 7210f63884
13 changed files with 156 additions and 0 deletions

View File

@ -16,6 +16,7 @@ import (
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"
pCMCCCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud"
pConstellix "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/constellix"
pDeSEC "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/desec"
pDigitalOcean "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/digitalocean"
pDNSLA "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla"
@ -234,6 +235,22 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi
return applicant, err
}
case domain.ACMEDns01ProviderTypeConstellix:
{
access := domain.AccessConfigForConstellix{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pConstellix.NewChallengeProvider(&pConstellix.ChallengeProviderConfig{
ApiKey: access.ApiKey,
SecretKey: access.SecretKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ACMEDns01ProviderTypeDeSEC:
{
access := domain.AccessConfigForDeSEC{}

View File

@ -109,6 +109,11 @@ type AccessConfigForCMCCCloud struct {
AccessKeySecret string `json:"accessKeySecret"`
}
type AccessConfigForConstellix struct {
ApiKey string `json:"apiKey"`
SecretKey string `json:"secretKey"`
}
type AccessConfigForDeSEC struct {
Token string `json:"token"`
}

View File

@ -28,6 +28,7 @@ const (
AccessProviderTypeCloudflare = AccessProviderType("cloudflare")
AccessProviderTypeClouDNS = AccessProviderType("cloudns")
AccessProviderTypeCMCCCloud = AccessProviderType("cmcccloud")
AccessProviderTypeConstellix = AccessProviderType("constellix")
AccessProviderTypeCTCCCloud = AccessProviderType("ctcccloud") // 天翼云(预留)
AccessProviderTypeCUCCCloud = AccessProviderType("cucccloud") // 联通云(预留)
AccessProviderTypeDeSEC = AccessProviderType("desec")
@ -131,6 +132,7 @@ const (
ACMEDns01ProviderTypeCloudflare = ACMEDns01ProviderType(AccessProviderTypeCloudflare)
ACMEDns01ProviderTypeClouDNS = ACMEDns01ProviderType(AccessProviderTypeClouDNS)
ACMEDns01ProviderTypeCMCCCloud = ACMEDns01ProviderType(AccessProviderTypeCMCCCloud)
ACMEDns01ProviderTypeConstellix = ACMEDns01ProviderType(AccessProviderTypeConstellix)
ACMEDns01ProviderTypeDeSEC = ACMEDns01ProviderType(AccessProviderTypeDeSEC)
ACMEDns01ProviderTypeDigitalOcean = ACMEDns01ProviderType(AccessProviderTypeDigitalOcean)
ACMEDns01ProviderTypeDNSLA = ACMEDns01ProviderType(AccessProviderTypeDNSLA)

View File

@ -0,0 +1,38 @@
package cloudns
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/constellix"
)
type ChallengeProviderConfig struct {
ApiKey string `json:"apiKey"`
SecretKey string `json:"secretKey"`
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 := constellix.NewDefaultConfig()
providerConfig.APIKey = config.ApiKey
providerConfig.SecretKey = config.SecretKey
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := constellix.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@ -28,6 +28,7 @@ import AccessFormCdnflyConfig from "./AccessFormCdnflyConfig";
import AccessFormCloudflareConfig from "./AccessFormCloudflareConfig";
import AccessFormClouDNSConfig from "./AccessFormClouDNSConfig";
import AccessFormCMCCCloudConfig from "./AccessFormCMCCCloudConfig";
import AccessFormConstellixConfig from "./AccessFormConstellixConfig";
import AccessFormDeSECConfig from "./AccessFormDeSECConfig";
import AccessFormDigitalOceanConfig from "./AccessFormDigitalOceanConfig";
import AccessFormDingTalkBotConfig from "./AccessFormDingTalkBotConfig";
@ -219,6 +220,8 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
return <AccessFormClouDNSConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.CMCCCLOUD:
return <AccessFormCMCCCloudConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.CONSTELLIX:
return <AccessFormConstellixConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.DESEC:
return <AccessFormDeSECConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.DIGITALOCEAN:

View File

@ -0,0 +1,67 @@
import { useTranslation } from "react-i18next";
import { Form, type FormInstance, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type AccessConfigForConstellix } from "@/domain/access";
type AccessFormConstellixConfigFieldValues = Nullish<AccessConfigForConstellix>;
export type AccessFormConstellixConfigProps = {
form: FormInstance;
formName: string;
disabled?: boolean;
initialValues?: AccessFormConstellixConfigFieldValues;
onValuesChange?: (values: AccessFormConstellixConfigFieldValues) => void;
};
const initFormModel = (): AccessFormConstellixConfigFieldValues => {
return {
apiKey: "",
secretKey: "",
};
};
const AccessFormConstellixConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange: onValuesChange }: AccessFormConstellixConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
apiKey: z.string().trim().nonempty(t("access.form.constellix_api_key.placeholder")),
secretKey: z.string().trim().nonempty(t("access.form.constellix_secret_key.placeholder")),
});
const formRule = createSchemaFieldRule(formSchema);
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
onValuesChange?.(values);
};
return (
<Form
form={formInst}
disabled={disabled}
initialValues={initialValues ?? initFormModel()}
layout="vertical"
name={formName}
onValuesChange={handleFormChange}
>
<Form.Item
name="apiKey"
label={t("access.form.constellix_api_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.constellix_api_key.tooltip") }}></span>}
>
<Input autoComplete="new-password" placeholder={t("access.form.constellix_api_key.placeholder")} />
</Form.Item>
<Form.Item
name="secretKey"
label={t("access.form.constellix_secret_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.constellix_secret_key.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.constellix_secret_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessFormConstellixConfig;

View File

@ -23,6 +23,7 @@ export interface AccessModel extends BaseModel {
| AccessConfigForCloudflare
| AccessConfigForClouDNS
| AccessConfigForCMCCCloud
| AccessConfigForConstellix
| AccessConfigForDeSEC
| AccessConfigForDigitalOcean
| AccessConfigForDingTalkBot
@ -172,6 +173,11 @@ export type AccessConfigForCMCCCloud = {
accessKeySecret: string;
};
export type AccessConfigForConstellix = {
apiKey: string;
secretKey: string;
};
export type AccessConfigForDeSEC = {
token: string;
};

View File

@ -22,6 +22,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
CLOUDFLARE: "cloudflare",
CLOUDNS: "cloudns",
CMCCCLOUD: "cmcccloud",
CONSTELLIX: "constellix",
DESEC: "desec",
DIGITALOCEAN: "digitalocean",
DINGTALKBOT: "dingtalkbot",
@ -144,6 +145,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.CLOUDFLARE, "provider.cloudflare", "/imgs/providers/cloudflare.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.CLOUDNS, "provider.cloudns", "/imgs/providers/cloudns.png", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.CONSTELLIX, "provider.constellix", "/imgs/providers/constellix.png", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.DESEC, "provider.desec", "/imgs/providers/desec.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.DIGITALOCEAN, "provider.digitalocean", "/imgs/providers/digitalocean.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.DNSLA, "provider.dnsla", "/imgs/providers/dnsla.svg", [ACCESS_USAGES.DNS]],
@ -264,6 +266,7 @@ export const ACME_DNS01_PROVIDERS = Object.freeze({
CLOUDFLARE: `${ACCESS_PROVIDERS.CLOUDFLARE}`,
CLOUDNS: `${ACCESS_PROVIDERS.CLOUDNS}`,
CMCCCLOUD: `${ACCESS_PROVIDERS.CMCCCLOUD}`,
CONSTELLIX: `${ACCESS_PROVIDERS.CONSTELLIX}`,
DESEC: `${ACCESS_PROVIDERS.DESEC}`,
DIGITALOCEAN: `${ACCESS_PROVIDERS.DIGITALOCEAN}`,
DNSLA: `${ACCESS_PROVIDERS.DNSLA}`,
@ -323,6 +326,7 @@ export const acmeDns01ProvidersMap: Map<ACMEDns01Provider["type"] | string, ACME
[ACME_DNS01_PROVIDERS.BUNNY, "provider.bunny"],
[ACME_DNS01_PROVIDERS.CLOUDFLARE, "provider.cloudflare"],
[ACME_DNS01_PROVIDERS.CLOUDNS, "provider.cloudns"],
[ACME_DNS01_PROVIDERS.CONSTELLIX, "provider.constellix"],
[ACME_DNS01_PROVIDERS.DESEC, "provider.desec"],
[ACME_DNS01_PROVIDERS.DIGITALOCEAN, "provider.digitalocean"],
[ACME_DNS01_PROVIDERS.DNSLA, "provider.dnsla"],

View File

@ -146,6 +146,12 @@
"access.form.cmcccloud_access_key_secret.label": "CMCC ECloud AccessKeySecret",
"access.form.cmcccloud_access_key_secret.placeholder": "Please enter CMCC ECloud AccessKeySecret",
"access.form.cmcccloud_access_key_secret.tooltip": "For more information, see <a href=\"https://ecloud.10086.cn/op-help-center/doc/article/49739\" target=\"_blank\">https://ecloud.10086.cn/op-help-center/doc/article/49739</a>",
"access.form.constellix_api_key.label": "Constellix API key",
"access.form.constellix_api_key.placeholder": "Please enter Constellix API key",
"access.form.constellix_api_key.tooltip": "For more information, see <a href=\"https://support.constellix.com/hc/en-us/articles/34574197390491-How-to-Generate-an-API-Key\" target=\"_blank\">https://support.constellix.com/hc/en-us/articles/34574197390491-How-to-Generate-an-API-Key</a>",
"access.form.constellix_secret_key.label": "Constellix API secret key",
"access.form.constellix_secret_key.placeholder": "Please enter Constellix API secret key",
"access.form.constellix_secret_key.tooltip": "For more information, see <a href=\"https://support.constellix.com/hc/en-us/articles/34574197390491-How-to-Generate-an-API-Key\" target=\"_blank\">https://support.constellix.com/hc/en-us/articles/34574197390491-How-to-Generate-an-API-Key</a>",
"access.form.desec_token.label": "deSEC token",
"access.form.desec_token.placeholder": "Please enter deSEC token",
"access.form.desec_token.tooltip": "For more information, see <a href=\"https://desec.readthedocs.io/en/latest/auth/tokens.html#manage-tokens\" target=\"_blank\">https://desec.readthedocs.io/en/latest/auth/tokens.html</a>",

View File

@ -55,6 +55,7 @@
"provider.cloudflare": "Cloudflare",
"provider.cloudns": "ClouDNS",
"provider.cmcccloud": "China Mobile Cloud (ECloud)",
"provider.constellix": "Constellix",
"provider.ctcccloud": "China Telecom Cloud (State Cloud)",
"provider.cucccloud": "China Unicom Cloud",
"provider.desec": "deSEC",

View File

@ -146,6 +146,12 @@
"access.form.cmcccloud_access_key_secret.label": "移动云 AccessKeySecret",
"access.form.cmcccloud_access_key_secret.placeholder": "请输入移动云 AccessKeySecret",
"access.form.cmcccloud_access_key_secret.tooltip": "这是什么?请参阅 <a href=\"https://ecloud.10086.cn/op-help-center/doc/article/49739\" target=\"_blank\">https://ecloud.10086.cn/op-help-center/doc/article/49739</a>",
"access.form.constellix_api_key.label": "Constellix API Key",
"access.form.constellix_api_key.placeholder": "请输入 Constellix API Key",
"access.form.constellix_api_key.tooltip": "这是什么?请参阅 <a href=\"https://support.constellix.com/hc/en-us/articles/34574197390491-How-to-Generate-an-API-Key\" target=\"_blank\">https://support.constellix.com/hc/en-us/articles/34574197390491-How-to-Generate-an-API-Key</a>",
"access.form.constellix_secret_key.label": "Constellix Secret Key",
"access.form.constellix_secret_key.placeholder": "请输入 Constellix Secret Key",
"access.form.constellix_secret_key.tooltip": "这是什么?请参阅 <a href=\"https://support.constellix.com/hc/en-us/articles/34574197390491-How-to-Generate-an-API-Key\" target=\"_blank\">https://support.constellix.com/hc/en-us/articles/34574197390491-How-to-Generate-an-API-Key</a>",
"access.form.desec_token.label": "deSEC Token",
"access.form.desec_token.placeholder": "请输入 deSEC Token",
"access.form.desec_token.tooltip": "这是什么?请参阅 <a href=\"https://desec.readthedocs.io/en/latest/auth/tokens.html#manage-tokens\" target=\"_blank\">https://desec.readthedocs.io/en/latest/auth/tokens.html</a>",

View File

@ -55,6 +55,7 @@
"provider.cloudflare": "Cloudflare",
"provider.cloudns": "ClouDNS",
"provider.cmcccloud": "移动云",
"provider.constellix": "Constellix",
"provider.ctcccloud": "联通云",
"provider.cucccloud": "天翼云",
"provider.desec": "deSEC",