diff --git a/README.md b/README.md index 3ab77300..ed79b12f 100644 --- a/README.md +++ b/README.md @@ -86,20 +86,21 @@ make local.run [展开查看] -| 提供商 | 备注 | -| :--------------------------------------------- | :-------------------------------------- | -| [阿里云](https://www.aliyun.com/) | | -| [腾讯云](https://cloud.tencent.com/) | | -| [华为云](https://www.huaweicloud.com/) | | -| [火山引擎](https://www.volcengine.com/) | | -| [AWS Route53](https://aws.amazon.com/route53/) | | -| [Azure](https://azure.microsoft.com/) | | -| [CloudFlare](https://www.cloudflare.com/) | | -| [GoDaddy](https://www.godaddy.com/) | | -| [Name.com](https://www.name.com/) | | -| [NameSilo](https://www.namesilo.com/) | | -| [PowerDNS](https://www.powerdns.com/) | | -| ACME 代理 HTTP 请求 | 可申请允许通过 HTTP 请求修改 DNS 的域名 | +| 提供商 | 备注 | +| :----------------------------------------------------------------- | :-------------------------------------- | +| [阿里云](https://www.aliyun.com/) | | +| [腾讯云](https://cloud.tencent.com/) | | +| [华为云](https://www.huaweicloud.com/) | | +| [火山引擎](https://www.volcengine.com/) | | +| [AWS Route53](https://aws.amazon.com/route53/) | | +| [Azure](https://azure.microsoft.com/) | | +| [CloudFlare](https://www.cloudflare.com/) | | +| [GoDaddy](https://www.godaddy.com/) | | +| [Name.com](https://www.name.com/) | | +| [NameSilo](https://www.namesilo.com/) | | +| [IBM NS1 Connect](https://www.ibm.com/cn-zh/products/ns1-connect/) | | +| [PowerDNS](https://www.powerdns.com/) | | +| ACME 代理 HTTP 请求 | 可申请允许通过 HTTP 请求修改 DNS 的域名 | diff --git a/README_EN.md b/README_EN.md index f0f8338e..9c9aab55 100644 --- a/README_EN.md +++ b/README_EN.md @@ -85,20 +85,21 @@ The following DNS providers are supported: [Fold/Unfold to view ...] -| Provider | Remarks | -| :--------------------------------------------- | :------------------------------------ | -| [Alibaba Cloud](https://www.alibabacloud.com/) | | -| [Tencent Cloud](https://www.tencentcloud.com/) | | -| [Huawei Cloud](https://www.huaweicloud.com/) | | -| [Volcengine](https://www.volcengine.com/) | | -| [AWS Route53](https://aws.amazon.com/route53/) | | -| [Azure DNS](https://azure.microsoft.com/) | | -| [CloudFlare](https://www.cloudflare.com/) | | -| [GoDaddy](https://www.godaddy.com/) | | -| [Name.com](https://www.name.com/) | | -| [NameSilo](https://www.namesilo.com/) | | -| [PowerDNS](https://www.powerdns.com/) | | -| ACME Proxy HTTP Request | Supports managing DNS by HTTP request | +| Provider | Remarks | +| :----------------------------------------------------------- | :------------------------------------ | +| [Alibaba Cloud](https://www.alibabacloud.com/) | | +| [Tencent Cloud](https://www.tencentcloud.com/) | | +| [Huawei Cloud](https://www.huaweicloud.com/) | | +| [Volcengine](https://www.volcengine.com/) | | +| [AWS Route53](https://aws.amazon.com/route53/) | | +| [Azure DNS](https://azure.microsoft.com/) | | +| [CloudFlare](https://www.cloudflare.com/) | | +| [GoDaddy](https://www.godaddy.com/) | | +| [Name.com](https://www.name.com/) | | +| [NameSilo](https://www.namesilo.com/) | | +| [IBM NS1 Connect](https://www.ibm.com/products/ns1-connect/) | | +| [PowerDNS](https://www.powerdns.com/) | | +| ACME Proxy HTTP Request | Supports managing DNS by HTTP request | diff --git a/go.mod b/go.mod index 1ff91571..9d52261f 100644 --- a/go.mod +++ b/go.mod @@ -87,6 +87,7 @@ require ( go.mongodb.org/mongo-driver v1.12.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ns1/ns1-go.v2 v2.13.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect diff --git a/go.sum b/go.sum index 8e6555c8..d41faffa 100644 --- a/go.sum +++ b/go.sum @@ -1369,6 +1369,8 @@ gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/ns1/ns1-go.v2 v2.13.0 h1:I5NNqI9Bi1SGK92TVkOvLTwux5LNrix/99H2datVh48= +gopkg.in/ns1/ns1-go.v2 v2.13.0/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 795afe08..8e42f4f2 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -15,6 +15,7 @@ import ( providerHuaweiCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/huaweicloud" providerNameDotCom "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namedotcom" providerNameSilo "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namesilo" + providerNS1 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1" providerPowerDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/powerdns" providerTencentCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud" providerVolcEngine "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/volcengine" @@ -167,6 +168,20 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } + case domain.ApplyDNSProviderTypeNS1: + { + access := domain.AccessConfigForNS1{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) + } + + applicant, err := providerNS1.NewChallengeProvider(&providerNS1.NS1ApplicantConfig{ + ApiKey: access.ApiKey, + PropagationTimeout: options.PropagationTimeout, + }) + return applicant, err + } + case domain.ApplyDNSProviderTypePowerDNS: { access := domain.AccessConfigForPowerDNS{} diff --git a/internal/domain/access.go b/internal/domain/access.go index 1893b4ab..dc118482 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -91,6 +91,10 @@ type AccessConfigForNameSilo struct { ApiKey string `json:"apiKey"` } +type AccessConfigForNS1 struct { + ApiKey string `json:"apiKey"` +} + type AccessConfigForPowerDNS struct { ApiUrl string `json:"apiUrl"` ApiKey string `json:"apiKey"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index b90d9b38..dead9c57 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -23,6 +23,7 @@ const ( AccessProviderTypeLocal = AccessProviderType("local") AccessProviderTypeNameDotCom = AccessProviderType("namedotcom") AccessProviderTypeNameSilo = AccessProviderType("namesilo") + AccessProviderTypeNS1 = AccessProviderType("ns1") AccessProviderTypePowerDNS = AccessProviderType("powerdns") AccessProviderTypeQiniu = AccessProviderType("qiniu") AccessProviderTypeSSH = AccessProviderType("ssh") @@ -54,6 +55,7 @@ const ( ApplyDNSProviderTypeHuaweiCloudDNS = ApplyDNSProviderType("huaweicloud-dns") ApplyDNSProviderTypeNameDotCom = ApplyDNSProviderType("namedotcom") ApplyDNSProviderTypeNameSilo = ApplyDNSProviderType("namesilo") + ApplyDNSProviderTypeNS1 = ApplyDNSProviderType("ns1") ApplyDNSProviderTypePowerDNS = ApplyDNSProviderType("powerdns") ApplyDNSProviderTypeTencentCloud = ApplyDNSProviderType("tencentcloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeTencentCloudDNS] ApplyDNSProviderTypeTencentCloudDNS = ApplyDNSProviderType("tencentcloud-dns") diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1/ns1.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1/ns1.go new file mode 100644 index 00000000..7403ecb8 --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1/ns1.go @@ -0,0 +1,33 @@ +package ns1 + +import ( + "errors" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/providers/dns/ns1" +) + +type NS1ApplicantConfig struct { + ApiKey string `json:"apiKey"` + PropagationTimeout int32 `json:"propagationTimeout,omitempty"` +} + +func NewChallengeProvider(config *NS1ApplicantConfig) (challenge.Provider, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + providerConfig := ns1.NewDefaultConfig() + providerConfig.APIKey = config.ApiKey + if config.PropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.PropagationTimeout) * time.Second + } + + provider, err := ns1.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/ui/public/imgs/providers/ns1.svg b/ui/public/imgs/providers/ns1.svg new file mode 100644 index 00000000..07811134 --- /dev/null +++ b/ui/public/imgs/providers/ns1.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index b398013a..040e52dd 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -21,6 +21,7 @@ import AccessFormGoDaddyConfig from "./AccessFormGoDaddyConfig"; import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig"; import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig"; import AccessFormLocalConfig from "./AccessFormLocalConfig"; +import AccessFormNS1Config from "./AccessFormNS1Config"; import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig"; import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig"; import AccessFormPowerDNSConfig from "./AccessFormPowerDNSConfig"; @@ -111,6 +112,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.NAMESILO: return ; + case ACCESS_PROVIDERS.NS1: + return ; case ACCESS_PROVIDERS.POWERDNS: return ; case ACCESS_PROVIDERS.QINIU: diff --git a/ui/src/components/access/AccessFormNS1Config.tsx b/ui/src/components/access/AccessFormNS1Config.tsx new file mode 100644 index 00000000..5b12feb0 --- /dev/null +++ b/ui/src/components/access/AccessFormNS1Config.tsx @@ -0,0 +1,61 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForNS1 } from "@/domain/access"; + +type AccessFormNS1ConfigFieldValues = Nullish; + +export type AccessFormNS1ConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormNS1ConfigFieldValues; + onValuesChange?: (values: AccessFormNS1ConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormNS1ConfigFieldValues => { + return { + apiKey: "", + }; +}; + +const AccessFormNS1Config = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormNS1ConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiKey: z + .string() + .min(1, t("access.form.ns1_api_key.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default AccessFormNS1Config; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 01d97d04..ed2bd310 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -102,6 +102,10 @@ export type AccessConfigForNameSilo = { apiKey: string; }; +export type AccessConfigForNS1 = { + apiKey: string; +}; + export type AccessConfigForPowerDNS = { apiUrl: string; apiKey: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 42250e9d..ba1a10f2 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -18,6 +18,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ LOCAL: "local", NAMEDOTCOM: "namedotcom", NAMESILO: "namesilo", + NS1: "ns1", POWERDNS: "powerdns", QINIU: "qiniu", SSH: "ssh", @@ -68,6 +69,7 @@ export const accessProvidersMap: Map [ diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 66228013..d609d6e7 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -103,6 +103,9 @@ "access.form.namesilo_api_key.label": "NameSilo API key", "access.form.namesilo_api_key.placeholder": "Please enter NameSilo API key", "access.form.namesilo_api_key.tooltip": "For more information, see https://www.namesilo.com/support/v2/articles/account-options/api-manager", + "access.form.ns1_api_key.label": "NS1 API key", + "access.form.ns1_api_key.placeholder": "Please enter NS1 API key", + "access.form.ns1_api_key.tooltip": "For more information, see https://www.ibm.com/docs/en/ns1-connect?topic=introduction-using-api", "access.form.powerdns_api_url.label": "PowerDNS API URL", "access.form.powerdns_api_url.placeholder": "Please enter PowerDNS API URL", "access.form.powerdns_api_url.tooltip": "For more information, see https://doc.powerdns.com/authoritative/http-api/index.html#endpoints-and-objects-in-the-api", diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index dc8ea864..7b0199a9 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -66,6 +66,7 @@ "common.provider.local": "Local deployment", "common.provider.namedotcom": "Name.com", "common.provider.namesilo": "NameSilo", + "common.provider.ns1": "NS1 (IBM NS1 Connect)", "common.provider.powerdns": "PowerDNS", "common.provider.qiniu": "Qiniu", "common.provider.qiniu.cdn": "Qiniu - Content Delivery Network (CDN)", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index e24c125b..b9107581 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -45,14 +45,14 @@ "access.form.aws_secret_access_key.label": "AWS SecretAccessKey", "access.form.aws_secret_access_key.placeholder": "请输入 AWS SecretAccessKey", "access.form.aws_secret_access_key.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/id_credentials_access-keys.html", - "access.form.azure_tenant_id.label": "Azure TenantId", - "access.form.azure_tenant_id.placeholder": "请输入 Azure TenantId", + "access.form.azure_tenant_id.label": "Azure 租户 ID", + "access.form.azure_tenant_id.placeholder": "请输入 Azure 租户 ID", "access.form.azure_tenant_id.tooltip": "这是什么?请参阅 https://learn.microsoft.com/zh-cn/azure/azure-portal/get-subscription-tenant-id", - "access.form.azure_client_id.label": "Azure ClientId", - "access.form.azure_client_id.placeholder": "请输入 Azure ClientId", + "access.form.azure_client_id.label": "Azure 客户端 ID", + "access.form.azure_client_id.placeholder": "请输入 Azure 客户端 ID", "access.form.azure_client_id.tooltip": "这是什么?请参阅 https://learn.microsoft.com/zh-cn/azure/azure-monitor/logs/api/register-app-for-token", - "access.form.azure_client_secret.label": "Azure ClientSecret", - "access.form.azure_client_secret.placeholder": "请输入 Azure ClientSecret", + "access.form.azure_client_secret.label": "Azure 客户端密码", + "access.form.azure_client_secret.placeholder": "请输入 Azure 客户端密码", "access.form.azure_client_secret.tooltip": "这是什么?请参阅 https://learn.microsoft.com/zh-cn/azure/azure-monitor/logs/api/register-app-for-token", "access.form.azure_cloud_name.label": "Azure 主权云环境(可选)", "access.form.azure_cloud_name.placeholder": "请输入 Azure 主权云环境(例如:public)", @@ -103,6 +103,9 @@ "access.form.namesilo_api_key.label": "NameSilo API Key", "access.form.namesilo_api_key.placeholder": "请输入 NameSilo API Key", "access.form.namesilo_api_key.tooltip": "这是什么?请参阅 https://www.namesilo.com/support/v2/articles/account-options/api-manager", + "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.powerdns_api_url.label": "PowerDNS API URL", "access.form.powerdns_api_url.placeholder": "请输入 PowerDNS API URL", "access.form.powerdns_api_url.tooltip": "这是什么?请参阅 https://doc.powerdns.com/authoritative/http-api/index.html#endpoints-and-objects-in-the-api", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index 80d6ccc9..0951152a 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -66,6 +66,7 @@ "common.provider.local": "本地部署", "common.provider.namedotcom": "Name.com", "common.provider.namesilo": "NameSilo", + "common.provider.ns1": "NS1(IBM NS1 Connect)", "common.provider.powerdns": "PowerDNS", "common.provider.qiniu": "七牛云", "common.provider.qiniu.cdn": "七牛云 - 内容分发网络 CDN",