diff --git a/go.mod b/go.mod
index 88635d103..f9742529b 100644
--- a/go.mod
+++ b/go.mod
@@ -128,6 +128,7 @@ require (
github.com/nrdcg/desec v0.11.1 // indirect
github.com/nrdcg/goacmedns v0.2.0 // indirect
github.com/nrdcg/porkbun v0.4.0 // indirect
+ github.com/ovh/go-ovh v1.9.0 // indirect
github.com/peterhellberg/link v1.2.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/qiniu/dyn v1.3.0 // indirect
diff --git a/go.sum b/go.sum
index 02794a994..3613e26b2 100644
--- a/go.sum
+++ b/go.sum
@@ -649,6 +649,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
+github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
@@ -717,6 +719,8 @@ github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7y
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE=
+github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE=
+github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 h1:2nosf3P75OZv2/ZO/9Px5ZgZ5gbKrzA3joN1QMfOGMQ=
diff --git a/internal/certapply/applicators/sp_ovhcloud.go b/internal/certapply/applicators/sp_ovhcloud.go
new file mode 100644
index 000000000..5fe9d80db
--- /dev/null
+++ b/internal/certapply/applicators/sp_ovhcloud.go
@@ -0,0 +1,35 @@
+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/ovhcloud"
+ xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
+)
+
+func init() {
+ if err := ACMEDns01Registries.Register(domain.ACMEDns01ProviderTypeOVHcloud, func(options *ProviderFactoryOptions) (challenge.Provider, error) {
+ credentials := domain.AccessConfigForOVHcloud{}
+ if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
+ return nil, fmt.Errorf("failed to populate provider access config: %w", err)
+ }
+
+ provider, err := ovhcloud.NewChallengeProvider(&ovhcloud.ChallengeProviderConfig{
+ Endpoint: credentials.Endpoint,
+ AuthMethod: credentials.AuthMethod,
+ ApplicationKey: credentials.ApplicationKey,
+ ApplicationSecret: credentials.ApplicationSecret,
+ ConsumerKey: credentials.ConsumerKey,
+ ClientId: credentials.ClientId,
+ ClientSecret: credentials.ClientSecret,
+ 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 20207c419..972c7fd37 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -351,6 +351,16 @@ type AccessConfigForNS1 struct {
ApiKey string `json:"apiKey"`
}
+type AccessConfigForOVHcloud struct {
+ Endpoint string `json:"endpoint"`
+ AuthMethod string `json:"authMethod"`
+ ApplicationKey string `json:"applicationKey,omitempty"`
+ ApplicationSecret string `json:"applicationSecret,omitempty"`
+ ConsumerKey string `json:"consumerKey,omitempty"`
+ ClientId string `json:"clientId,omitempty"`
+ ClientSecret string `json:"clientSecret,omitempty"`
+}
+
type AccessConfigForPorkbun struct {
ApiKey string `json:"apiKey"`
SecretApiKey string `json:"secretApiKey"`
@@ -412,16 +422,16 @@ type AccessConfigForSpaceship struct {
type AccessConfigForSSH struct {
Host string `json:"host"`
Port int32 `json:"port"`
- AuthMethod string `json:"authMethod,omitempty"`
- Username string `json:"username,omitempty"`
+ AuthMethod string `json:"authMethod"`
+ Username string `json:"username"`
Password string `json:"password,omitempty"`
Key string `json:"key,omitempty"`
KeyPassphrase string `json:"keyPassphrase,omitempty"`
JumpServers []struct {
Host string `json:"host"`
Port int32 `json:"port"`
- AuthMethod string `json:"authMethod,omitempty"`
- Username string `json:"username,omitempty"`
+ AuthMethod string `json:"authMethod"`
+ Username string `json:"username"`
Password string `json:"password,omitempty"`
Key string `json:"key,omitempty"`
KeyPassphrase string `json:"keyPassphrase,omitempty"`
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index aa1c63556..c640c55b4 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -79,7 +79,7 @@ const (
AccessProviderTypeNetcup = AccessProviderType("netcup")
AccessProviderTypeNetlify = AccessProviderType("netlify")
AccessProviderTypeNS1 = AccessProviderType("ns1")
- AccessProviderTypeOVHcloud = AccessProviderType("ovhcloud") // OVHcloud(预留)
+ AccessProviderTypeOVHcloud = AccessProviderType("ovhcloud")
AccessProviderTypePorkbun = AccessProviderType("porkbun")
AccessProviderTypePowerDNS = AccessProviderType("powerdns")
AccessProviderTypeProxmoxVE = AccessProviderType("proxmoxve")
diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/ovhcloud/consts.go b/pkg/core/ssl-applicator/acme-dns01/providers/ovhcloud/consts.go
new file mode 100644
index 000000000..dd32fa900
--- /dev/null
+++ b/pkg/core/ssl-applicator/acme-dns01/providers/ovhcloud/consts.go
@@ -0,0 +1,6 @@
+package ovhcloud
+
+const (
+ AUTH_METHOD_APPLICATION = "application"
+ AUTH_METHOD_OAUTH2 = "oauth2"
+)
diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/ovhcloud/ovhcloud.go b/pkg/core/ssl-applicator/acme-dns01/providers/ovhcloud/ovhcloud.go
new file mode 100644
index 000000000..4d2b22c17
--- /dev/null
+++ b/pkg/core/ssl-applicator/acme-dns01/providers/ovhcloud/ovhcloud.go
@@ -0,0 +1,58 @@
+package ovhcloud
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/go-acme/lego/v4/providers/dns/ovh"
+
+ "github.com/certimate-go/certimate/pkg/core"
+)
+
+type ChallengeProviderConfig struct {
+ Endpoint string `json:"endpoint"`
+ AuthMethod string `json:"authMethod"`
+ ApplicationKey string `json:"applicationKey,omitempty"`
+ ApplicationSecret string `json:"applicationSecret,omitempty"`
+ ConsumerKey string `json:"consumerKey,omitempty"`
+ ClientId string `json:"clientId,omitempty"`
+ ClientSecret string `json:"clientSecret,omitempty"`
+ 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 := ovh.NewDefaultConfig()
+ providerConfig.APIEndpoint = config.Endpoint
+ switch config.AuthMethod {
+ case AUTH_METHOD_APPLICATION:
+ providerConfig.ApplicationKey = config.ApplicationKey
+ providerConfig.ApplicationSecret = config.ApplicationSecret
+ providerConfig.ConsumerKey = config.ConsumerKey
+ case AUTH_METHOD_OAUTH2:
+ providerConfig.OAuth2Config = &ovh.OAuth2Config{
+ ClientID: config.ClientId,
+ ClientSecret: config.ClientSecret,
+ }
+ default:
+ return nil, fmt.Errorf("unsupported auth method '%s'", config.AuthMethod)
+ }
+ if config.DnsPropagationTimeout != 0 {
+ providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
+ }
+ if config.DnsTTL != 0 {
+ providerConfig.TTL = int(config.DnsTTL)
+ }
+
+ provider, err := ovh.NewDNSProviderConfig(providerConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return provider, nil
+}
diff --git a/ui/public/imgs/providers/ovhcloud.svg b/ui/public/imgs/providers/ovhcloud.svg
new file mode 100644
index 000000000..b29b804d5
--- /dev/null
+++ b/ui/public/imgs/providers/ovhcloud.svg
@@ -0,0 +1 @@
+
diff --git a/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx b/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx
index 2d59bf3a0..135c7a80d 100644
--- a/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx
+++ b/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx
@@ -65,6 +65,7 @@ import AccessConfigFieldsProviderNameSilo from "./AccessConfigFieldsProviderName
import AccessConfigFieldsProviderNetcup from "./AccessConfigFieldsProviderNetcup";
import AccessConfigFieldsProviderNetlify from "./AccessConfigFieldsProviderNetlify";
import AccessConfigFieldsProviderNS1 from "./AccessConfigFieldsProviderNS1";
+import AccessConfigFieldsProviderOVHcloud from "./AccessConfigFieldsProviderOVHcloud";
import AccessConfigFieldsProviderPorkbun from "./AccessConfigFieldsProviderPorkbun";
import AccessConfigFieldsProviderPowerDNS from "./AccessConfigFieldsProviderPowerDNS";
import AccessConfigFieldsProviderProxmoxVE from "./AccessConfigFieldsProviderProxmoxVE";
@@ -161,6 +162,7 @@ const providerComponentMap: Partial {
+ const { i18n, t } = useTranslation();
+
+ const { parentNamePath } = useFormNestedFieldsContext();
+ const formSchema = z.object({
+ [parentNamePath]: getSchema({ i18n }),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+ const formInst = Form.useFormInstance();
+ const initialValues = getInitialValues();
+
+ const fieldAuthMethod = Form.useWatch([parentNamePath, "authMethod"], formInst);
+
+ return (
+ <>
+
+ ({ value }))}
+ placeholder={t("access.form.ovhcloud_endpoint.placeholder")}
+ filterOption={(inputValue, option) => option!.value.toLowerCase().includes(inputValue.toLowerCase())}
+ />
+
+
+
+
+ {t("access.form.ovhcloud_auth_method.option.application.label")}
+ {t("access.form.ovhcloud_auth_method.option.oauth2.label")}
+
+
+
+
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+
+
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ >
+ );
+};
+
+const getInitialValues = (): Nullish>> => {
+ return {
+ endpoint: "ovh-eu",
+ authMethod: AUTH_METHOD_APPLICATION,
+ };
+};
+
+const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => {
+ const { t } = i18n;
+
+ return z
+ .object({
+ endpoint: z.string().nonempty(t("access.form.ovhcloud_endpoint.placeholder")),
+ authMethod: z.literal([AUTH_METHOD_APPLICATION, AUTH_METHOD_OAUTH2], t("access.form.ovhcloud_auth_method.placeholder")),
+ applicationKey: z.string().nullish(),
+ applicationSecret: z.string().nullish(),
+ consumerKey: z.string().nullish(),
+ clientId: z.string().nullish(),
+ clientSecret: z.string().nullish(),
+ })
+ .superRefine((values, ctx) => {
+ switch (values.authMethod) {
+ case AUTH_METHOD_APPLICATION:
+ {
+ if (!values.applicationKey?.trim()) {
+ ctx.addIssue({
+ code: "custom",
+ message: t("access.form.ovhcloud_application_key.placeholder"),
+ path: ["applicationKey"],
+ });
+ }
+
+ if (!values.applicationSecret?.trim()) {
+ ctx.addIssue({
+ code: "custom",
+ message: t("access.form.ovhcloud_application_secret.placeholder"),
+ path: ["applicationSecret"],
+ });
+ }
+
+ if (!values.consumerKey?.trim()) {
+ ctx.addIssue({
+ code: "custom",
+ message: t("access.form.ovhcloud_consumer_key.placeholder"),
+ path: ["consumerKey"],
+ });
+ }
+ }
+ break;
+
+ case AUTH_METHOD_OAUTH2:
+ {
+ if (!values.clientId?.trim()) {
+ ctx.addIssue({
+ code: "custom",
+ message: t("access.form.ovhcloud_client_id.placeholder"),
+ path: ["clientId"],
+ });
+ }
+
+ if (!values.clientSecret?.trim()) {
+ ctx.addIssue({
+ code: "custom",
+ message: t("access.form.ovhcloud_client_secret.placeholder"),
+ path: ["clientSecret"],
+ });
+ }
+ }
+ break;
+ }
+ });
+};
+
+const _default = Object.assign(AccessConfigFormFieldsProviderOVHcloud, {
+ getInitialValues,
+ getSchema,
+});
+
+export default _default;
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index ddc9904f2..ab93f5573 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -201,6 +201,7 @@ export const accessProvidersMap: Maphttps://www.ibm.com/docs/en/ns1-connect?topic=introduction-using-api",
+ "access.form.ovhcloud_endpoint.label": "OVHcloud API endpoint",
+ "access.form.ovhcloud_endpoint.placeholder": "Please enter OVHcloud API endpoint",
+ "access.form.ovhcloud_auth_method.label": "OVHcloud API authentication method",
+ "access.form.ovhcloud_auth_method.placeholder": "Please select OVHcloud API authentication method",
+ "access.form.ovhcloud_auth_method.option.application.label": "Application key & secret",
+ "access.form.ovhcloud_auth_method.option.oauth2.label": "OAuth2 client credentials",
+ "access.form.ovhcloud_application_key.label": "OVHcloud application key",
+ "access.form.ovhcloud_application_key.placeholder": "Please enter OVHcloud application key",
+ "access.form.ovhcloud_application_key.tooltip": "For more information, see https://docs.ovh.com/gb/en/customer/first-steps-with-ovh-api/",
+ "access.form.ovhcloud_application_secret.label": "OVHcloud application secret",
+ "access.form.ovhcloud_application_secret.placeholder": "Please enter OVHcloud application secret",
+ "access.form.ovhcloud_application_secret.tooltip": "For more information, see https://docs.ovh.com/gb/en/customer/first-steps-with-ovh-api/",
+ "access.form.ovhcloud_consumer_key.label": "OVHcloud consumer key",
+ "access.form.ovhcloud_consumer_key.placeholder": "Please enter OVHcloud consumer key",
+ "access.form.ovhcloud_consumer_key.tooltip": "For more information, see https://docs.ovh.com/gb/en/customer/first-steps-with-ovh-api/",
+ "access.form.ovhcloud_client_id.label": "OVHcloud client ID",
+ "access.form.ovhcloud_client_id.placeholder": "Please enter OVHcloud client ID",
+ "access.form.ovhcloud_client_id.tooltip": "For more information, see https://help.ovhcloud.com/csm/en-manage-service-account?id=kb_article_view&sysparm_article=KB0059343",
+ "access.form.ovhcloud_client_secret.label": "OVHcloud client secret",
+ "access.form.ovhcloud_client_secret.placeholder": "Please enter OVHcloud client secret",
+ "access.form.ovhcloud_client_secret.tooltip": "For more information, see https://help.ovhcloud.com/csm/en-manage-service-account?id=kb_article_view&sysparm_article=KB0059343",
"access.form.porkbun_api_key.label": "Porkbun API key",
"access.form.porkbun_api_key.placeholder": "Please enter Porkbun API key",
"access.form.porkbun_api_key.tooltip": "For more information, see https://porkbun.com/api/json/v3/documentation",
diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json
index 37f8eb381..dbf7cb237 100644
--- a/ui/src/i18n/locales/zh/nls.access.json
+++ b/ui/src/i18n/locales/zh/nls.access.json
@@ -407,6 +407,27 @@
"access.form.ns1_api_key.label": "NS1 API Key",
"access.form.ns1_api_key.placeholder": "请输入 NS1 API Key",
"access.form.ns1_api_key.tooltip": "这是什么?请参阅 https://www.ibm.com/docs/zh/ns1-connect?topic=introduction-using-api",
+ "access.form.ovhcloud_endpoint.label": "OVHcloud API 端点",
+ "access.form.ovhcloud_endpoint.placeholder": "请输入 OVHcloud API 端点",
+ "access.form.ovhcloud_auth_method.label": "OVHcloud API 认证方式",
+ "access.form.ovhcloud_auth_method.placeholder": "请选择 OVHcloud API 认证方式",
+ "access.form.ovhcloud_auth_method.option.application.label": "Application Key & Secret",
+ "access.form.ovhcloud_auth_method.option.oauth2.label": "OAuth2 Client Credentials",
+ "access.form.ovhcloud_application_key.label": "OVHcloud Application Key",
+ "access.form.ovhcloud_application_key.placeholder": "请输入 OVHcloud Application Key",
+ "access.form.ovhcloud_application_key.tooltip": "这是什么?请参阅 https://docs.ovh.com/gb/en/customer/first-steps-with-ovh-api/",
+ "access.form.ovhcloud_application_secret.label": "OVHcloud Application Secret",
+ "access.form.ovhcloud_application_secret.placeholder": "请输入 OVHcloud Application Secret",
+ "access.form.ovhcloud_application_secret.tooltip": "这是什么?请参阅 https://docs.ovh.com/gb/en/customer/first-steps-with-ovh-api/",
+ "access.form.ovhcloud_consumer_key.label": "OVHcloud Consumer Key",
+ "access.form.ovhcloud_consumer_key.placeholder": "请输入 OVHcloud Consumer Key",
+ "access.form.ovhcloud_consumer_key.tooltip": "这是什么?请参阅 https://docs.ovh.com/gb/en/customer/first-steps-with-ovh-api/",
+ "access.form.ovhcloud_client_id.label": "OVHcloud Client ID",
+ "access.form.ovhcloud_client_id.placeholder": "请输入 OVHcloud Client ID",
+ "access.form.ovhcloud_client_id.tooltip": "这是什么?请参阅 https://help.ovhcloud.com/csm/en-manage-service-account?id=kb_article_view&sysparm_article=KB0059343",
+ "access.form.ovhcloud_client_secret.label": "OVHcloud Client Cecret",
+ "access.form.ovhcloud_client_secret.placeholder": "请输入 OVHcloud Client Secret",
+ "access.form.ovhcloud_client_secret.tooltip": "这是什么?请参阅 https://help.ovhcloud.com/csm/en-manage-service-account?id=kb_article_view&sysparm_article=KB0059343",
"access.form.porkbun_api_key.label": "Porkbun API Key",
"access.form.porkbun_api_key.placeholder": "请输入 Porkbun API Key",
"access.form.porkbun_api_key.tooltip": "这是什么?请参阅 https://porkbun.com/api/json/v3/documentation",