修复AnyTLS无法配置证书的错误

This commit is contained in:
CN-JS-HuiBai
2026-04-15 18:57:33 +08:00
parent abc7c0d933
commit a2a3ba12b3
8 changed files with 243 additions and 42 deletions

View File

@@ -17,6 +17,8 @@ import (
"github.com/libdns/acmedns" "github.com/libdns/acmedns"
"github.com/libdns/alidns" "github.com/libdns/alidns"
"github.com/libdns/cloudflare" "github.com/libdns/cloudflare"
"github.com/libdns/dnspod"
"github.com/libdns/tencentcloud"
"github.com/mholt/acmez/v3/acme" "github.com/mholt/acmez/v3/acme"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
@@ -81,7 +83,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
} }
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" { if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
var solver certmagic.DNS01Solver var solver certmagic.DNS01Solver
switch dnsOptions.Provider { switch C.NormalizeACMEDNSProvider(dnsOptions.Provider) {
case C.DNSProviderAliDNS: case C.DNSProviderAliDNS:
solver.DNSProvider = &alidns.Provider{ solver.DNSProvider = &alidns.Provider{
CredentialInfo: alidns.CredentialInfo{ CredentialInfo: alidns.CredentialInfo{
@@ -96,6 +98,17 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
APIToken: dnsOptions.CloudflareOptions.APIToken, APIToken: dnsOptions.CloudflareOptions.APIToken,
ZoneToken: dnsOptions.CloudflareOptions.ZoneToken, ZoneToken: dnsOptions.CloudflareOptions.ZoneToken,
} }
case C.DNSProviderTencentCloud:
solver.DNSProvider = &tencentcloud.Provider{
SecretId: dnsOptions.TencentCloudOptions.SecretID,
SecretKey: dnsOptions.TencentCloudOptions.SecretKey,
SessionToken: dnsOptions.TencentCloudOptions.SessionToken,
Region: dnsOptions.TencentCloudOptions.Region,
}
case C.DNSProviderDNSPod:
solver.DNSProvider = &dnspod.Provider{
APIToken: dnsOptions.DNSPodOptions.APIToken,
}
case C.DNSProviderACMEDNS: case C.DNSProviderACMEDNS:
solver.DNSProvider = &acmedns.Provider{ solver.DNSProvider = &acmedns.Provider{
Username: dnsOptions.ACMEDNSOptions.Username, Username: dnsOptions.ACMEDNSOptions.Username,

View File

@@ -1,5 +1,7 @@
package constant package constant
import "strings"
const ( const (
DefaultDNSTTL = 600 DefaultDNSTTL = 600
) )
@@ -33,4 +35,25 @@ const (
DNSProviderAliDNS = "alidns" DNSProviderAliDNS = "alidns"
DNSProviderCloudflare = "cloudflare" DNSProviderCloudflare = "cloudflare"
DNSProviderACMEDNS = "acmedns" DNSProviderACMEDNS = "acmedns"
DNSProviderTencentCloud = "tencentcloud"
DNSProviderDNSPod = "dnspod"
) )
func NormalizeACMEDNSProvider(provider string) string {
switch strings.ToLower(strings.TrimSpace(provider)) {
case "", DNSProviderAliDNS, DNSProviderCloudflare, DNSProviderACMEDNS:
return strings.ToLower(strings.TrimSpace(provider))
case "aliyun":
return DNSProviderAliDNS
case "cf":
return DNSProviderCloudflare
case "acme-dns":
return DNSProviderACMEDNS
case "tencent", "tencentcloud", "dnspod-tencentcloud", "qcloud":
return DNSProviderTencentCloud
case "dnspod":
return DNSProviderDNSPod
default:
return strings.ToLower(strings.TrimSpace(provider))
}
}

2
go.mod
View File

@@ -20,7 +20,9 @@ require (
github.com/libdns/acmedns v0.5.0 github.com/libdns/acmedns v0.5.0
github.com/libdns/alidns v1.0.6 github.com/libdns/alidns v1.0.6
github.com/libdns/cloudflare v0.2.2 github.com/libdns/cloudflare v0.2.2
github.com/libdns/dnspod v0.0.3
github.com/libdns/libdns v1.1.1 github.com/libdns/libdns v1.1.1
github.com/libdns/tencentcloud v1.4.3
github.com/logrusorgru/aurora v2.0.3+incompatible github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/mdlayher/netlink v1.9.0 github.com/mdlayher/netlink v1.9.0
github.com/metacubex/utls v1.8.4 github.com/metacubex/utls v1.8.4

View File

@@ -28,34 +28,43 @@ type ACMECertificateProviderOptions struct {
} }
type _ACMEProviderDNS01ChallengeOptions struct { type _ACMEProviderDNS01ChallengeOptions struct {
TTL badoption.Duration `json:"ttl,omitempty"` TTL badoption.Duration `json:"ttl,omitempty"`
PropagationDelay badoption.Duration `json:"propagation_delay,omitempty"` PropagationDelay badoption.Duration `json:"propagation_delay,omitempty"`
PropagationTimeout badoption.Duration `json:"propagation_timeout,omitempty"` PropagationTimeout badoption.Duration `json:"propagation_timeout,omitempty"`
Resolvers badoption.Listable[string] `json:"resolvers,omitempty"` Resolvers badoption.Listable[string] `json:"resolvers,omitempty"`
OverrideDomain string `json:"override_domain,omitempty"` OverrideDomain string `json:"override_domain,omitempty"`
Provider string `json:"provider,omitempty"` Provider string `json:"provider,omitempty"`
AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"` AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"`
CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"` CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"`
ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"` ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"`
TencentCloudOptions ACMEDNS01TencentCloudOptions `json:"-"`
DNSPodOptions ACMEDNS01DNSPodOptions `json:"-"`
} }
type ACMEProviderDNS01ChallengeOptions _ACMEProviderDNS01ChallengeOptions type ACMEProviderDNS01ChallengeOptions _ACMEProviderDNS01ChallengeOptions
func (o ACMEProviderDNS01ChallengeOptions) MarshalJSON() ([]byte, error) { func (o ACMEProviderDNS01ChallengeOptions) MarshalJSON() ([]byte, error) {
provider := C.NormalizeACMEDNSProvider(o.Provider)
var v any var v any
switch o.Provider { switch provider {
case C.DNSProviderAliDNS: case C.DNSProviderAliDNS:
v = o.AliDNSOptions v = o.AliDNSOptions
case C.DNSProviderCloudflare: case C.DNSProviderCloudflare:
v = o.CloudflareOptions v = o.CloudflareOptions
case C.DNSProviderACMEDNS: case C.DNSProviderACMEDNS:
v = o.ACMEDNSOptions v = o.ACMEDNSOptions
case C.DNSProviderTencentCloud:
v = o.TencentCloudOptions
case C.DNSProviderDNSPod:
v = o.DNSPodOptions
case "": case "":
return nil, E.New("missing provider type") return nil, E.New("missing provider type")
default: default:
return nil, E.New("unknown provider type: ", o.Provider) return nil, E.New("unknown provider type: ", o.Provider)
} }
return badjson.MarshallObjects((_ACMEProviderDNS01ChallengeOptions)(o), v) copyValue := (_ACMEProviderDNS01ChallengeOptions)(o)
copyValue.Provider = provider
return badjson.MarshallObjects(copyValue, v)
} }
func (o *ACMEProviderDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error { func (o *ACMEProviderDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error {
@@ -63,6 +72,7 @@ func (o *ACMEProviderDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error {
if err != nil { if err != nil {
return err return err
} }
o.Provider = C.NormalizeACMEDNSProvider(o.Provider)
var v any var v any
switch o.Provider { switch o.Provider {
case C.DNSProviderAliDNS: case C.DNSProviderAliDNS:
@@ -71,6 +81,10 @@ func (o *ACMEProviderDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error {
v = &o.CloudflareOptions v = &o.CloudflareOptions
case C.DNSProviderACMEDNS: case C.DNSProviderACMEDNS:
v = &o.ACMEDNSOptions v = &o.ACMEDNSOptions
case C.DNSProviderTencentCloud:
v = &o.TencentCloudOptions
case C.DNSProviderDNSPod:
v = &o.DNSPodOptions
case "": case "":
return E.New("missing provider type") return E.New("missing provider type")
default: default:

View File

@@ -28,29 +28,38 @@ type ACMEExternalAccountOptions struct {
} }
type _ACMEDNS01ChallengeOptions struct { type _ACMEDNS01ChallengeOptions struct {
Provider string `json:"provider,omitempty"` Provider string `json:"provider,omitempty"`
AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"` AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"`
CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"` CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"`
ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"` ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"`
TencentCloudOptions ACMEDNS01TencentCloudOptions `json:"-"`
DNSPodOptions ACMEDNS01DNSPodOptions `json:"-"`
} }
type ACMEDNS01ChallengeOptions _ACMEDNS01ChallengeOptions type ACMEDNS01ChallengeOptions _ACMEDNS01ChallengeOptions
func (o ACMEDNS01ChallengeOptions) MarshalJSON() ([]byte, error) { func (o ACMEDNS01ChallengeOptions) MarshalJSON() ([]byte, error) {
provider := C.NormalizeACMEDNSProvider(o.Provider)
var v any var v any
switch o.Provider { switch provider {
case C.DNSProviderAliDNS: case C.DNSProviderAliDNS:
v = o.AliDNSOptions v = o.AliDNSOptions
case C.DNSProviderCloudflare: case C.DNSProviderCloudflare:
v = o.CloudflareOptions v = o.CloudflareOptions
case C.DNSProviderACMEDNS: case C.DNSProviderACMEDNS:
v = o.ACMEDNSOptions v = o.ACMEDNSOptions
case C.DNSProviderTencentCloud:
v = o.TencentCloudOptions
case C.DNSProviderDNSPod:
v = o.DNSPodOptions
case "": case "":
return nil, E.New("missing provider type") return nil, E.New("missing provider type")
default: default:
return nil, E.New("unknown provider type: " + o.Provider) return nil, E.New("unknown provider type: " + o.Provider)
} }
return badjson.MarshallObjects((_ACMEDNS01ChallengeOptions)(o), v) copyValue := (_ACMEDNS01ChallengeOptions)(o)
copyValue.Provider = provider
return badjson.MarshallObjects(copyValue, v)
} }
func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error { func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error {
@@ -58,6 +67,7 @@ func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error {
if err != nil { if err != nil {
return err return err
} }
o.Provider = C.NormalizeACMEDNSProvider(o.Provider)
var v any var v any
switch o.Provider { switch o.Provider {
case C.DNSProviderAliDNS: case C.DNSProviderAliDNS:
@@ -66,6 +76,10 @@ func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error {
v = &o.CloudflareOptions v = &o.CloudflareOptions
case C.DNSProviderACMEDNS: case C.DNSProviderACMEDNS:
v = &o.ACMEDNSOptions v = &o.ACMEDNSOptions
case C.DNSProviderTencentCloud:
v = &o.TencentCloudOptions
case C.DNSProviderDNSPod:
v = &o.DNSPodOptions
default: default:
return E.New("unknown provider type: " + o.Provider) return E.New("unknown provider type: " + o.Provider)
} }
@@ -94,3 +108,14 @@ type ACMEDNS01ACMEDNSOptions struct {
Subdomain string `json:"subdomain,omitempty"` Subdomain string `json:"subdomain,omitempty"`
ServerURL string `json:"server_url,omitempty"` ServerURL string `json:"server_url,omitempty"`
} }
type ACMEDNS01TencentCloudOptions struct {
SecretID string `json:"secret_id,omitempty"`
SecretKey string `json:"secret_key,omitempty"`
SessionToken string `json:"session_token,omitempty"`
Region string `json:"region,omitempty"`
}
type ACMEDNS01DNSPodOptions struct {
APIToken string `json:"api_token,omitempty"`
}

View File

@@ -30,7 +30,9 @@ import (
"github.com/caddyserver/zerossl" "github.com/caddyserver/zerossl"
"github.com/libdns/alidns" "github.com/libdns/alidns"
"github.com/libdns/cloudflare" "github.com/libdns/cloudflare"
"github.com/libdns/dnspod"
"github.com/libdns/libdns" "github.com/libdns/libdns"
"github.com/libdns/tencentcloud"
"github.com/mholt/acmez/v3/acme" "github.com/mholt/acmez/v3/acme"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
@@ -224,7 +226,7 @@ func newDNSSolver(dnsOptions *option.ACMEProviderDNS01ChallengeOptions, logger *
Logger: logger.Named("dns_manager"), Logger: logger.Named("dns_manager"),
}, },
} }
switch dnsOptions.Provider { switch C.NormalizeACMEDNSProvider(dnsOptions.Provider) {
case C.DNSProviderAliDNS: case C.DNSProviderAliDNS:
solver.DNSProvider = &alidns.Provider{ solver.DNSProvider = &alidns.Provider{
CredentialInfo: alidns.CredentialInfo{ CredentialInfo: alidns.CredentialInfo{
@@ -240,6 +242,17 @@ func newDNSSolver(dnsOptions *option.ACMEProviderDNS01ChallengeOptions, logger *
ZoneToken: dnsOptions.CloudflareOptions.ZoneToken, ZoneToken: dnsOptions.CloudflareOptions.ZoneToken,
HTTPClient: httpClient, HTTPClient: httpClient,
} }
case C.DNSProviderTencentCloud:
solver.DNSProvider = &tencentcloud.Provider{
SecretId: dnsOptions.TencentCloudOptions.SecretID,
SecretKey: dnsOptions.TencentCloudOptions.SecretKey,
SessionToken: dnsOptions.TencentCloudOptions.SessionToken,
Region: dnsOptions.TencentCloudOptions.Region,
}
case C.DNSProviderDNSPod:
solver.DNSProvider = &dnspod.Provider{
APIToken: dnsOptions.DNSPodOptions.APIToken,
}
case C.DNSProviderACMEDNS: case C.DNSProviderACMEDNS:
solver.DNSProvider = &acmeDNSProvider{ solver.DNSProvider = &acmeDNSProvider{
username: dnsOptions.ACMEDNSOptions.Username, username: dnsOptions.ACMEDNSOptions.Username,

View File

@@ -467,9 +467,9 @@ func hasUsableServerTLS(tlsOptions option.InboundTLSOptions) bool {
(len(tlsOptions.Certificate) > 0 && len(tlsOptions.Key) > 0) (len(tlsOptions.Certificate) > 0 && len(tlsOptions.Key) > 0)
} }
func applyACMEConfig(tlsOptions *option.InboundTLSOptions, certConfig *XCertConfig, autoTLS bool, domain string, listenPort int) bool { func applyACMEConfigDetailed(tlsOptions *option.InboundTLSOptions, certConfig *XCertConfig, autoTLS bool, domain string, listenPort int) (bool, string) {
if !autoTLS && certConfig == nil { if !autoTLS && certConfig == nil {
return false return false, "acme disabled: no auto_tls and no cert_config"
} }
mode := "" mode := ""
if certConfig != nil { if certConfig != nil {
@@ -479,54 +479,98 @@ func applyACMEConfig(tlsOptions *option.InboundTLSOptions, certConfig *XCertConf
mode = "http" mode = "http"
} }
if mode != "http" && mode != "dns" { if mode != "http" && mode != "dns" {
return false return false, "unsupported cert_mode: " + mode
} }
domain = strings.TrimSpace(domain) domain = strings.TrimSpace(domain)
if domain == "" { if domain == "" {
return false return false, "missing domain/server_name for ACME"
} }
acmeOptions := &option.InboundACMEOptions{ acmeOptions := &option.InboundACMEOptions{
Domain: badoption.Listable[string]{domain}, Domain: badoption.Listable[string]{domain},
DataDirectory: "acme", DataDirectory: "acme",
DefaultServerName: domain, DefaultServerName: domain,
DisableHTTPChallenge: true,
} }
if certConfig != nil { if certConfig != nil {
acmeOptions.Email = strings.TrimSpace(certConfig.Email) acmeOptions.Email = strings.TrimSpace(certConfig.Email)
} }
if listenPort > 0 && listenPort != 443 { switch mode {
acmeOptions.AlternativeTLSPort = uint16(listenPort) case "http":
} acmeOptions.DisableHTTPChallenge = false
if mode == "dns" && certConfig != nil { acmeOptions.DisableTLSALPNChallenge = true
dnsProvider := strings.ToLower(strings.TrimSpace(certConfig.DNSProvider)) if certConfig != nil && certConfig.HTTPPort > 0 && certConfig.HTTPPort != 80 {
if dnsProvider == "" { acmeOptions.AlternativeHTTPPort = uint16(certConfig.HTTPPort)
return false
} }
case "dns":
acmeOptions.DisableHTTPChallenge = true acmeOptions.DisableHTTPChallenge = true
acmeOptions.DisableTLSALPNChallenge = true acmeOptions.DisableTLSALPNChallenge = true
if listenPort > 0 && listenPort != 443 {
acmeOptions.AlternativeTLSPort = uint16(listenPort)
}
}
if mode == "dns" && certConfig != nil {
dnsProvider := C.NormalizeACMEDNSProvider(certConfig.DNSProvider)
if dnsProvider == "" {
return false, "missing dns_provider for cert_mode=dns"
}
dns01 := &option.ACMEDNS01ChallengeOptions{Provider: dnsProvider} dns01 := &option.ACMEDNS01ChallengeOptions{Provider: dnsProvider}
switch dnsProvider { switch dnsProvider {
case C.DNSProviderCloudflare: case C.DNSProviderCloudflare:
dns01.CloudflareOptions.APIToken = firstNonEmpty(certConfig.DNSEnv["CF_API_TOKEN"], certConfig.DNSEnv["CLOUDFLARE_API_TOKEN"]) dns01.CloudflareOptions.APIToken = firstNonEmpty(certConfig.DNSEnv["CF_API_TOKEN"], certConfig.DNSEnv["CLOUDFLARE_API_TOKEN"])
dns01.CloudflareOptions.ZoneToken = firstNonEmpty(certConfig.DNSEnv["CF_ZONE_TOKEN"], certConfig.DNSEnv["CLOUDFLARE_ZONE_TOKEN"]) dns01.CloudflareOptions.ZoneToken = firstNonEmpty(certConfig.DNSEnv["CF_ZONE_TOKEN"], certConfig.DNSEnv["CLOUDFLARE_ZONE_TOKEN"])
if dns01.CloudflareOptions.APIToken == "" && dns01.CloudflareOptions.ZoneToken == "" {
return false, "cloudflare dns challenge requires CF_API_TOKEN/CLOUDFLARE_API_TOKEN or CF_ZONE_TOKEN/CLOUDFLARE_ZONE_TOKEN"
}
case C.DNSProviderAliDNS: case C.DNSProviderAliDNS:
dns01.AliDNSOptions.AccessKeyID = firstNonEmpty(certConfig.DNSEnv["ALICLOUD_ACCESS_KEY_ID"], certConfig.DNSEnv["ALI_ACCESS_KEY_ID"]) dns01.AliDNSOptions.AccessKeyID = firstNonEmpty(certConfig.DNSEnv["ALICLOUD_ACCESS_KEY_ID"], certConfig.DNSEnv["ALI_ACCESS_KEY_ID"])
dns01.AliDNSOptions.AccessKeySecret = firstNonEmpty(certConfig.DNSEnv["ALICLOUD_ACCESS_KEY_SECRET"], certConfig.DNSEnv["ALI_ACCESS_KEY_SECRET"]) dns01.AliDNSOptions.AccessKeySecret = firstNonEmpty(certConfig.DNSEnv["ALICLOUD_ACCESS_KEY_SECRET"], certConfig.DNSEnv["ALI_ACCESS_KEY_SECRET"])
dns01.AliDNSOptions.RegionID = firstNonEmpty(certConfig.DNSEnv["ALICLOUD_REGION_ID"], certConfig.DNSEnv["ALI_REGION_ID"]) dns01.AliDNSOptions.RegionID = firstNonEmpty(certConfig.DNSEnv["ALICLOUD_REGION_ID"], certConfig.DNSEnv["ALI_REGION_ID"])
dns01.AliDNSOptions.SecurityToken = firstNonEmpty(certConfig.DNSEnv["ALICLOUD_SECURITY_TOKEN"], certConfig.DNSEnv["ALI_SECURITY_TOKEN"]) dns01.AliDNSOptions.SecurityToken = firstNonEmpty(certConfig.DNSEnv["ALICLOUD_SECURITY_TOKEN"], certConfig.DNSEnv["ALI_SECURITY_TOKEN"])
if dns01.AliDNSOptions.AccessKeyID == "" || dns01.AliDNSOptions.AccessKeySecret == "" {
return false, "alidns dns challenge requires ALICLOUD_ACCESS_KEY_ID and ALICLOUD_ACCESS_KEY_SECRET"
}
case C.DNSProviderTencentCloud:
dns01.TencentCloudOptions.SecretID = firstNonEmpty(certConfig.DNSEnv["TENCENTCLOUD_SECRET_ID"], certConfig.DNSEnv["TENCENT_SECRET_ID"], certConfig.DNSEnv["SECRET_ID"])
dns01.TencentCloudOptions.SecretKey = firstNonEmpty(certConfig.DNSEnv["TENCENTCLOUD_SECRET_KEY"], certConfig.DNSEnv["TENCENT_SECRET_KEY"], certConfig.DNSEnv["SECRET_KEY"])
dns01.TencentCloudOptions.SessionToken = firstNonEmpty(certConfig.DNSEnv["TENCENTCLOUD_SESSION_TOKEN"], certConfig.DNSEnv["TENCENT_SESSION_TOKEN"], certConfig.DNSEnv["SESSION_TOKEN"])
dns01.TencentCloudOptions.Region = firstNonEmpty(certConfig.DNSEnv["TENCENTCLOUD_REGION"], certConfig.DNSEnv["TENCENT_REGION"], certConfig.DNSEnv["REGION"])
if dns01.TencentCloudOptions.SecretID == "" || dns01.TencentCloudOptions.SecretKey == "" {
return false, "tencentcloud dns challenge requires TENCENTCLOUD_SECRET_ID and TENCENTCLOUD_SECRET_KEY"
}
case C.DNSProviderDNSPod:
dns01.DNSPodOptions.APIToken = firstNonEmpty(certConfig.DNSEnv["DNSPOD_TOKEN"], certConfig.DNSEnv["API_TOKEN"])
if dns01.DNSPodOptions.APIToken == "" {
tencentSecretID := firstNonEmpty(certConfig.DNSEnv["TENCENTCLOUD_SECRET_ID"], certConfig.DNSEnv["TENCENT_SECRET_ID"], certConfig.DNSEnv["SECRET_ID"])
tencentSecretKey := firstNonEmpty(certConfig.DNSEnv["TENCENTCLOUD_SECRET_KEY"], certConfig.DNSEnv["TENCENT_SECRET_KEY"], certConfig.DNSEnv["SECRET_KEY"])
if tencentSecretID == "" || tencentSecretKey == "" {
return false, "dnspod dns challenge requires DNSPOD_TOKEN or TencentCloud SecretID/SecretKey"
}
dns01.Provider = C.DNSProviderTencentCloud
dns01.TencentCloudOptions.SecretID = tencentSecretID
dns01.TencentCloudOptions.SecretKey = tencentSecretKey
dns01.TencentCloudOptions.SessionToken = firstNonEmpty(certConfig.DNSEnv["TENCENTCLOUD_SESSION_TOKEN"], certConfig.DNSEnv["TENCENT_SESSION_TOKEN"], certConfig.DNSEnv["SESSION_TOKEN"])
dns01.TencentCloudOptions.Region = firstNonEmpty(certConfig.DNSEnv["TENCENTCLOUD_REGION"], certConfig.DNSEnv["TENCENT_REGION"], certConfig.DNSEnv["REGION"])
}
case C.DNSProviderACMEDNS: case C.DNSProviderACMEDNS:
dns01.ACMEDNSOptions.Username = certConfig.DNSEnv["ACMEDNS_USERNAME"] dns01.ACMEDNSOptions.Username = certConfig.DNSEnv["ACMEDNS_USERNAME"]
dns01.ACMEDNSOptions.Password = certConfig.DNSEnv["ACMEDNS_PASSWORD"] dns01.ACMEDNSOptions.Password = certConfig.DNSEnv["ACMEDNS_PASSWORD"]
dns01.ACMEDNSOptions.Subdomain = certConfig.DNSEnv["ACMEDNS_SUBDOMAIN"] dns01.ACMEDNSOptions.Subdomain = certConfig.DNSEnv["ACMEDNS_SUBDOMAIN"]
dns01.ACMEDNSOptions.ServerURL = certConfig.DNSEnv["ACMEDNS_SERVER_URL"] dns01.ACMEDNSOptions.ServerURL = certConfig.DNSEnv["ACMEDNS_SERVER_URL"]
if dns01.ACMEDNSOptions.Username == "" || dns01.ACMEDNSOptions.Password == "" || dns01.ACMEDNSOptions.Subdomain == "" || dns01.ACMEDNSOptions.ServerURL == "" {
return false, "acmedns dns challenge requires username, password, subdomain and server_url"
}
default: default:
return false return false, "unsupported dns_provider: " + dnsProvider
} }
acmeOptions.DNS01Challenge = dns01 acmeOptions.DNS01Challenge = dns01
} }
tlsOptions.ACME = acmeOptions tlsOptions.ACME = acmeOptions
return true return true, ""
}
func applyACMEConfig(tlsOptions *option.InboundTLSOptions, certConfig *XCertConfig, autoTLS bool, domain string, listenPort int) bool {
ok, _ := applyACMEConfigDetailed(tlsOptions, certConfig, autoTLS, domain, listenPort)
return ok
} }
func mergedTLSSettings(inner XInnerConfig, config *XNodeConfig) *XTLSSettings { func mergedTLSSettings(inner XInnerConfig, config *XNodeConfig) *XTLSSettings {
@@ -1067,11 +1111,12 @@ func (s *Service) setupNode() error {
autoTLSDomain = tlsSettings.ServerName autoTLSDomain = tlsSettings.ServerName
} }
hasACME := false hasACME := false
acmeReason := ""
if !hasCertificate { if !hasCertificate {
hasACME = applyACMEConfig(&tlsOptions, certConfig, inner.AutoTLS || configAutoTLS, autoTLSDomain, inner.Port) hasACME, acmeReason = applyACMEConfigDetailed(&tlsOptions, certConfig, inner.AutoTLS || configAutoTLS, autoTLSDomain, inner.Port)
} }
if certConfig != nil && !hasCertificate && !hasACME && certConfig.CertMode != "" && certConfig.CertMode != "none" { if certConfig != nil && !hasCertificate && !hasACME && certConfig.CertMode != "" && certConfig.CertMode != "none" {
s.logger.Warn("Xboard cert_config present but unsupported or incomplete for local TLS. cert_mode=", certConfig.CertMode) s.logger.Warn("Xboard cert_config present but unsupported or incomplete for local TLS. cert_mode=", certConfig.CertMode, ", reason=", acmeReason)
} }
if hasACME { if hasACME {
s.logger.Info("Xboard ACME configured for domain ", autoTLSDomain) s.logger.Info("Xboard ACME configured for domain ", autoTLSDomain)

View File

@@ -3,6 +3,7 @@ package xboard
import ( import (
"testing" "testing"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/json/badoption" "github.com/sagernet/sing/common/json/badoption"
) )
@@ -158,11 +159,11 @@ func TestApplyACMEConfigFromAutoTLS(t *testing.T) {
if len(tlsOptions.ACME.Domain) != 1 || tlsOptions.ACME.Domain[0] != "example.com" { if len(tlsOptions.ACME.Domain) != 1 || tlsOptions.ACME.Domain[0] != "example.com" {
t.Fatalf("ACME domains = %+v", tlsOptions.ACME.Domain) t.Fatalf("ACME domains = %+v", tlsOptions.ACME.Domain)
} }
if tlsOptions.ACME.AlternativeTLSPort != 8443 { if tlsOptions.ACME.DisableHTTPChallenge {
t.Fatalf("AlternativeTLSPort = %d, want 8443", tlsOptions.ACME.AlternativeTLSPort) t.Fatal("DisableHTTPChallenge should be false for auto_tls/http mode")
} }
if !tlsOptions.ACME.DisableHTTPChallenge { if !tlsOptions.ACME.DisableTLSALPNChallenge {
t.Fatal("DisableHTTPChallenge should be true for inline ACME") t.Fatal("DisableTLSALPNChallenge should be true for auto_tls/http mode")
} }
} }
@@ -193,6 +194,71 @@ func TestApplyACMEConfigFromDNSCertMode(t *testing.T) {
} }
} }
func TestApplyACMEConfigFromTencentCloudDNSCertMode(t *testing.T) {
var tlsOptions option.InboundTLSOptions
ok := applyACMEConfig(&tlsOptions, &XCertConfig{
CertMode: "dns",
Domain: "code.littlediary.cn",
DNSProvider: "tencentcloud",
DNSEnv: map[string]string{
"TENCENTCLOUD_SECRET_ID": "sid",
"TENCENTCLOUD_SECRET_KEY": "skey",
},
}, false, "code.littlediary.cn", 45365)
if !ok {
t.Fatal("applyACMEConfig() returned false")
}
if tlsOptions.ACME == nil || tlsOptions.ACME.DNS01Challenge == nil {
t.Fatal("DNS01Challenge not configured")
}
if tlsOptions.ACME.DNS01Challenge.Provider != C.DNSProviderTencentCloud {
t.Fatalf("DNS provider = %q", tlsOptions.ACME.DNS01Challenge.Provider)
}
if tlsOptions.ACME.DNS01Challenge.TencentCloudOptions.SecretID != "sid" {
t.Fatalf("TencentCloud SecretID = %q", tlsOptions.ACME.DNS01Challenge.TencentCloudOptions.SecretID)
}
if tlsOptions.ACME.DNS01Challenge.TencentCloudOptions.SecretKey != "skey" {
t.Fatalf("TencentCloud SecretKey = %q", tlsOptions.ACME.DNS01Challenge.TencentCloudOptions.SecretKey)
}
if tlsOptions.ACME.AlternativeTLSPort != 45365 {
t.Fatalf("AlternativeTLSPort = %d, want 45365", tlsOptions.ACME.AlternativeTLSPort)
}
}
func TestApplyACMEConfigFromDNSPodAliasWithTencentCredentials(t *testing.T) {
var tlsOptions option.InboundTLSOptions
ok := applyACMEConfig(&tlsOptions, &XCertConfig{
CertMode: "dns",
Domain: "code.littlediary.cn",
DNSProvider: "dnspod",
DNSEnv: map[string]string{
"TENCENTCLOUD_SECRET_ID": "sid",
"TENCENTCLOUD_SECRET_KEY": "skey",
},
}, false, "code.littlediary.cn", 443)
if !ok {
t.Fatal("applyACMEConfig() returned false")
}
if tlsOptions.ACME == nil || tlsOptions.ACME.DNS01Challenge == nil {
t.Fatal("DNS01Challenge not configured")
}
if tlsOptions.ACME.DNS01Challenge.Provider != C.DNSProviderTencentCloud {
t.Fatalf("DNS provider = %q, want %q", tlsOptions.ACME.DNS01Challenge.Provider, C.DNSProviderTencentCloud)
}
}
func TestMergedTLSSettingsUsesTopLevelServerName(t *testing.T) {
tlsSettings := mergedTLSSettings(XInnerConfig{}, &XNodeConfig{
ServerName: "code.littlediary.cn",
})
if tlsSettings == nil {
t.Fatal("mergedTLSSettings() returned nil")
}
if tlsSettings.ServerName != "code.littlediary.cn" {
t.Fatalf("ServerName = %q, want %q", tlsSettings.ServerName, "code.littlediary.cn")
}
}
func TestHasUsableServerTLS(t *testing.T) { func TestHasUsableServerTLS(t *testing.T) {
if hasUsableServerTLS(option.InboundTLSOptions{}) { if hasUsableServerTLS(option.InboundTLSOptions{}) {
t.Fatal("empty TLS options should not be usable") t.Fatal("empty TLS options should not be usable")