Scert/backend/internal/dnsprovider/catalog.go

181 lines
7.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package dnsprovider
import (
"errors"
"fmt"
"sort"
"strings"
)
const (
Cloudflare = "cloudflare"
Aliyun = "aliyun"
DNSPod = "dnspod"
CNAME = "cname"
HuaweiCloud = "huaweicloud"
TencentCloud = "tencentcloud"
Route53 = "route53"
GoogleCloud = "googlecloud"
Other = "other"
)
var ErrProviderUnavailable = errors.New("DNS 服务商适配器尚未开放")
type CredentialField struct {
Name string `json:"name"`
Label string `json:"label"`
Secret bool `json:"secret"`
Required bool `json:"required"`
Placeholder string `json:"placeholder,omitempty"`
}
type Descriptor struct {
ID string `json:"id"`
Name string `json:"name"`
Available bool `json:"available"`
CredentialFields []CredentialField `json:"credential_fields"`
Description string `json:"description"`
}
var descriptors = map[string]Descriptor{
Cloudflare: {
ID: Cloudflare, Name: "Cloudflare", Available: true,
Description: "使用 API Token 创建并清理 DNS-01 测试记录。",
CredentialFields: []CredentialField{
{Name: "api_token", Label: "DNS API Token", Secret: true, Required: true, Placeholder: "需要 Zone:Read 与 DNS:Edit 权限"},
{Name: "zone_token", Label: "Zone API Token", Secret: true, Placeholder: "可选;用于拆分 Zone 读取权限"},
},
},
Aliyun: {
ID: Aliyun, Name: "阿里云 DNS", Available: true,
Description: "仅操作显式托管区内的允许域名。范围用英文逗号分隔;通配符只允许一层子域,不包含根域。旧配置需补充范围后重新验证。",
CredentialFields: []CredentialField{
{Name: "access_key_id", Label: "AccessKey ID", Required: true},
{Name: "access_key_secret", Label: "AccessKey Secret", Secret: true, Required: true},
{Name: "security_token", Label: "Security Token", Secret: true},
{Name: "managed_zones", Label: "明确托管区", Required: true, Placeholder: "例如 example.com,example.net;不自动尝试父域"},
{Name: "allowed_domains", Label: "允许操作的域名", Required: true, Placeholder: "例如 www.example.com,*.example.net;根域须单独填写"},
},
},
DNSPod: {
ID: DNSPod, Name: "DNSPod", Available: true,
Description: "使用 DNSPod API 凭据创建和清理 DNS-01 测试记录。",
CredentialFields: []CredentialField{
{Name: "secret_id", Label: "SecretId", Required: true},
{Name: "secret_key", Label: "SecretKey", Secret: true, Required: true},
{Name: "session_token", Label: "Session Token", Secret: true},
},
},
CNAME: {
ID: CNAME, Name: "CNAME 委派", Description: "使用已配置的 CNAME 将 DNS-01 挑战转发到专用验证域,并由目标 DNS 服务商创建和清理 TXT 记录。",
Available: true,
CredentialFields: []CredentialField{
{Name: "delegated_domain", Label: "专用验证域", Required: true, Placeholder: "例如 acme.example.com"},
{Name: "delegated_provider", Label: "委派目标 DNS 服务商", Required: true},
{Name: "delegated_credentials_json", Label: "委派凭据 JSON", Secret: true, Required: true, Placeholder: "例如 {\"api_token\":\"...\"}"},
},
},
HuaweiCloud: {
ID: HuaweiCloud, Name: "华为云", Available: true,
Description: "使用 Access Key、Secret Access Key 和区域创建 DNS-01 测试记录。",
CredentialFields: []CredentialField{
{Name: "access_key_id", Label: "Access Key ID", Required: true},
{Name: "secret_access_key", Label: "Secret Access Key", Secret: true, Required: true},
{Name: "region", Label: "区域", Required: true, Placeholder: "例如 cn-north-4"},
},
},
TencentCloud: {
ID: TencentCloud, Name: "腾讯云", Available: true,
Description: "使用腾讯云 DNSPod API 凭据创建和清理 DNS-01 测试记录。",
CredentialFields: []CredentialField{
{Name: "secret_id", Label: "SecretId", Required: true},
{Name: "secret_key", Label: "SecretKey", Secret: true, Required: true},
{Name: "region", Label: "区域", Placeholder: "可选"},
{Name: "session_token", Label: "Session Token", Secret: true},
},
},
Route53: {
ID: Route53, Name: "AWS Route 53", Available: true,
Description: "使用 AWS 凭据和区域创建和清理 DNS-01 测试记录。",
CredentialFields: []CredentialField{
{Name: "access_key_id", Label: "Access Key ID", Required: true},
{Name: "secret_access_key", Label: "Secret Access Key", Secret: true, Required: true},
{Name: "region", Label: "区域", Required: true, Placeholder: "例如 us-east-1"},
{Name: "session_token", Label: "Session Token", Secret: true},
{Name: "hosted_zone_id", Label: "Hosted Zone ID"},
},
},
GoogleCloud: {
ID: GoogleCloud, Name: "Google Cloud DNS", Available: true,
Description: "使用 Google Cloud 服务账号创建和清理 DNS-01 测试记录。",
CredentialFields: []CredentialField{
{Name: "service_account_json", Label: "服务账号 JSON", Secret: true, Required: true},
{Name: "project", Label: "项目 ID", Required: true},
{Name: "zone_id", Label: "托管区域 ID"},
},
},
Other: {
ID: Other, Name: "其他服务商", Description: "扩展占位;需要实现专用 Provider 适配器后才能在线验证。",
CredentialFields: []CredentialField{
{Name: "provider_name", Label: "服务商标识", Required: true},
{Name: "config_json", Label: "Provider 配置 JSON", Secret: true, Required: true},
},
},
}
func Descriptors() []Descriptor {
order := []string{Cloudflare, Aliyun, DNSPod, CNAME, HuaweiCloud, TencentCloud, Route53, GoogleCloud, Other}
result := make([]Descriptor, 0, len(order))
for _, id := range order {
result = append(result, descriptors[id])
}
return result
}
func GetDescriptor(providerID string) (Descriptor, error) {
providerID = strings.ToLower(strings.TrimSpace(providerID))
descriptor, ok := descriptors[providerID]
if !ok {
return Descriptor{}, fmt.Errorf("未知 DNS 服务商:%s", providerID)
}
return descriptor, nil
}
func ValidateCredentials(providerID string, credentials map[string]string) (map[string]string, error) {
descriptor, err := GetDescriptor(providerID)
if err != nil {
return nil, err
}
cleaned := make(map[string]string, len(descriptor.CredentialFields))
allowed := make(map[string]struct{}, len(descriptor.CredentialFields))
for _, field := range descriptor.CredentialFields {
allowed[field.Name] = struct{}{}
value := strings.TrimSpace(credentials[field.Name])
if field.Required && value == "" {
return nil, fmt.Errorf("%s不能为空", field.Label)
}
if value != "" {
cleaned[field.Name] = value
}
}
for name := range credentials {
if _, ok := allowed[name]; !ok {
return nil, fmt.Errorf("DNS 凭据字段无效:%s", name)
}
}
return cleaned, nil
}
func CredentialNames(providerID string) []string {
descriptor, err := GetDescriptor(providerID)
if err != nil {
return nil
}
result := make([]string, 0, len(descriptor.CredentialFields))
for _, field := range descriptor.CredentialFields {
result = append(result, field.Name)
}
sort.Strings(result)
return result
}