From d17e93384b67f44f4f623fbb97f900c4ae127fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 16 Sep 2023 21:37:22 +0800 Subject: [PATCH] Add ACME DNS01 challenge support via libdns --- common/tls/acme.go | 18 ++++++ constant/dns.go | 6 ++ docs/configuration/shared/dns01_challenge.md | 31 ++++++++++ .../shared/dns01_challenge.zh.md | 31 ++++++++++ docs/configuration/shared/tls.md | 9 ++- docs/configuration/shared/tls.zh.md | 9 ++- go.mod | 2 + go.sum | 5 ++ mkdocs.yml | 2 + option/tls_acme.go | 59 +++++++++++++++++++ 10 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 constant/dns.go create mode 100644 docs/configuration/shared/dns01_challenge.md create mode 100644 docs/configuration/shared/dns01_challenge.zh.md diff --git a/common/tls/acme.go b/common/tls/acme.go index a881ace0..81aa6b8e 100644 --- a/common/tls/acme.go +++ b/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) } diff --git a/constant/dns.go b/constant/dns.go new file mode 100644 index 00000000..3907b8c1 --- /dev/null +++ b/constant/dns.go @@ -0,0 +1,6 @@ +package constant + +const ( + DNSProviderAliDNS = "alidns" + DNSProviderCloudflare = "cloudflare" +) diff --git a/docs/configuration/shared/dns01_challenge.md b/docs/configuration/shared/dns01_challenge.md new file mode 100644 index 00000000..f9949e16 --- /dev/null +++ b/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": "" +} +``` \ No newline at end of file diff --git a/docs/configuration/shared/dns01_challenge.zh.md b/docs/configuration/shared/dns01_challenge.zh.md new file mode 100644 index 00000000..c942fef0 --- /dev/null +++ b/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": "" +} +``` \ No newline at end of file diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index a5f08240..b0652c07 100644 --- a/docs/configuration/shared/tls.md +++ b/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 "" diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index 0c7243f3..90b28847 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/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 "" diff --git a/go.mod b/go.mod index 95edc167..c87c9c61 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index b6a8d013..cdb1ec54 100644 --- a/go.sum +++ b/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= diff --git a/mkdocs.yml b/mkdocs.yml index 7c29f4f1..5632affa 100644 --- a/mkdocs.yml +++ b/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 传输层 diff --git a/option/tls_acme.go b/option/tls_acme.go index c9c1e0a5..1068237e 100644 --- a/option/tls_acme.go +++ b/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"` +}