浏览代码

Add ACME DNS01 challenge support via libdns

世界 2 年之前
父节点
当前提交
d17e93384b

+ 18 - 0
common/tls/acme.go

@@ -9,10 +9,13 @@ import (
 	"strings"
 
 	"github.com/sagernet/sing-box/adapter"
+	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 	E "github.com/sagernet/sing/common/exceptions"
 
 	"github.com/caddyserver/certmagic"
+	"github.com/libdns/alidns"
+	"github.com/libdns/cloudflare"
 	"github.com/mholt/acmez/acme"
 	"go.uber.org/zap"
 	"go.uber.org/zap/zapcore"
@@ -74,6 +77,21 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
 		AltTLSALPNPort:          int(options.AlternativeTLSPort),
 		Logger:                  config.Logger,
 	}
+	if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
+		var solver certmagic.DNS01Solver
+		switch dnsOptions.Provider {
+		case C.DNSProviderAliDNS:
+			solver.DNSProvider = &alidns.Provider{
+				AccKeyID:     dnsOptions.AliDNSOptions.AccessKeyID,
+				AccKeySecret: dnsOptions.AliDNSOptions.AccessKeySecret,
+				RegionID:     dnsOptions.AliDNSOptions.RegionID,
+			}
+		case C.DNSProviderCloudflare:
+			solver.DNSProvider = &cloudflare.Provider{
+				APIToken: dnsOptions.CloudflareOptions.APIToken,
+			}
+		}
+	}
 	if options.ExternalAccount != nil && options.ExternalAccount.KeyID != "" {
 		acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount)
 	}

+ 6 - 0
constant/dns.go

@@ -0,0 +1,6 @@
+package constant
+
+const (
+	DNSProviderAliDNS     = "alidns"
+	DNSProviderCloudflare = "cloudflare"
+)

+ 31 - 0
docs/configuration/shared/dns01_challenge.md

@@ -0,0 +1,31 @@
+### Structure
+
+```json
+{
+  "provider": "",
+  
+  ... // Provider Fields
+}
+```
+
+### Provider Fields
+
+#### Alibaba Cloud DNS
+
+```json
+{
+  "provider": "alidns",
+  "access_key_id": "",
+  "access_key_secret": "",
+  "region_id": ""
+}
+```
+
+#### Cloudflare
+
+```json
+{
+  "provider": "cloudflare",
+  "api_token": ""
+}
+```

+ 31 - 0
docs/configuration/shared/dns01_challenge.zh.md

@@ -0,0 +1,31 @@
+### 结构
+
+```json
+{
+  "provider": "",
+  
+  ... // 提供商字段
+}
+```
+
+### 提供商字段
+
+#### Alibaba Cloud DNS
+
+```json
+{
+  "provider": "alidns",
+  "access_key_id": "",
+  "access_key_secret": "",
+  "region_id": ""
+}
+```
+
+#### Cloudflare
+
+```json
+{
+  "provider": "cloudflare",
+  "api_token": ""
+}
+```

+ 8 - 1
docs/configuration/shared/tls.md

