From 9b15893fc2f1d83a2551727d9bf6e4f02044a9b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 26 Mar 2026 15:19:26 +0800 Subject: [PATCH] Add ACME profile support for IP address certificates Auto-select `shortlived` profile for Let's Encrypt when domain list contains IP addresses. Expose `profile` option to allow manual override for custom CA servers. --- common/tls/acme.go | 11 +++++++++++ .../shared/certificate-provider/acme.md | 12 ++++++++++++ .../shared/certificate-provider/acme.zh.md | 12 ++++++++++++ option/acme.go | 1 + option/tls_acme.go | 1 + service/acme/service.go | 11 +++++++++++ 6 files changed, 48 insertions(+) diff --git a/common/tls/acme.go b/common/tls/acme.go index d576fc6b1..7491255a1 100644 --- a/common/tls/acme.go +++ b/common/tls/acme.go @@ -69,10 +69,21 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound Storage: storage, Logger: zapLogger, } + profile := options.Profile + if profile == "" && acmeServer == certmagic.LetsEncryptProductionCA { + for _, domain := range options.Domain { + if certmagic.SubjectIsIP(domain) { + profile = "shortlived" + break + } + } + } + acmeConfig := certmagic.ACMEIssuer{ CA: acmeServer, Email: options.Email, Agreed: true, + Profile: profile, DisableHTTPChallenge: options.DisableHTTPChallenge, DisableTLSALPNChallenge: options.DisableTLSALPNChallenge, AltHTTPPort: int(options.AlternativeHTTPPort), diff --git a/docs/configuration/shared/certificate-provider/acme.md b/docs/configuration/shared/certificate-provider/acme.md index 440ed1568..8d595f319 100644 --- a/docs/configuration/shared/certificate-provider/acme.md +++ b/docs/configuration/shared/certificate-provider/acme.md @@ -6,6 +6,7 @@ icon: material/new-box :material-plus: [account_key](#account_key) :material-plus: [key_type](#key_type) + :material-plus: [profile](#profile) :material-plus: [detour](#detour) # ACME @@ -37,6 +38,7 @@ icon: material/new-box }, "dns01_challenge": {}, "key_type": "", + "profile": "", "detour": "" } ``` @@ -141,6 +143,16 @@ The private key type to generate for new certificates. | `rsa2048` | RSA | | `rsa4096` | RSA | +#### profile + +!!! question "Since sing-box 1.14.0" + +The ACME profile name to use for certificate orders. + +See [ACME Profiles](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/) for details. + +When using Let's Encrypt with IP address identifiers, the `shortlived` profile is automatically selected if not set. + #### detour !!! question "Since sing-box 1.14.0" diff --git a/docs/configuration/shared/certificate-provider/acme.zh.md b/docs/configuration/shared/certificate-provider/acme.zh.md index d95930a55..1679dcaca 100644 --- a/docs/configuration/shared/certificate-provider/acme.zh.md +++ b/docs/configuration/shared/certificate-provider/acme.zh.md @@ -6,6 +6,7 @@ icon: material/new-box :material-plus: [account_key](#account_key) :material-plus: [key_type](#key_type) + :material-plus: [profile](#profile) :material-plus: [detour](#detour) # ACME @@ -37,6 +38,7 @@ icon: material/new-box }, "dns01_challenge": {}, "key_type": "", + "profile": "", "detour": "" } ``` @@ -136,6 +138,16 @@ ACME DNS01 质询字段。如果配置,将禁用其他质询方法。 | `rsa2048` | RSA | | `rsa4096` | RSA | +#### profile + +!!! question "自 sing-box 1.14.0 起" + +用于证书订单的 ACME 配置文件名称。 + +参阅 [ACME Profiles](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/)。 + +当使用 Let's Encrypt 且包含 IP 地址标识符时,如果未设置,将自动选择 `shortlived` 配置文件。 + #### detour !!! question "自 sing-box 1.14.0 起" diff --git a/option/acme.go b/option/acme.go index ea9349b72..27fd94a8c 100644 --- a/option/acme.go +++ b/option/acme.go @@ -24,6 +24,7 @@ type ACMECertificateProviderOptions struct { ExternalAccount *ACMEExternalAccountOptions `json:"external_account,omitempty"` DNS01Challenge *ACMEProviderDNS01ChallengeOptions `json:"dns01_challenge,omitempty"` KeyType ACMEKeyType `json:"key_type,omitempty"` + Profile string `json:"profile,omitempty"` Detour string `json:"detour,omitempty"` } diff --git a/option/tls_acme.go b/option/tls_acme.go index 6dd8fa708..636abc6ec 100644 --- a/option/tls_acme.go +++ b/option/tls_acme.go @@ -20,6 +20,7 @@ type InboundACMEOptions struct { AlternativeTLSPort uint16 `json:"alternative_tls_port,omitempty"` ExternalAccount *ACMEExternalAccountOptions `json:"external_account,omitempty"` DNS01Challenge *ACMEDNS01ChallengeOptions `json:"dns01_challenge,omitempty"` + Profile string `json:"profile,omitempty"` } type ACMEExternalAccountOptions struct { diff --git a/service/acme/service.go b/service/acme/service.go index 8286a1971..73494ceb7 100644 --- a/service/acme/service.go +++ b/service/acme/service.go @@ -114,11 +114,22 @@ func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag s config.KeySource = certmagic.StandardKeyGenerator{KeyType: keyType} } + profile := options.Profile + if profile == "" && acmeServer == certmagic.LetsEncryptProductionCA { + for _, domain := range options.Domain { + if certmagic.SubjectIsIP(domain) { + profile = "shortlived" + break + } + } + } + acmeIssuer := certmagic.ACMEIssuer{ CA: acmeServer, Email: options.Email, AccountKeyPEM: options.AccountKey, Agreed: true, + Profile: profile, DisableHTTPChallenge: options.DisableHTTPChallenge, DisableTLSALPNChallenge: options.DisableTLSALPNChallenge, AltHTTPPort: int(options.AlternativeHTTPPort),