149 lines
5.4 KiB
Go
149 lines
5.4 KiB
Go
package dnsprovider
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"strings"
|
||
|
||
"github.com/go-acme/lego/v5/challenge"
|
||
"github.com/go-acme/lego/v5/providers/dns/cloudflare"
|
||
"github.com/go-acme/lego/v5/providers/dns/gcloud"
|
||
"github.com/go-acme/lego/v5/providers/dns/huaweicloud"
|
||
"github.com/go-acme/lego/v5/providers/dns/route53"
|
||
"github.com/go-acme/lego/v5/providers/dns/tencentcloud"
|
||
"golang.org/x/oauth2/google"
|
||
gdns "google.golang.org/api/dns/v1"
|
||
|
||
"git.pcux.cn/xuans/Scert/backend/internal/domain"
|
||
)
|
||
|
||
type LegoFactory struct{}
|
||
|
||
func NewLegoFactory() LegoFactory {
|
||
return LegoFactory{}
|
||
}
|
||
|
||
func (LegoFactory) Create(providerID string, credentials map[string]string) (challenge.Provider, error) {
|
||
providerID = strings.ToLower(strings.TrimSpace(providerID))
|
||
descriptor, err := GetDescriptor(providerID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if !descriptor.Available {
|
||
return nil, fmt.Errorf("%w:%s", ErrProviderUnavailable, descriptor.Name)
|
||
}
|
||
cleaned, err := ValidateCredentials(providerID, credentials)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
switch providerID {
|
||
case CNAME:
|
||
delegatedDomain := normalizeProviderDomain(cleaned["delegated_domain"])
|
||
if delegatedDomain == "" || strings.HasPrefix(delegatedDomain, "*.") || !domain.IsValidName(delegatedDomain) {
|
||
return nil, ErrInvalidDelegatedDomain
|
||
}
|
||
delegatedProvider := strings.ToLower(strings.TrimSpace(cleaned["delegated_provider"]))
|
||
if delegatedProvider == CNAME || delegatedProvider == Other || delegatedProvider == "" {
|
||
return nil, fmt.Errorf("%w: %s", ErrDelegatedProviderUnavailable, delegatedProvider)
|
||
}
|
||
var delegatedCredentials map[string]string
|
||
if err := json.Unmarshal([]byte(cleaned["delegated_credentials_json"]), &delegatedCredentials); err != nil {
|
||
return nil, fmt.Errorf("%w: %v", ErrInvalidDelegatedCredentials, err)
|
||
}
|
||
if delegatedCredentials == nil {
|
||
return nil, ErrInvalidDelegatedCredentials
|
||
}
|
||
target, err := (LegoFactory{}).Create(delegatedProvider, delegatedCredentials)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("%w: %v", ErrDelegatedProviderUnavailable, err)
|
||
}
|
||
return newCNAMEDelegationProvider(delegatedDomain, target)
|
||
case Cloudflare:
|
||
cfg := cloudflare.NewDefaultConfig()
|
||
cfg.AuthToken = cleaned["api_token"]
|
||
cfg.ZoneToken = cleaned["zone_token"]
|
||
if cfg.ZoneToken == "" {
|
||
cfg.ZoneToken = cfg.AuthToken
|
||
}
|
||
provider, err := cloudflare.NewDNSProviderConfig(cfg)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建 Cloudflare DNS Provider 失败:%w", err)
|
||
}
|
||
return provider, nil
|
||
case Aliyun:
|
||
return newScopedAliyunProvider(cleaned)
|
||
case DNSPod, TencentCloud:
|
||
cfg := tencentcloud.NewDefaultConfig()
|
||
cfg.SecretID = cleaned["secret_id"]
|
||
cfg.SecretKey = cleaned["secret_key"]
|
||
cfg.SessionToken = cleaned["session_token"]
|
||
provider, err := tencentcloud.NewDNSProviderConfig(cfg)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建腾讯云 DNSPod Provider 失败:%w", err)
|
||
}
|
||
return provider, nil
|
||
case HuaweiCloud:
|
||
cfg := huaweicloud.NewDefaultConfig()
|
||
cfg.AccessKeyID = cleaned["access_key_id"]
|
||
cfg.SecretAccessKey = cleaned["secret_access_key"]
|
||
cfg.Region = cleaned["region"]
|
||
provider, err := huaweicloud.NewDNSProviderConfig(cfg)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建华为云 DNS Provider 失败:%w", err)
|
||
}
|
||
return provider, nil
|
||
case Route53:
|
||
cfg := route53.NewDefaultConfig()
|
||
cfg.AccessKeyID = cleaned["access_key_id"]
|
||
cfg.SecretAccessKey = cleaned["secret_access_key"]
|
||
cfg.SessionToken = cleaned["session_token"]
|
||
cfg.Region = cleaned["region"]
|
||
cfg.HostedZoneID = cleaned["hosted_zone_id"]
|
||
provider, err := route53.NewDNSProviderConfig(cfg)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建 AWS Route 53 DNS Provider 失败:%w", err)
|
||
}
|
||
return provider, nil
|
||
case GoogleCloud:
|
||
serviceAccountJSON := strings.TrimSpace(cleaned["service_account_json"])
|
||
var serviceAccount struct {
|
||
ProjectID string `json:"project_id"`
|
||
}
|
||
if err := json.Unmarshal([]byte(serviceAccountJSON), &serviceAccount); err != nil {
|
||
return nil, fmt.Errorf("创建 Google Cloud DNS Provider 失败:服务账号 JSON 格式无效:%w", err)
|
||
}
|
||
if serviceAccount.ProjectID == "" {
|
||
serviceAccount.ProjectID = cleaned["project"]
|
||
if serviceAccount.ProjectID == "" {
|
||
return nil, fmt.Errorf("创建 Google Cloud DNS Provider 失败:项目 ID 不能为空")
|
||
}
|
||
var raw map[string]any
|
||
if err := json.Unmarshal([]byte(serviceAccountJSON), &raw); err != nil {
|
||
return nil, fmt.Errorf("创建 Google Cloud DNS Provider 失败:服务账号 JSON 格式无效:%w", err)
|
||
}
|
||
raw["project_id"] = serviceAccount.ProjectID
|
||
data, err := json.Marshal(raw)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建 Google Cloud DNS Provider 失败:编码服务账号 JSON 失败:%w", err)
|
||
}
|
||
serviceAccountJSON = string(data)
|
||
}
|
||
jwtConfig, err := google.JWTConfigFromJSON([]byte(serviceAccountJSON), gdns.NdevClouddnsReadwriteScope)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建 Google Cloud DNS Provider 失败:服务账号凭据无效:%w", err)
|
||
}
|
||
cfg := gcloud.NewDefaultConfig()
|
||
cfg.Project = serviceAccount.ProjectID
|
||
cfg.ZoneID = cleaned["zone_id"]
|
||
cfg.HTTPClient = jwtConfig.Client(context.Background())
|
||
provider, err := gcloud.NewDNSProviderConfig(cfg)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建 Google Cloud DNS Provider 失败:%w", err)
|
||
}
|
||
return provider, nil
|
||
default:
|
||
return nil, fmt.Errorf("%w:%s", ErrProviderUnavailable, descriptor.Name)
|
||
}
|
||
}
|