@@ -25,7 +25,8 @@
     "external_account": {
       "key_id": "",
       "mac_key": ""
-    }
+    },
+    "dns01_challenge": {}
   },
   "ech": {
     "enabled": false,
@@ -348,6 +349,12 @@ The key identifier.
 
 The MAC key.
 
+#### dns01_challenge
+
+ACME DNS01 challenge field. If configured, other challenge methods will be disabled.
+
+See [DNS01 Challenge Fields](/configuration/shared/dns01_challenge) for details.
+
 ### Reality Fields
 
 !!! warning ""

+ 8 - 1
docs/configuration/shared/tls.zh.md

@@ -25,7 +25,8 @@
     "external_account": {
       "key_id": "",
       "mac_key": ""
-    }
+    },
+    "dns01_challenge": {}
   },
   "ech": {
     "enabled": false,
@@ -339,6 +340,12 @@ EAB(外部帐户绑定)包含将 ACME 帐户绑定或映射到其他已知
 
 MAC 密钥。
 
+#### dns01_challenge
+
+ACME DNS01 验证字段。如果配置,将禁用其他验证方法。
+
+参阅 [DNS01 验证字段](/configuration/shared/dns01_challenge)。
+
 ### Reality 字段
 
 !!! warning ""

+ 2 - 0
go.mod

@@ -15,6 +15,8 @@ require (
 	github.com/go-chi/render v1.0.3
 	github.com/gofrs/uuid/v5 v5.0.0
 	github.com/insomniacslk/dhcp v0.0.0-20230816195147-b3ca2534940d
+	github.com/libdns/alidns v1.0.3
+	github.com/libdns/cloudflare v0.1.0
 	github.com/logrusorgru/aurora v2.0.3+incompatible
 	github.com/mholt/acmez v1.2.0
 	github.com/miekg/dns v1.1.55

+ 5 - 0
go.sum

@@ -68,6 +68,11 @@ github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZY
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=
+github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
+github.com/libdns/cloudflare v0.1.0 h1:93WkJaGaiXCe353LHEP36kAWCUw0YjFqwhkBkU2/iic=
+github.com/libdns/cloudflare v0.1.0/go.mod h1:a44IP6J1YH6nvcNl1PverfJviADgXUnsozR3a7vBKN8=
+github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
 github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
 github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
 github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=

+ 2 - 0
mkdocs.yml

@@ -71,6 +71,7 @@ nav:
           - Listen Fields: configuration/shared/listen.md
           - Dial Fields: configuration/shared/dial.md
           - TLS: configuration/shared/tls.md
+          - DNS01 Challenge Fields: configuration/shared/dns01_challenge.md
           - Multiplex: configuration/shared/multiplex.md
           - V2Ray Transport: configuration/shared/v2ray-transport.md
           - UDP over TCP: configuration/shared/udp-over-tcp.md
@@ -193,6 +194,7 @@ plugins:
           Shared: 通用
           Listen Fields: 监听字段
           Dial Fields: 拨号字段
+          DNS01 Challenge Fields: DNS01 验证字段
           Multiplex: 多路复用
           V2Ray Transport: V2Ray 传输层
 

+ 59 - 0
option/tls_acme.go

@@ -1,5 +1,11 @@
 package option
 
+import (
+	"github.com/sagernet/sing-box/common/json"
+	C "github.com/sagernet/sing-box/constant"
+	E "github.com/sagernet/sing/common/exceptions"
+)
+
 type InboundACMEOptions struct {
 	Domain                  Listable[string]            `json:"domain,omitempty"`
 	DataDirectory           string                      `json:"data_directory,omitempty"`
@@ -11,9 +17,62 @@ type InboundACMEOptions struct {
 	AlternativeHTTPPort     uint16                      `json:"alternative_http_port,omitempty"`
 	AlternativeTLSPort      uint16                      `json:"alternative_tls_port,omitempty"`
 	ExternalAccount         *ACMEExternalAccountOptions `json:"external_account,omitempty"`
+	DNS01Challenge          *ACMEDNS01ChallengeOptions  `json:"dns01_challenge,omitempty"`
 }
 
 type ACMEExternalAccountOptions struct {
 	KeyID  string `json:"key_id,omitempty"`
 	MACKey string `json:"mac_key,omitempty"`
 }
+
+type _ACMEDNS01ChallengeOptions struct {
+	Provider          string                     `json:"provider,omitempty"`
+	AliDNSOptions     ACMEDNS01AliDNSOptions     `json:"-"`
+	CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"`
+}
+
+type ACMEDNS01ChallengeOptions _ACMEDNS01ChallengeOptions
+
+func (o ACMEDNS01ChallengeOptions) MarshalJSON() ([]byte, error) {
+	var v any
+	switch o.Provider {
+	case C.DNSProviderAliDNS:
+		v = o.AliDNSOptions
+	case C.DNSProviderCloudflare:
+		v = o.CloudflareOptions
+	default:
+		return nil, E.New("unknown provider type: " + o.Provider)
+	}
+	return MarshallObjects((_ACMEDNS01ChallengeOptions)(o), v)
+}
+
+func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error {
+	err := json.Unmarshal(bytes, (*_ACMEDNS01ChallengeOptions)(o))
+	if err != nil {
+		return err
+	}
+	var v any
+	switch o.Provider {
+	case C.DNSProviderAliDNS:
+		v = &o.AliDNSOptions
+	case C.DNSProviderCloudflare:
+		v = &o.CloudflareOptions
+	default:
+		return E.New("unknown provider type: " + o.Provider)
+	}
+	err = UnmarshallExcluded(bytes, (*_ACMEDNS01ChallengeOptions)(o), v)
+	if err != nil {
+		return E.Cause(err, "DNS01 challenge options")
+	}
+	return nil
+}
+
+type ACMEDNS01AliDNSOptions struct {
+	AccessKeyID     string `json:"access_key_id,omitempty"`
+	AccessKeySecret string `json:"access_key_secret,omitempty"`
+	RegionID        string `json:"region_id,omitempty"`
+}
+
+type ACMEDNS01CloudflareOptions struct {
+	APIToken string `json:"api_token,omitempty"`
+}