From 3bb2cfb6ec6662ed39bf3856f9e4a7bc8cae3124 Mon Sep 17 00:00:00 2001 From: rowanchen-com Date: Sat, 23 May 2026 21:53:44 +0800 Subject: [PATCH 1/5] feat(ssl): upgrade go-acme/lego to v5.1.0 lego v5 introduces several breaking API changes that touch every entry point we use. This commit migrates 1Panel's SSL stack to v5 and adds a data migration so existing certificates keep auto-renewing without operator action. Code changes: - AcmeUser.Key is now a crypto.Signer (was crypto.PrivateKey); same for the key returned by GetPrivateKeyByType. Account registration is now an *acme.ExtendedAccount (was *registration.Resource). - A new parsePrivateKeyPEM helper accepts both v4 (SEC1 EC, PKCS#1 RSA) and v5 (PKCS#8) key formats, so account/certificate keys persisted under v4 keep loading after the upgrade. - Obtain/ObtainForCSR/Revoke now require a context.Context. - dns01.AddRecursiveNameservers and dns01.AddDNSTimeout were removed. Recursive nameservers and timeout are now passed via dns01.SetDefaultClient(dns01.NewClient(&dns01.Options{...})). - ObtainRequest disables the CN by default in v5; we set EnableCommonName: true to preserve the v4 behaviour for legacy clients (Java keystores, embedded routers, etc.) that still rely on the CommonName field. - lego.CertificateConfig.KeyType was removed (the key type is taken from the user's account); the matching code path is dropped. - certificate.Resource.Domain was renamed to Domains (now []string). - log.Logger is now an *slog.Logger; switch website_ssl.go over to slog with a TextHandler that writes to the existing file logger. KeyType migration: - certcrypto.KeyType string values were renamed in v5: P256->EC256, P384->EC384, 2048->RSA2048, 3072->RSA3072, 4096->RSA4096, 8192->RSA8192. A gormigrate migration rewrites the key_type column in website_acme_accounts, website_ssls and website_cas on first start, so old certificates renew under their new names automatically. - A normalizeKeyType helper falls back to the same mapping at runtime, in case the database migration has not yet run. - Frontend KeyTypes select options and four form-initial keyType defaults are updated to the v5 form so newly created accounts also use the new strings. Build and vet pass on linux/amd64 (GOOS=linux GOARCH=amd64 go build/vet ./...). --- agent/app/service/website_acme_account.go | 2 +- agent/app/service/website_ca.go | 2 +- agent/app/service/website_ssl.go | 14 +- agent/go.mod | 16 +- agent/go.sum | 40 ++--- agent/init/migration/migrate.go | 1 + agent/init/migration/migrations/init.go | 53 ++++++ agent/utils/ssl/acme.go | 163 +++++++++++------- agent/utils/ssl/client.go | 53 ++++-- agent/utils/ssl/dns_provider.go | 64 +++---- agent/utils/ssl/manual_client.go | 26 +-- frontend/src/global/mimetype.ts | 10 +- .../website/ssl/acme-account/create/index.vue | 2 +- .../src/views/website/ssl/ca/create/index.vue | 2 +- .../src/views/website/ssl/ca/obtain/index.vue | 2 +- .../src/views/website/ssl/create/index.vue | 2 +- 16 files changed, 271 insertions(+), 181 deletions(-) diff --git a/agent/app/service/website_acme_account.go b/agent/app/service/website_acme_account.go index af7fc5b29d16..5d5367c87ebe 100644 --- a/agent/app/service/website_acme_account.go +++ b/agent/app/service/website_acme_account.go @@ -68,7 +68,7 @@ func (w WebsiteAcmeAccountService) Create(create request.WebsiteAcmeAccountCreat return nil, err } acmeAccount.PrivateKey = string(privateKey) - acmeAccount.URL = client.User.Registration.URI + acmeAccount.URL = client.User.Registration.Location if err := websiteAcmeRepo.Create(*acmeAccount); err != nil { return nil, err diff --git a/agent/app/service/website_ca.go b/agent/app/service/website_ca.go index e6f899cf1e4c..6b8ffa0906b2 100644 --- a/agent/app/service/website_ca.go +++ b/agent/app/service/website_ca.go @@ -30,7 +30,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/i18n" "github.com/1Panel-dev/1Panel/agent/utils/files" "github.com/1Panel-dev/1Panel/agent/utils/ssl" - "github.com/go-acme/lego/v4/certcrypto" + "github.com/go-acme/lego/v5/certcrypto" ) type WebsiteCAService struct { diff --git a/agent/app/service/website_ssl.go b/agent/app/service/website_ssl.go index 92ac08aaa78e..51c432a86008 100644 --- a/agent/app/service/website_ssl.go +++ b/agent/app/service/website_ssl.go @@ -7,14 +7,15 @@ import ( "fmt" "io" "log" + "log/slog" "os" "path" "strconv" "strings" "time" - "github.com/go-acme/lego/v4/certificate" - legoLogger "github.com/go-acme/lego/v4/log" + "github.com/go-acme/lego/v5/certificate" + legoLogger "github.com/go-acme/lego/v5/log" "github.com/jinzhu/gorm" "github.com/1Panel-dev/1Panel/agent/app/dto/request" @@ -343,7 +344,10 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error { if logFile != nil { defer logFile.Close() } - legoLogger.Logger = logger + // lego v5 switched to slog. Bridge it to the existing *log.Logger + // so the SSL apply log is still written to the per-domain file + // under SSLLogDir. + legoLogger.SetDefault(slog.New(slog.NewTextHandler(logger.Writer(), nil))) startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)}) if websiteSSL.Provider == constant.DNSAccount { startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type}) @@ -470,7 +474,9 @@ func handleError(websiteSSL *model.WebsiteSSL, err error) { websiteSSL.Status = constant.SSLApplyError } websiteSSL.Message = err.Error() - legoLogger.Logger.Println(i18n.GetErrMsg("ApplySSLFailed", map[string]interface{}{"domain": websiteSSL.PrimaryDomain, "detail": err.Error()})) + // lego v5 uses slog; use the same global default logger to write the + // failure message to the SSL log. + legoLogger.Default().Error(i18n.GetErrMsg("ApplySSLFailed", map[string]interface{}{"domain": websiteSSL.PrimaryDomain, "detail": err.Error()})) _ = websiteSSLRepo.Save(websiteSSL) } diff --git a/agent/go.mod b/agent/go.mod index 5ee37d038879..9f8d19407ab3 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -14,7 +14,7 @@ require ( github.com/fsnotify/fsnotify v1.9.0 github.com/gin-gonic/gin v1.12.0 github.com/glebarez/sqlite v1.11.0 - github.com/go-acme/lego/v4 v4.35.2 + github.com/go-acme/lego/v5 v5.1.0 github.com/go-gormigrate/gormigrate/v2 v2.1.5 github.com/go-playground/validator/v10 v10.30.2 github.com/go-redis/redis v6.15.9+incompatible @@ -58,7 +58,7 @@ require ( golang.org/x/text v0.37.0 golang.org/x/time v0.15.0 google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478 - gopkg.in/ini.v1 v1.67.1 + gopkg.in/ini.v1 v1.67.2 gopkg.in/yaml.v3 v3.0.1 gorm.io/gorm v1.31.1 ) @@ -84,13 +84,13 @@ require ( github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect - github.com/aws/aws-sdk-go-v2/service/route53 v1.62.6 // indirect + github.com/aws/aws-sdk-go-v2/service/route53 v1.62.7 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect github.com/aws/smithy-go v1.25.1 // indirect - github.com/baidubce/bce-sdk-go v0.9.265 // indirect + github.com/baidubce/bce-sdk-go v0.9.266 // indirect github.com/bodgit/plumbing v1.3.0 // indirect github.com/bodgit/sevenzip v1.6.1 // indirect github.com/bodgit/windows v1.0.1 // indirect @@ -152,7 +152,7 @@ require ( github.com/kr/fs v0.1.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect - github.com/mattn/go-isatty v0.0.21 // indirect + github.com/mattn/go-isatty v0.0.22 // indirect github.com/mattn/go-shellwords v1.0.13 // indirect github.com/minio/crc64nvme v1.1.1 // indirect github.com/minio/md5-simd v1.1.2 // indirect @@ -171,7 +171,6 @@ require ( github.com/mozillazg/go-httpheader v0.4.0 // indirect github.com/namedotcom/go/v4 v4.0.2 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect - github.com/nrdcg/dnspod-go v0.4.0 // indirect github.com/nrdcg/freemyip v0.3.0 // indirect github.com/nrdcg/goacmedns v0.2.0 // indirect github.com/nrdcg/namesilo v0.5.0 // indirect @@ -191,7 +190,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.83 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.102 // indirect github.com/therootcompany/xz v1.0.1 // indirect github.com/tinylib/msgp v1.6.4 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect @@ -200,7 +199,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.1 // indirect github.com/ulikunitz/xz v0.5.15 // indirect - github.com/volcengine/volc-sdk-golang v1.0.242 // indirect + github.com/volcengine/volc-sdk-golang v1.0.248 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.2.0 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect @@ -227,4 +226,5 @@ require ( modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect modernc.org/sqlite v1.48.2 // indirect + software.sslmate.com/src/go-pkcs12 v0.7.1 // indirect ) diff --git a/agent/go.sum b/agent/go.sum index ea9581e7d87f..0d9c6e8a2f02 100644 --- a/agent/go.sum +++ b/agent/go.sum @@ -145,8 +145,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZL github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA= -github.com/aws/aws-sdk-go-v2/service/route53 v1.62.6 h1:6b+KS0uVMMsCUKlW8OPNxmcEmoEUtqP1LfnzSzWmuQM= -github.com/aws/aws-sdk-go-v2/service/route53 v1.62.6/go.mod h1:+wmraHmxwqi7feUL/41uULJWl8V1HxtxzOJH6a4ZRg4= +github.com/aws/aws-sdk-go-v2/service/route53 v1.62.7 h1:twRRMmtSITnt/rrp+D7UDLzE5pKMZe759aalkUdN+OY= +github.com/aws/aws-sdk-go-v2/service/route53 v1.62.7/go.mod h1:ztM1lr+sRoCAI8336ZUvlRPbToue0d3gE/wd6jomSJ8= github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc= github.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI= github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE= @@ -158,8 +158,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7 github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI= github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= -github.com/baidubce/bce-sdk-go v0.9.265 h1:xZeLhmADeOmmV+Zlt+3TelazS0czBNXELYSPrbtU3zE= -github.com/baidubce/bce-sdk-go v0.9.265/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/baidubce/bce-sdk-go v0.9.266 h1:MDWm/S4TNRZRH8Mo5J0t4gq34G3y2+WcbG0KCGLEpek= +github.com/baidubce/bce-sdk-go v0.9.266/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -297,8 +297,8 @@ github.com/go-acme/alidns-20150109/v4 v4.7.0 h1:PqJ/wR0JTpL4v0Owu1uM7bPQ1Yww0eQL github.com/go-acme/alidns-20150109/v4 v4.7.0/go.mod h1:btQvB6xZoN6ykKB74cPhiR+uvhrEE2AFVXm6RDmCHm0= github.com/go-acme/esa-20240910/v2 v2.48.0 h1:muSDyhjDTejxUGe3FTthCPCqRaEdYY9cG3N/AmU52Lc= github.com/go-acme/esa-20240910/v2 v2.48.0/go.mod h1:shPb6hzc1rJL15IJBY8HQ4GZk4E8RC52+52twutEwIg= -github.com/go-acme/lego/v4 v4.35.2 h1:uVQg+KC/yj9R2g7Q9W5wDqhvQvxV5SMu5eqFVoN5xZU= -github.com/go-acme/lego/v4 v4.35.2/go.mod h1:pX2jN5n8OphMGY1IaMjYm5DAEzguBaKRt8AvJAgJXpc= +github.com/go-acme/lego/v5 v5.1.0 h1:8JvnoRgH6uyFENTKS+3+JXdobpo4jYGfj4Bs4izVwmk= +github.com/go-acme/lego/v5 v5.1.0/go.mod h1:oCmaeLjpcqHmB1JGqGzsAjCMXXCPbDpNCckYv6J47m4= github.com/go-acme/tencentclouddnspod v1.3.24 h1:uCSiOW1EJttcnOON+MVVyVDJguFL/Q4NIGkq1CrT9p8= github.com/go-acme/tencentclouddnspod v1.3.24/go.mod h1:RKcB2wSoZncjBA0OEFj59s1ko1XDy+ZsAtk+9uMxUF0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -587,8 +587,8 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs= -github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-shellwords v1.0.13 h1:DC0OMEpGjm6LfNFU4ckYcvbQKyp2vE8atyFGXNtDcf4= github.com/mattn/go-shellwords v1.0.13/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= @@ -667,8 +667,6 @@ github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ= github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U= -github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc= github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM= github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0= @@ -834,8 +832,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.24/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.83 h1:C8ro7XQVV17O+A7zUTe28VK02NuyazuaY0CB2CH5Scw= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.83/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.102 h1:3n7BpLOPnE9Rv3Y9Jxgl/XaQX3GzktO+dEaAbtVbjSk= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.102/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0= github.com/tencentyun/cos-go-sdk-v5 v0.7.73 h1:uFfgp1A7cQaAGR6QP9DsIkoEQ67b8ewj5r1RV6XB540= github.com/tencentyun/cos-go-sdk-v5 v0.7.73/go.mod h1:STbTNaNKq03u+gscPEGOahKzLcGSYOj6Dzc5zNay7Pg= @@ -864,8 +862,8 @@ github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o github.com/upyun/go-sdk v2.1.0+incompatible h1:OdjXghQ/TVetWV16Pz3C1/SUpjhGBVPr+cLiqZLLyq0= github.com/upyun/go-sdk v2.1.0+incompatible/go.mod h1:eu3F5Uz4b9ZE5bE5QsCL6mgSNWRwfj0zpJ9J626HEqs= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/volcengine/volc-sdk-golang v1.0.242 h1:YEnYLl8mn83JCdO/X5GRDvxfxGvpUqk5j0Mj4VhwM6Y= -github.com/volcengine/volc-sdk-golang v1.0.242/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= +github.com/volcengine/volc-sdk-golang v1.0.248 h1:2XxvADkC9Q3UNxE1/X/QhRB45Ic2z7dbH33jZurwB8I= +github.com/volcengine/volc-sdk-golang v1.0.248/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= @@ -1345,8 +1343,8 @@ google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478 h1:aLsVTW0lZ8+IY5u google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478/go.mod h1:YJAzKjfHIUHb9T+bfu8L7mthAp7VVXQBUs1PLdBWS7M= google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec= google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 h1:seT2EwLWM78plQ7wcDfuWBc/4FAEAXDDiaSol4ku4qo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1364,8 +1362,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= -google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1394,8 +1392,8 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k= -gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= +gopkg.in/ini.v1 v1.67.2 h1:JtOSMb9OuaCZKr7h5D/h6iii14sK0hLbplTc6frx4Ss= +gopkg.in/ini.v1 v1.67.2/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -1457,3 +1455,5 @@ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +software.sslmate.com/src/go-pkcs12 v0.7.1 h1:bxkUPRsvTPNRBZa4M/aSX4PyMOEbq3V8I6hbkG4F4Q8= +software.sslmate.com/src/go-pkcs12 v0.7.1/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index 7707272b16c4..76755271724f 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -83,6 +83,7 @@ func InitAgentDB() { migrations.AddFileManageAISettings, migrations.AddFileShareTable, migrations.AddFileHistoryTable, + migrations.MigrateLegoV5, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index f94525b8d835..36f0c6f3b7b3 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -1260,3 +1260,56 @@ var AddFileHistoryTable = &gormigrate.Migration{ return nil }, } + +// MigrateLegoV5 normalizes data persisted under lego v4 so that lego v5 can read it. +// +// Two things changed in lego v5 that affect existing rows: +// +// 1. certcrypto.KeyType string values were renamed: +// "P256"->"EC256", "P384"->"EC384", +// "2048"->"RSA2048", "3072"->"RSA3072", "4096"->"RSA4096", "8192"->"RSA8192". +// We update every key_type column we own to the new form. +// +// 2. DnsPod provider was removed from upstream lego v5; the frontend already +// marked it deprecated. Existing DnsPod website_dns_accounts rows are kept +// so the user can decide what to do, but any website_ssls still pointing +// at a DnsPod account would fail to renew. We log a warning row count and +// leave deletion to the operator. +var MigrateLegoV5 = &gormigrate.Migration{ + ID: "20260523-migrate-lego-v5", + Migrate: func(tx *gorm.DB) error { + keyTypeMap := map[string]string{ + "P256": "EC256", + "P384": "EC384", + "2048": "RSA2048", + "3072": "RSA3072", + "4096": "RSA4096", + "8192": "RSA8192", + } + for old, neu := range keyTypeMap { + if err := tx.Model(&model.WebsiteAcmeAccount{}). + Where("key_type = ?", old). + Update("key_type", neu).Error; err != nil { + return fmt.Errorf("migrate WebsiteAcmeAccount.key_type %s->%s: %w", old, neu, err) + } + if err := tx.Model(&model.WebsiteSSL{}). + Where("key_type = ?", old). + Update("key_type", neu).Error; err != nil { + return fmt.Errorf("migrate WebsiteSSL.key_type %s->%s: %w", old, neu, err) + } + if err := tx.Model(&model.WebsiteCA{}). + Where("key_type = ?", old). + Update("key_type", neu).Error; err != nil { + return fmt.Errorf("migrate WebsiteCA.key_type %s->%s: %w", old, neu, err) + } + } + + var dnsPodCount int64 + _ = tx.Model(&model.WebsiteDnsAccount{}).Where("type = ?", "DnsPod").Count(&dnsPodCount).Error + if dnsPodCount > 0 { + global.LOG.Warnf("lego v5 removed the DnsPod provider; %d existing DnsPod DNS account(s) will not be usable for renewal -- please switch them to TencentCloud", dnsPodCount) + } + + return nil + }, +} diff --git a/agent/utils/ssl/acme.go b/agent/utils/ssl/acme.go index 3e7efc268e08..4128dfbdf58b 100644 --- a/agent/utils/ssl/acme.go +++ b/agent/utils/ssl/acme.go @@ -1,6 +1,7 @@ package ssl import ( + "context" "crypto" "crypto/ecdsa" "crypto/rsa" @@ -10,9 +11,6 @@ import ( "encoding/pem" "errors" "fmt" - "github.com/1Panel-dev/1Panel/agent/app/dto" - "github.com/1Panel-dev/1Panel/agent/buserr" - "golang.org/x/crypto/acme" "io" "net" "net/http" @@ -22,14 +20,45 @@ import ( "strings" "time" + "golang.org/x/crypto/acme" + + "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/model" - "github.com/go-acme/lego/v4/certcrypto" - "github.com/go-acme/lego/v4/lego" - "github.com/go-acme/lego/v4/registration" + "github.com/1Panel-dev/1Panel/agent/buserr" + legoacme "github.com/go-acme/lego/v5/acme" + "github.com/go-acme/lego/v5/certcrypto" + "github.com/go-acme/lego/v5/lego" + "github.com/go-acme/lego/v5/registration" ) var Orders = make(map[uint]*acme.Order) +// parsePrivateKeyPEM decodes a PEM-encoded private key produced by either +// lego v4 (SEC1 for EC, PKCS#1 for RSA) or lego v5 (PKCS#8 for both). It tries +// PKCS#8 first because that is what v5 and any modern tooling emits, then +// falls back to the legacy formats so account keys persisted under v4 keep +// working without manual conversion. +func parsePrivateKeyPEM(pemBytes []byte) (crypto.Signer, error) { + block, _ := pem.Decode(pemBytes) + if block == nil { + return nil, buserr.New("invalid PEM block") + } + if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil { + signer, ok := key.(crypto.Signer) + if !ok { + return nil, fmt.Errorf("PKCS#8 key does not implement crypto.Signer") + } + return signer, nil + } + if key, err := x509.ParseECPrivateKey(block.Bytes); err == nil { + return key, nil + } + if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil { + return key, nil + } + return nil, fmt.Errorf("unsupported private key format") +} + type zeroSSLRes struct { Success bool `json:"success"` EabKid string `json:"eab_kid"` @@ -46,20 +75,44 @@ const ( KeyRSA4096 = certcrypto.RSA4096 ) +// normalizeKeyType maps legacy v4 KeyType strings (P256/P384/2048/...) to v5 form (EC256/EC384/RSA2048/...). +// Used as a safety net in case database migration has not run yet. +func normalizeKeyType(stored string) KeyType { + switch stored { + case "P256": + return certcrypto.EC256 + case "P384": + return certcrypto.EC384 + case "2048": + return certcrypto.RSA2048 + case "3072": + return certcrypto.RSA3072 + case "4096": + return certcrypto.RSA4096 + case "8192": + return certcrypto.RSA8192 + default: + return KeyType(stored) + } +} + +// AcmeUser implements registration.User. Key is crypto.Signer +// (lego v5 changed the field type from crypto.PrivateKey to crypto.Signer). type AcmeUser struct { Email string - Registration *registration.Resource - Key crypto.PrivateKey + Registration *legoacme.ExtendedAccount + Key crypto.Signer } func (u *AcmeUser) GetEmail() string { return u.Email } -func (u *AcmeUser) GetRegistration() *registration.Resource { +func (u *AcmeUser) GetRegistration() *legoacme.ExtendedAccount { return u.Registration } -func (u *AcmeUser) GetPrivateKey() crypto.PrivateKey { + +func (u *AcmeUser) GetPrivateKey() crypto.Signer { return u.Key } @@ -95,31 +148,28 @@ func GetPrivateKey(priKey crypto.PrivateKey, keyType KeyType) ([]byte, error) { func NewRegisterClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemProxy) (*AcmeClient, error) { var ( - priKey crypto.PrivateKey + priKey crypto.Signer err error ) + keyType := normalizeKeyType(acmeAccount.KeyType) + if acmeAccount.PrivateKey != "" { - switch KeyType(acmeAccount.KeyType) { - case KeyEC256, KeyEC384: - block, _ := pem.Decode([]byte(acmeAccount.PrivateKey)) - priKey, err = x509.ParseECPrivateKey(block.Bytes) - if err != nil { - return nil, err - } - case KeyRSA2048, KeyRSA3072, KeyRSA4096: - block, _ := pem.Decode([]byte(acmeAccount.PrivateKey)) - priKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return nil, err - } + signer, parseErr := parsePrivateKeyPEM([]byte(acmeAccount.PrivateKey)) + if parseErr != nil { + return nil, parseErr } - + priKey = signer } else { - priKey, err = certcrypto.GeneratePrivateKey(KeyType(acmeAccount.KeyType)) - if err != nil { - return nil, err + generated, genErr := certcrypto.GeneratePrivateKey(keyType) + if genErr != nil { + return nil, genErr + } + signer, ok := generated.(crypto.Signer) + if !ok { + return nil, fmt.Errorf("generated key does not implement crypto.Signer") } + priKey = signer } myUser := &AcmeUser{ @@ -131,7 +181,10 @@ func NewRegisterClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemP if err != nil { return nil, err } - var reg *registration.Resource + + ctx := context.Background() + + var reg *legoacme.ExtendedAccount if acmeAccount.Type == "zerossl" || acmeAccount.Type == "google" || acmeAccount.Type == "freessl" || (acmeAccount.Type == "custom" && acmeAccount.UseEAB) { if acmeAccount.Type == "zerossl" { var res *zeroSSLRes @@ -152,12 +205,12 @@ func NewRegisterClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemP Kid: acmeAccount.EabKid, HmacEncoded: acmeAccount.EabHmacKey, } - reg, err = client.Registration.RegisterWithExternalAccountBinding(eabOptions) + reg, err = client.Registration.RegisterWithExternalAccountBinding(ctx, eabOptions) if err != nil { return nil, err } } else { - reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) + reg, err = client.Registration.Register(ctx, registration.RegisterOptions{TermsOfServiceAgreed: true}) if err != nil { return nil, err } @@ -205,13 +258,14 @@ func NewConfigWithProxy(user registration.User, accountType, customCaURL string, proxyUser = systemProxy.User proxyPassword = systemProxy.Password } + // lego v5 removed lego.CertificateConfig.KeyType; the key type is now + // taken from the user's account for each ObtainRequest call. return &lego.Config{ CADirURL: caDirURL, UserAgent: "1Panel", User: user, HTTPClient: createHTTPClientWithProxy(proxyURL, proxyUser, proxyPassword), Certificate: lego.CertificateConfig{ - KeyType: certcrypto.RSA2048, Timeout: 60 * time.Second, }, } @@ -312,38 +366,21 @@ func getZeroSSLEabCredentials(email string) (*zeroSSLRes, error) { return &result, nil } -func GetPrivateKeyByType(keyType, sslPrivateKey string) (crypto.PrivateKey, error) { - var ( - privateKey crypto.PrivateKey - err error - ) - kType := KeyType(keyType) +// GetPrivateKeyByType returns a crypto.Signer (lego v5 changed from crypto.PrivateKey). +func GetPrivateKeyByType(keyType, sslPrivateKey string) (crypto.Signer, error) { + kType := normalizeKeyType(keyType) if sslPrivateKey == "" { - privateKey, err = certcrypto.GeneratePrivateKey(kType) - if err != nil { - return nil, err + generated, genErr := certcrypto.GeneratePrivateKey(kType) + if genErr != nil { + return nil, genErr } - return privateKey, nil - } - block, _ := pem.Decode([]byte(sslPrivateKey)) - if block == nil { - return nil, buserr.New("invalid PEM block") - } - var privKey crypto.PrivateKey - switch kType { - case certcrypto.EC256, certcrypto.EC384: - privKey, err = x509.ParseECPrivateKey(block.Bytes) - if err != nil { - return nil, err - } - case certcrypto.RSA2048, certcrypto.RSA3072, certcrypto.RSA4096: - privKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return nil, err + signer, ok := generated.(crypto.Signer) + if !ok { + return nil, fmt.Errorf("generated key does not implement crypto.Signer") } + return signer, nil } - privateKey = privKey - return privateKey, nil + return parsePrivateKeyPEM([]byte(sslPrivateKey)) } func getWebsiteSSLDomains(websiteSSL *model.WebsiteSSL) []string { @@ -359,19 +396,19 @@ const ( retryDelayOn503 = 30 * time.Second ) -// isHTTP503Error checks if an error is an HTTP 503 Service Unavailable error +// isHTTP503Error checks if an error is an HTTP 503 Service Unavailable error. func isHTTP503Error(err error) bool { if err == nil { return false } - // Check for golang.org/x/crypto/acme.Error (used in manual_client.go) + // Check for golang.org/x/crypto/acme.Error (used in manual_client.go). var acmeErr *acme.Error if errors.As(err, &acmeErr) { return acmeErr.StatusCode == http.StatusServiceUnavailable } - // Check error message for 503 (fallback for lego library errors) + // Check error message for 503 (fallback for lego library errors). errMsg := err.Error() return strings.Contains(errMsg, "503") || strings.Contains(errMsg, "Service busy") diff --git a/agent/utils/ssl/client.go b/agent/utils/ssl/client.go index 9d349fa674c0..032e4b1bcbff 100644 --- a/agent/utils/ssl/client.go +++ b/agent/utils/ssl/client.go @@ -1,6 +1,7 @@ package ssl import ( + "context" "crypto" "crypto/rand" "crypto/x509" @@ -12,10 +13,10 @@ import ( "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/global" - "github.com/go-acme/lego/v4/certificate" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/lego" - "github.com/go-acme/lego/v4/providers/http/webroot" + "github.com/go-acme/lego/v5/certificate" + "github.com/go-acme/lego/v5/challenge/dns01" + "github.com/go-acme/lego/v5/lego" + "github.com/go-acme/lego/v5/providers/http/webroot" "github.com/pkg/errors" ) @@ -62,13 +63,19 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web _ = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "false") } - return c.Client.Challenge.SetDNS01Provider(p, - dns01.CondOption(len(nameservers) > 0, - dns01.AddRecursiveNameservers(nameservers)), - dns01.CondOption(websiteSSL.SkipDNS, - dns01.DisableAuthoritativeNssPropagationRequirement()), - dns01.AddDNSTimeout(dnsTimeOut), - ) + // lego v5 removed dns01.AddRecursiveNameservers and dns01.AddDNSTimeout; + // configure them via dns01.NewClient(&dns01.Options{...}) + SetDefaultClient. + dns01.SetDefaultClient(dns01.NewClient(&dns01.Options{ + RecursiveNameservers: nameservers, + Timeout: dnsTimeOut, + })) + + var opts []dns01.ChallengeOption + if websiteSSL.SkipDNS { + opts = append(opts, dns01.DisableAuthoritativeNssPropagationRequirement()) + } + + return c.Client.Challenge.SetDNS01Provider(p, opts...) } func (c *AcmeClient) UseHTTP(path string) error { @@ -84,18 +91,24 @@ func (c *AcmeClient) UseHTTP(path string) error { return nil } -func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.PrivateKey) (certificate.Resource, error) { +func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.Signer) (certificate.Resource, error) { + // lego v5 disables Common Name by default; explicitly enable it to keep + // the v4 behaviour, so legacy Java/router clients that still rely on the + // CommonName field do not fail TLS handshake. request := certificate.ObtainRequest{ - Domains: domains, - Bundle: true, - PrivateKey: privateKey, + Domains: domains, + Bundle: true, + PrivateKey: privateKey, + EnableCommonName: true, } + ctx := context.Background() + var certificates *certificate.Resource var err error for attempt := 1; attempt <= maxRetryAttempts; attempt++ { - certificates, err = c.Client.Certificate.Obtain(request) + certificates, err = c.Client.Certificate.Obtain(ctx, request) if err == nil { return *certificates, nil } @@ -114,7 +127,7 @@ func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.PrivateKey) ( return certificate.Resource{}, err } -func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.PrivateKey) (certificate.Resource, error) { +func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.Signer) (certificate.Resource, error) { csrTemplate := &x509.CertificateRequest{ Subject: pkix.Name{ CommonName: "", @@ -142,9 +155,11 @@ func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.PrivateKey) (c Bundle: true, } + ctx := context.Background() + var certificates *certificate.Resource for attempt := 1; attempt <= maxRetryAttempts; attempt++ { - certificates, err = c.Client.Certificate.ObtainForCSR(req) + certificates, err = c.Client.Certificate.ObtainForCSR(ctx, req) if err == nil { return *certificates, nil } @@ -163,5 +178,5 @@ func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.PrivateKey) (c } func (c *AcmeClient) RevokeSSL(pemSSL []byte) error { - return c.Client.Certificate.Revoke(pemSSL) + return c.Client.Certificate.Revoke(context.Background(), pemSSL) } diff --git a/agent/utils/ssl/dns_provider.go b/agent/utils/ssl/dns_provider.go index 141c2287e34c..3d2d0c644839 100644 --- a/agent/utils/ssl/dns_provider.go +++ b/agent/utils/ssl/dns_provider.go @@ -2,40 +2,39 @@ package ssl import ( "encoding/json" - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/providers/dns/acmedns" - "github.com/go-acme/lego/v4/providers/dns/alidns" - "github.com/go-acme/lego/v4/providers/dns/aliesa" - "github.com/go-acme/lego/v4/providers/dns/baiducloud" - "github.com/go-acme/lego/v4/providers/dns/clouddns" - "github.com/go-acme/lego/v4/providers/dns/cloudflare" - "github.com/go-acme/lego/v4/providers/dns/cloudns" - "github.com/go-acme/lego/v4/providers/dns/dnspod" - "github.com/go-acme/lego/v4/providers/dns/dynu" - "github.com/go-acme/lego/v4/providers/dns/freemyip" - "github.com/go-acme/lego/v4/providers/dns/godaddy" - "github.com/go-acme/lego/v4/providers/dns/huaweicloud" - "github.com/go-acme/lego/v4/providers/dns/namecheap" - "github.com/go-acme/lego/v4/providers/dns/namedotcom" - "github.com/go-acme/lego/v4/providers/dns/namesilo" - "github.com/go-acme/lego/v4/providers/dns/ovh" - "github.com/go-acme/lego/v4/providers/dns/porkbun" - "github.com/go-acme/lego/v4/providers/dns/rainyun" - "github.com/go-acme/lego/v4/providers/dns/regru" - "github.com/go-acme/lego/v4/providers/dns/route53" - "github.com/go-acme/lego/v4/providers/dns/spaceship" - "github.com/go-acme/lego/v4/providers/dns/technitium" - "github.com/go-acme/lego/v4/providers/dns/tencentcloud" - "github.com/go-acme/lego/v4/providers/dns/vercel" - "github.com/go-acme/lego/v4/providers/dns/volcengine" - "github.com/go-acme/lego/v4/providers/dns/westcn" "time" + + "github.com/go-acme/lego/v5/challenge" + "github.com/go-acme/lego/v5/providers/dns/acmedns" + "github.com/go-acme/lego/v5/providers/dns/alidns" + "github.com/go-acme/lego/v5/providers/dns/aliesa" + "github.com/go-acme/lego/v5/providers/dns/baiducloud" + "github.com/go-acme/lego/v5/providers/dns/clouddns" + "github.com/go-acme/lego/v5/providers/dns/cloudflare" + "github.com/go-acme/lego/v5/providers/dns/cloudns" + "github.com/go-acme/lego/v5/providers/dns/dynu" + "github.com/go-acme/lego/v5/providers/dns/freemyip" + "github.com/go-acme/lego/v5/providers/dns/godaddy" + "github.com/go-acme/lego/v5/providers/dns/huaweicloud" + "github.com/go-acme/lego/v5/providers/dns/namecheap" + "github.com/go-acme/lego/v5/providers/dns/namedotcom" + "github.com/go-acme/lego/v5/providers/dns/namesilo" + "github.com/go-acme/lego/v5/providers/dns/ovh" + "github.com/go-acme/lego/v5/providers/dns/porkbun" + "github.com/go-acme/lego/v5/providers/dns/rainyun" + "github.com/go-acme/lego/v5/providers/dns/regru" + "github.com/go-acme/lego/v5/providers/dns/route53" + "github.com/go-acme/lego/v5/providers/dns/spaceship" + "github.com/go-acme/lego/v5/providers/dns/technitium" + "github.com/go-acme/lego/v5/providers/dns/tencentcloud" + "github.com/go-acme/lego/v5/providers/dns/vercel" + "github.com/go-acme/lego/v5/providers/dns/volcengine" + "github.com/go-acme/lego/v5/providers/dns/westcn" ) type DnsType string const ( - DnsPod DnsType = "DnsPod" AliYun DnsType = "AliYun" AliESA DnsType = "AliESA" AWSRoute53 DnsType = "AWSRoute53" @@ -60,7 +59,7 @@ const ( Ovh DnsType = "Ovh" AcmeDNS DnsType = "AcmeDNS" PorkBun DnsType = "PorkBun" - Technitium DnsType = "Technitium" + Technitium DnsType = "Technitium" ) type DNSParam struct { @@ -103,13 +102,6 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e return nil, err } switch dnsType { - case DnsPod: - config := dnspod.NewDefaultConfig() - config.LoginToken = param.ID + "," + param.Token - config.PropagationTimeout = propagationTimeout - config.PollingInterval = pollingInterval - config.TTL = ttl - p, err = dnspod.NewDNSProviderConfig(config) case AliYun: config := alidns.NewDefaultConfig() config.SecretKey = param.SecretKey diff --git a/agent/utils/ssl/manual_client.go b/agent/utils/ssl/manual_client.go index 655de5524bb7..772f568daae2 100644 --- a/agent/utils/ssl/manual_client.go +++ b/agent/utils/ssl/manual_client.go @@ -11,7 +11,7 @@ import ( "encoding/pem" "fmt" "github.com/1Panel-dev/1Panel/agent/app/model" - "github.com/go-acme/lego/v4/certificate" + "github.com/go-acme/lego/v5/certificate" "github.com/miekg/dns" "golang.org/x/crypto/acme" "log" @@ -31,30 +31,16 @@ type RequestCertRequest struct { } func NewCustomAcmeClient(acmeAccount *model.WebsiteAcmeAccount, logger *log.Logger) (*ManualClient, error) { - var ( - key crypto.PrivateKey - err error - ) - switch KeyType(acmeAccount.KeyType) { - case KeyEC256, KeyEC384: - block, _ := pem.Decode([]byte(acmeAccount.PrivateKey)) - key, err = x509.ParseECPrivateKey(block.Bytes) - if err != nil { - return nil, err - } - case KeyRSA2048, KeyRSA3072, KeyRSA4096: - block, _ := pem.Decode([]byte(acmeAccount.PrivateKey)) - key, err = x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return nil, err - } + key, err := parsePrivateKeyPEM([]byte(acmeAccount.PrivateKey)) + if err != nil { + return nil, err } if logger == nil { logger = log.Default() } client := &acme.Client{ - Key: key.(crypto.Signer), + Key: key, DirectoryURL: getCaDirURL(acmeAccount.Type, acmeAccount.CaDirURL), } return &ManualClient{ @@ -355,7 +341,7 @@ func (c *ManualClient) RequestCertificate(ctx context.Context, websiteSSL *model } c.logger.Printf("[INFO] acme: Server responded with a certificate.") resource := certificate.Resource{ - Domain: domains[0], + Domains: domains, CertURL: certURL, CertStableURL: certURL, PrivateKey: []byte(privateKeyPEM), diff --git a/frontend/src/global/mimetype.ts b/frontend/src/global/mimetype.ts index 8811f2e047a2..18bb6af1e060 100644 --- a/frontend/src/global/mimetype.ts +++ b/frontend/src/global/mimetype.ts @@ -168,11 +168,11 @@ export const AcmeAccountTypes = [ ]; export const KeyTypes = [ - { label: 'EC 256', value: 'P256' }, - { label: 'EC 384', value: 'P384' }, - { label: 'RSA 2048', value: '2048' }, - { label: 'RSA 3072', value: '3072' }, - { label: 'RSA 4096', value: '4096' }, + { label: 'EC 256', value: 'EC256' }, + { label: 'EC 384', value: 'EC384' }, + { label: 'RSA 2048', value: 'RSA2048' }, + { label: 'RSA 3072', value: 'RSA3072' }, + { label: 'RSA 4096', value: 'RSA4096' }, ]; export const DNSTypes = [ diff --git a/frontend/src/views/website/ssl/acme-account/create/index.vue b/frontend/src/views/website/ssl/acme-account/create/index.vue index cbaa11af5d02..6e8b4c6c7af3 100644 --- a/frontend/src/views/website/ssl/acme-account/create/index.vue +++ b/frontend/src/views/website/ssl/acme-account/create/index.vue @@ -111,7 +111,7 @@ const initData = () => ({ type: 'letsencrypt', eabKid: '', eabHmacKey: '', - keyType: 'P256', + keyType: 'EC256', useProxy: false, caDirURL: '', useEAB: false, diff --git a/frontend/src/views/website/ssl/ca/create/index.vue b/frontend/src/views/website/ssl/ca/create/index.vue index 5f1cb375daa4..35ea352abe42 100644 --- a/frontend/src/views/website/ssl/ca/create/index.vue +++ b/frontend/src/views/website/ssl/ca/create/index.vue @@ -72,7 +72,7 @@ const rules = ref({ const initData = () => ({ name: '', - keyType: 'P256', + keyType: 'EC256', commonName: '', country: 'CN', organization: '', diff --git a/frontend/src/views/website/ssl/ca/obtain/index.vue b/frontend/src/views/website/ssl/ca/obtain/index.vue index 751cc70916d5..3250fd29ac0e 100644 --- a/frontend/src/views/website/ssl/ca/obtain/index.vue +++ b/frontend/src/views/website/ssl/ca/obtain/index.vue @@ -103,7 +103,7 @@ const rules = ref({ }); const initData = () => ({ - keyType: 'P256', + keyType: 'EC256', domains: '', id: 0, time: 10, diff --git a/frontend/src/views/website/ssl/create/index.vue b/frontend/src/views/website/ssl/create/index.vue index c00914bd4bf4..eb426ff7115f 100644 --- a/frontend/src/views/website/ssl/create/index.vue +++ b/frontend/src/views/website/ssl/create/index.vue @@ -242,7 +242,7 @@ const initData = () => ({ acmeAccountId: undefined, dnsAccountId: undefined, autoRenew: true, - keyType: 'P256', + keyType: 'EC256', pushDir: false, dir: '', description: '', From d08397feafc7b0f6292aed0b9da046db4422ff89 Mon Sep 17 00:00:00 2001 From: rowanchen-com Date: Sat, 23 May 2026 21:59:19 +0800 Subject: [PATCH 2/5] feat(ssl): add Dynadot DNS provider, drop DnsPod Dynadot was added to upstream lego in v5.1.0 (go-acme/lego#3125). Wire it into the same DNS provider switch the other 25 providers use, and surface it in the frontend so users can pick it in the DNS account dialog. DnsPod is removed from the DNS provider list. lego v5 has no DnsPod implementation; the previous frontend already showed a deprecation hint pointing at TencentCloud, which is now the recommended way to manage DNSPod-hosted zones (the underlying API is the same). --- agent/utils/ssl/dns_provider.go | 10 ++++++++++ frontend/src/global/mimetype.ts | 4 ++-- .../website/ssl/dns-account/create/index.vue | 16 +++------------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/agent/utils/ssl/dns_provider.go b/agent/utils/ssl/dns_provider.go index 3d2d0c644839..efcc859db64d 100644 --- a/agent/utils/ssl/dns_provider.go +++ b/agent/utils/ssl/dns_provider.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v5/providers/dns/clouddns" "github.com/go-acme/lego/v5/providers/dns/cloudflare" "github.com/go-acme/lego/v5/providers/dns/cloudns" + "github.com/go-acme/lego/v5/providers/dns/dynadot" "github.com/go-acme/lego/v5/providers/dns/dynu" "github.com/go-acme/lego/v5/providers/dns/freemyip" "github.com/go-acme/lego/v5/providers/dns/godaddy" @@ -55,6 +56,7 @@ const ( ClouDNS DnsType = "ClouDNS" RegRu DnsType = "RegRu" Dynu DnsType = "Dynu" + Dynadot DnsType = "Dynadot" BaiduCloud DnsType = "BaiduCloud" Ovh DnsType = "Ovh" AcmeDNS DnsType = "AcmeDNS" @@ -266,6 +268,14 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e config.PollingInterval = pollingInterval config.TTL = ttl p, err = dynu.NewDNSProviderConfig(config) + case Dynadot: + config := dynadot.NewDefaultConfig() + config.APIKey = param.APIkey + config.APISecret = param.APISecret + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = dynadot.NewDNSProviderConfig(config) case BaiduCloud: config := baiducloud.NewDefaultConfig() config.AccessKeyID = param.AccessKey diff --git a/frontend/src/global/mimetype.ts b/frontend/src/global/mimetype.ts index 18bb6af1e060..bb707add4d9b 100644 --- a/frontend/src/global/mimetype.ts +++ b/frontend/src/global/mimetype.ts @@ -273,8 +273,8 @@ export const DNSTypes = [ value: 'PorkBun', }, { - label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')', - value: 'DnsPod', + label: 'Dynadot', + value: 'Dynadot', }, { label: 'Technitium', diff --git a/frontend/src/views/website/ssl/dns-account/create/index.vue b/frontend/src/views/website/ssl/dns-account/create/index.vue index 6633d31d95f4..faff8995babb 100644 --- a/frontend/src/views/website/ssl/dns-account/create/index.vue +++ b/frontend/src/views/website/ssl/dns-account/create/index.vue @@ -15,9 +15,6 @@ :value="type.value" > - - {{ $t('ssl.deprecatedHelper') }} -
-
- - - - - - -
@@ -93,6 +82,7 @@ account.type === 'Godaddy' || account.type === 'RainYun' || account.type === 'Spaceship' || + account.type === 'Dynadot' || account.type === 'Dynu' " > @@ -104,7 +94,7 @@ @@ -260,7 +250,7 @@ const resetForm = () => { account.value = { id: 0, name: '', - type: 'DnsPod', + type: 'AliYun', authorization: {}, }; accountForm.value?.resetFields(); From 0e4a94018d2d47801b6bd2b8183f4adc7fcbfdc8 Mon Sep 17 00:00:00 2001 From: rowanchen-com Date: Mon, 25 May 2026 12:51:05 +0800 Subject: [PATCH 3/5] fix(ssl): address review feedback (validator, DnsPod errors, DNS-01 race) Three issues raised on the PR: 1. Backend KeyType validators in WebsiteAcmeAccountCreate, WebsiteCACreate and WebsiteCAObtain still required the v4 strings (P256/P384/2048/...), so after the frontend started sending v5 strings (EC256/RSA2048/...) the create/obtain endpoints would 422. Validators now accept both v4 and v5 forms; service-layer normalizeKeyType continues to handle either case. 2. getDNSProviderConfig had no default branch in its switch. A DNS account with type `DnsPod` (kept on disk after the v5 upgrade) silently fell through and returned (nil, nil), making the DNS-01 step fail far away from the real cause. Add an explicit DnsPod branch that returns a clear `removed in lego v5; switch to TencentCloud` error, plus a generic default for any other unknown type. Surface the same warning in the UI: the DNS account list shows a `removed` tag on DnsPod rows, and the SSL list shows the same tag on certificates that are still bound to a DnsPod account, so users see the broken state before auto-renewal silently fails. 3. lego v5 keeps the recursive-resolver Client and the LEGO_DISABLE_CNAME_SUPPORT environment switch as process-wide globals, and reads dns01.DefaultClient() inside its own propagation precheck loop. Two concurrent SSL applications with different nameserver / DisableCNAME settings could clobber each other and use the wrong resolver. Wrap the whole DNS-01 flow (UseDns through Obtain*) in a package-level mutex so one apply finishes before the next one configures the globals. i18n strings for the DnsPod removal hints are added to all 10 locales to match the existing translation coverage of nearby keys. --- agent/app/dto/request/website_ssl.go | 6 +-- agent/utils/ssl/client.go | 39 ++++++++++++++++++- agent/utils/ssl/dns_provider.go | 11 ++++++ frontend/src/lang/modules/en.ts | 5 +++ frontend/src/lang/modules/es-es.ts | 5 +++ frontend/src/lang/modules/ja.ts | 5 +++ frontend/src/lang/modules/ko.ts | 5 +++ frontend/src/lang/modules/ms.ts | 5 +++ frontend/src/lang/modules/pt-br.ts | 5 +++ frontend/src/lang/modules/ru.ts | 5 +++ frontend/src/lang/modules/tr.ts | 5 +++ frontend/src/lang/modules/zh-Hant.ts | 5 +++ frontend/src/lang/modules/zh.ts | 5 +++ .../views/website/ssl/dns-account/index.vue | 5 +++ frontend/src/views/website/ssl/index.vue | 15 ++++++- 15 files changed, 121 insertions(+), 5 deletions(-) diff --git a/agent/app/dto/request/website_ssl.go b/agent/app/dto/request/website_ssl.go index 4522e58ed45c..0a6c91ad022b 100644 --- a/agent/app/dto/request/website_ssl.go +++ b/agent/app/dto/request/website_ssl.go @@ -61,7 +61,7 @@ type WebsiteSSLObtain struct { type WebsiteAcmeAccountCreate struct { Email string `json:"email" validate:"required"` Type string `json:"type" validate:"required,oneof=letsencrypt zerossl buypass google custom"` - KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192"` + KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192 EC256 EC384 RSA2048 RSA3072 RSA4096 RSA8192"` EabKid string `json:"eabKid"` EabHmacKey string `json:"eabHmacKey"` UseProxy bool `json:"useProxy"` @@ -138,7 +138,7 @@ type WebsiteCACreate struct { Organization string `json:"organization" validate:"required"` OrganizationUint string `json:"organizationUint"` Name string `json:"name" validate:"required"` - KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192"` + KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192 EC256 EC384 RSA2048 RSA3072 RSA4096 RSA8192"` Province string `json:"province" ` City string `json:"city"` } @@ -146,7 +146,7 @@ type WebsiteCACreate struct { type WebsiteCAObtain struct { ID uint `json:"id" validate:"required"` Domains string `json:"domains" validate:"required"` - KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192"` + KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192 EC256 EC384 RSA2048 RSA3072 RSA4096 RSA8192"` Time int `json:"time" validate:"required"` Unit string `json:"unit" validate:"required"` PushDir bool `json:"pushDir"` diff --git a/agent/utils/ssl/client.go b/agent/utils/ssl/client.go index 032e4b1bcbff..04fa6b89740c 100644 --- a/agent/utils/ssl/client.go +++ b/agent/utils/ssl/client.go @@ -8,6 +8,7 @@ import ( "crypto/x509/pkix" "net" "os" + "sync" "time" "github.com/1Panel-dev/1Panel/agent/app/dto" @@ -20,6 +21,13 @@ import ( "github.com/pkg/errors" ) +// dnsChallengeMu serializes DNS-01 issuance flows because lego v5 keeps the +// recursive-nameserver Client and the LEGO_DISABLE_CNAME_SUPPORT switch in +// process-wide globals. Without this lock, two concurrent SSL applications +// with different nameserver/CNAME settings would clobber each other and +// occasionally fail propagation checks against the wrong resolver. +var dnsChallengeMu sync.Mutex + type AcmeClientOption func(*AcmeClientOptions) type AcmeClientOptions struct { @@ -31,6 +39,11 @@ type AcmeClient struct { Client *lego.Client User *AcmeUser ProxyURL string + + // dnsChallengeLocked records whether this client currently holds + // dnsChallengeMu. It is set by UseDns and cleared by ObtainSSL/ + // ObtainIPSSL once the DNS-01 flow finishes. UseHTTP does not touch it. + dnsChallengeLocked bool } func NewAcmeClient(acmeAccount *model.WebsiteAcmeAccount, systemProxy *dto.SystemProxy) (*AcmeClient, error) { @@ -57,6 +70,15 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web if websiteSSL.Nameserver2 != "" { nameservers = append(nameservers, websiteSSL.Nameserver2) } + + // Hold the global DNS-01 lock for the entire flow that follows, including + // the Obtain call. lego v5 reads dns01.DefaultClient() inside its own + // propagation-precheck loop, so the lock cannot be released right after + // SetDefaultClient/SetDNS01Provider; it has to span the whole DNS-01 + // challenge. ObtainSSL / ObtainIPSSL release it once the request returns. + dnsChallengeMu.Lock() + c.dnsChallengeLocked = true + if websiteSSL.DisableCNAME { _ = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "true") } else { @@ -75,7 +97,20 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web opts = append(opts, dns01.DisableAuthoritativeNssPropagationRequirement()) } - return c.Client.Challenge.SetDNS01Provider(p, opts...) + if err := c.Client.Challenge.SetDNS01Provider(p, opts...); err != nil { + c.releaseDNSLock() + return err + } + return nil +} + +// releaseDNSLock releases dnsChallengeMu if it was acquired by UseDns. +// Safe to call multiple times; calls after the first one are no-ops. +func (c *AcmeClient) releaseDNSLock() { + if c.dnsChallengeLocked { + c.dnsChallengeLocked = false + dnsChallengeMu.Unlock() + } } func (c *AcmeClient) UseHTTP(path string) error { @@ -92,6 +127,7 @@ func (c *AcmeClient) UseHTTP(path string) error { } func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.Signer) (certificate.Resource, error) { + defer c.releaseDNSLock() // lego v5 disables Common Name by default; explicitly enable it to keep // the v4 behaviour, so legacy Java/router clients that still rely on the // CommonName field do not fail TLS handshake. @@ -128,6 +164,7 @@ func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.Signer) (cert } func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.Signer) (certificate.Resource, error) { + defer c.releaseDNSLock() csrTemplate := &x509.CertificateRequest{ Subject: pkix.Name{ CommonName: "", diff --git a/agent/utils/ssl/dns_provider.go b/agent/utils/ssl/dns_provider.go index efcc859db64d..02c9c2d19fa9 100644 --- a/agent/utils/ssl/dns_provider.go +++ b/agent/utils/ssl/dns_provider.go @@ -2,6 +2,7 @@ package ssl import ( "encoding/json" + "fmt" "time" "github.com/go-acme/lego/v5/challenge" @@ -313,6 +314,16 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e config.PollingInterval = pollingInterval config.TTL = ttl p, err = technitium.NewDNSProviderConfig(config) + default: + // Surfaces clear errors for legacy values (e.g. "DnsPod", which lego v5 + // removed) and for any future provider that the frontend can pick but + // the backend has not yet wired up. Without this default branch, p and + // err would both stay nil and the DNS-01 step would fail far away from + // the real cause. + if dnsType == "DnsPod" { + return nil, fmt.Errorf("DNS provider %q has been removed in lego v5; please switch this DNS account to TencentCloud, which manages DNSPod-hosted zones via the same underlying API", dnsType) + } + return nil, fmt.Errorf("unsupported DNS provider %q", dnsType) } if err != nil { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index d0e826fb7b81..aa25a3f97fda 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -3629,6 +3629,11 @@ const message = { deprecated: 'will be deprecated', deprecatedHelper: 'Maintenance has been stopped and may be abandoned in a future version. Please use Tencent Cloud method for analysis', + dnsPodRemoved: 'removed', + dnsPodRemovedTip: + 'DnsPod has been removed in lego v5; this account cannot be used for new certificates or renewals. Please switch to a TencentCloud DNS account.', + dnsPodRemovedSSLTip: + 'This certificate is bound to a DnsPod DNS account, which is no longer usable in lego v5. Auto-renewal will fail. Please switch the DNS account to TencentCloud.', disableCNAME: 'Disable CNAME', disableCNAMEHelper: 'Check here if the domain name has a CNAME record and the request fails.', nameserver: 'DNS server', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index ae8aec5f44c6..3002e7dda2d9 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -3680,6 +3680,11 @@ const message = { cfHelper: 'No uses la clave API Global', deprecated: 'será obsoleto', deprecatedHelper: 'El soporte se ha detenido y puede eliminarse en el futuro. Usa el método Tencent Cloud.', + dnsPodRemoved: 'eliminado', + dnsPodRemovedTip: + 'DnsPod ha sido eliminado en lego v5; esta cuenta no puede usarse para emisión o renovación de certificados. Cambia a una cuenta DNS de TencentCloud.', + dnsPodRemovedSSLTip: + 'La cuenta DnsPod asociada a este certificado ya no es válida en lego v5; la renovación automática fallará. Cambia la cuenta DNS a TencentCloud.', disableCNAME: 'Deshabilitar CNAME', disableCNAMEHelper: 'Marca si el dominio tiene un CNAME y falla la solicitud.', nameserver: 'Servidor DNS', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 92f306cef660..f29bd35d5b7b 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -3667,6 +3667,11 @@ const message = { deprecated: '非推奨されます', deprecatedHelper: 'メンテナンスは停止されており、将来のバージョンでは放棄される可能性があります。分析にはTencent Cloudメソッドを使用してください', + dnsPodRemoved: '削除済み', + dnsPodRemovedTip: + 'DnsPod は lego v5 で削除されました。このアカウントは証明書の申請/更新に使用できません。Tencent Cloud DNS アカウントに切り替えてください。', + dnsPodRemovedSSLTip: + 'この証明書がバインドされている DnsPod アカウントは lego v5 では使用できません。自動更新は失敗します。DNS アカウントを Tencent Cloud に切り替えてください。', disableCNAME: 'cnameを無効にします', disableCNAMEHelper: 'ドメイン名にCNAMEレコードがあり、リクエストが失敗するかどうかを確認してください。', nameserver: 'DNSサーバー', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 65b402a19548..fdf7592a172d 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -3583,6 +3583,11 @@ const message = { deprecated: '더 이상 지원되지 않습니다.', deprecatedHelper: '유지 관리가 중단되었으며 향후 버전에서 제외될 수 있습니다. Tencent Cloud 방법을 사용하여 분석하세요.', + dnsPodRemoved: '제거됨', + dnsPodRemovedTip: + 'DnsPod는 lego v5에서 제거되었습니다. 이 계정은 인증서 신청/갱신에 사용할 수 없습니다. Tencent Cloud DNS 계정으로 전환해 주세요.', + dnsPodRemovedSSLTip: + '이 인증서에 연결된 DnsPod 계정은 lego v5에서 사용할 수 없습니다. 자동 갱신이 실패합니다. DNS 계정을 Tencent Cloud로 전환해 주세요.', disableCNAME: 'CNAME 비활성화', disableCNAMEHelper: '도메인 이름에 CNAME 레코드가 있고 요청이 실패할 경우 선택하세요.', nameserver: 'DNS 서버', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 5fccf5ec62a9..24d4eb6d1c17 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -3717,6 +3717,11 @@ const message = { deprecated: 'akan dihentikan', deprecatedHelper: 'Penyelenggaraan telah dihentikan dan mungkin akan dibuang dalam versi masa hadapan. Sila gunakan kaedah Tencent Cloud untuk analisis', + dnsPodRemoved: 'dialih keluar', + dnsPodRemovedTip: + 'DnsPod telah dialih keluar dalam lego v5; akaun ini tidak boleh digunakan untuk permohonan/pembaharuan sijil. Sila tukar kepada akaun TencentCloud DNS.', + dnsPodRemovedSSLTip: + 'Akaun DnsPod yang dikaitkan dengan sijil ini tidak lagi boleh digunakan dalam lego v5; pembaharuan automatik akan gagal. Sila tukar akaun DNS kepada TencentCloud.', disableCNAME: 'Lumpuhkan CNAME', disableCNAMEHelper: 'Semak di sini jika nama domain mempunyai rekod CNAME dan permintaan gagal.', nameserver: 'Pelayan DNS', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 62496df4fdb9..1cf95fee7ad0 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -3853,6 +3853,11 @@ const message = { deprecated: 'será descontinuado', deprecatedHelper: 'A manutenção foi interrompida e pode ser abandonada em uma versão futura. Use o método Tencent Cloud para análise', + dnsPodRemoved: 'removido', + dnsPodRemovedTip: + 'O DnsPod foi removido no lego v5; esta conta não pode ser usada para emissão/renovação de certificados. Por favor, mude para uma conta DNS do TencentCloud.', + dnsPodRemovedSSLTip: + 'A conta DnsPod vinculada a este certificado não é mais válida no lego v5; a renovação automática falhará. Por favor, troque a conta DNS para TencentCloud.', disableCNAME: 'Desativar CNAME', disableCNAMEHelper: 'Marque esta opção se o domínio tiver um registro CNAME e a solicitação falhar.', nameserver: 'Servidor DNS', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index cbecd933723e..b19dc2163463 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -3709,6 +3709,11 @@ const message = { deprecated: 'будет устарелым', deprecatedHelper: 'Обслуживание остановлено и может быть удалено в будущей версии. Пожалуйста, используйте метод Tencent Cloud для анализа', + dnsPodRemoved: 'удалён', + dnsPodRemovedTip: + 'DnsPod удалён в lego v5; эта учётная запись не может использоваться для выпуска или продления сертификатов. Пожалуйста, переключитесь на учётную запись TencentCloud DNS.', + dnsPodRemovedSSLTip: + 'Учётная запись DnsPod, привязанная к этому сертификату, недоступна в lego v5; автопродление будет завершаться с ошибкой. Пожалуйста, переключите DNS-аккаунт на TencentCloud.', disableCNAME: 'Отключить CNAME', disableCNAMEHelper: 'Отметьте здесь, если доменное имя имеет запись CNAME и запрос не удается.', nameserver: 'DNS сервер', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 31906bb65791..4b571db7615e 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -3710,6 +3710,11 @@ const message = { deprecated: 'kaldırılacak', deprecatedHelper: 'Bakım durduruldu ve gelecek bir sürümde terk edilebilir. Lütfen analiz için Tencent Cloud yöntemini kullanın', + dnsPodRemoved: 'kaldırıldı', + dnsPodRemovedTip: + 'DnsPod, lego v5 sürümünde kaldırıldı; bu hesap sertifika başvurusu/yenilemesi için kullanılamaz. Lütfen TencentCloud DNS hesabına geçin.', + dnsPodRemovedSSLTip: + 'Bu sertifikaya bağlı DnsPod hesabı lego v5 sürümünde kullanılamaz; otomatik yenileme başarısız olur. Lütfen DNS hesabını TencentCloud’a değiştirin.', disableCNAME: 'CNAME’yi devre dışı bırak', disableCNAMEHelper: 'Alan adında bir CNAME kaydı varsa ve istek başarısız olursa burayı işaretleyin.', nameserver: 'DNS sunucusu', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 91dc5d7240ff..6f9499ac15e6 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -3381,6 +3381,11 @@ const message = { cfHelper: '請勿使用 Global API Key', deprecated: '即將廢棄', deprecatedHelper: '已經停止維護,可能會在以後的某個版本廢棄,請使用騰訊雲方式解析', + dnsPodRemoved: '已移除', + dnsPodRemovedTip: + 'DnsPod 已被 lego v5 移除,當前帳號無法用於證書申請/續簽,請改用騰訊雲 DNS 帳號', + dnsPodRemovedSSLTip: + '此證書綁定的 DnsPod 帳號在 lego v5 已不可用,自動續簽將失敗,請將 DNS 帳號切換到騰訊雲', disableCNAME: '停用 CNAME', disableCNAMEHelper: '有 CNAME 設定的域名,如果申請失敗,可以勾選此處', nameserver: 'DNS 伺服器', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 1735058d0364..3eb0ea4b3e33 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -3368,6 +3368,11 @@ const message = { cfHelper: '请勿使用 Global API Key', deprecated: '即将废弃', deprecatedHelper: '已经停止维护,可能会在以后的某个版本废弃,请使用腾讯云方式解析', + dnsPodRemoved: '已移除', + dnsPodRemovedTip: + 'DnsPod 已被 lego v5 移除,当前账号无法用于证书申请/续签,请改用腾讯云 DNS 账号', + dnsPodRemovedSSLTip: + '此证书绑定的 DnsPod 账号在 lego v5 已不可用,自动续签将失败,请将 DNS 账号切换到腾讯云', disableCNAME: '禁用 CNAME', disableCNAMEHelper: '有 CNAME 配置的域名,如果申请失败,可以勾选此处', nameserver: 'DNS 服务器', diff --git a/frontend/src/views/website/ssl/dns-account/index.vue b/frontend/src/views/website/ssl/dns-account/index.vue index c82ad12fbf6a..2467921fad9d 100644 --- a/frontend/src/views/website/ssl/dns-account/index.vue +++ b/frontend/src/views/website/ssl/dns-account/index.vue @@ -16,6 +16,11 @@ + > + + Date: Mon, 25 May 2026 17:56:23 +0800 Subject: [PATCH 4/5] fix(ssl): use lego v5 compat for KeyType + keep DnsPod option as deprecated Two follow-ups from review: - normalizeKeyType now delegates to lego v5's official compat.KeyTypeCompat.UnmarshalText, so we no longer maintain our own P256/2048->EC256/RSA2048 switch and stay aligned with upstream when more legacy values are added later. - mimetype.ts keeps DnsPod in DNSTypes with a deprecated:true flag instead of removing it. The DNS account dialog renders the option as 'DNSPod (deprecated)' and disables it on create, so users with existing DnsPod accounts can still see the type label and edit/remove the row, but new accounts cannot pick it. Backend default branch in getDNSProviderConfig already returns a clear 'switch to TencentCloud' error if anything still hits DnsPod at runtime. --- agent/utils/ssl/acme.go | 24 +++++++------------ frontend/src/global/mimetype.ts | 5 ++++ .../website/ssl/dns-account/create/index.vue | 6 ++++- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/agent/utils/ssl/acme.go b/agent/utils/ssl/acme.go index 4128dfbdf58b..1eab15a43edf 100644 --- a/agent/utils/ssl/acme.go +++ b/agent/utils/ssl/acme.go @@ -27,6 +27,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/buserr" legoacme "github.com/go-acme/lego/v5/acme" "github.com/go-acme/lego/v5/certcrypto" + "github.com/go-acme/lego/v5/certcrypto/compat" "github.com/go-acme/lego/v5/lego" "github.com/go-acme/lego/v5/registration" ) @@ -75,25 +76,16 @@ const ( KeyRSA4096 = certcrypto.RSA4096 ) -// normalizeKeyType maps legacy v4 KeyType strings (P256/P384/2048/...) to v5 form (EC256/EC384/RSA2048/...). -// Used as a safety net in case database migration has not run yet. +// normalizeKeyType maps legacy v4 KeyType strings (P256/P384/2048/...) to the +// v5 form (EC256/EC384/RSA2048/...) using lego v5's official compat helper, so +// any account/SSL row written under v4 keeps loading without manual conversion. +// Unknown values are returned as-is and let the downstream caller error out. func normalizeKeyType(stored string) KeyType { - switch stored { - case "P256": - return certcrypto.EC256 - case "P384": - return certcrypto.EC384 - case "2048": - return certcrypto.RSA2048 - case "3072": - return certcrypto.RSA3072 - case "4096": - return certcrypto.RSA4096 - case "8192": - return certcrypto.RSA8192 - default: + var k compat.KeyTypeCompat + if err := k.UnmarshalText([]byte(stored)); err != nil { return KeyType(stored) } + return KeyType(k) } // AcmeUser implements registration.User. Key is crypto.Signer diff --git a/frontend/src/global/mimetype.ts b/frontend/src/global/mimetype.ts index bb707add4d9b..32cb89e07c22 100644 --- a/frontend/src/global/mimetype.ts +++ b/frontend/src/global/mimetype.ts @@ -272,6 +272,11 @@ export const DNSTypes = [ label: 'PorkBun', value: 'PorkBun', }, + { + label: 'DNSPod', + value: 'DnsPod', + deprecated: true, + }, { label: 'Dynadot', value: 'Dynadot', diff --git a/frontend/src/views/website/ssl/dns-account/create/index.vue b/frontend/src/views/website/ssl/dns-account/create/index.vue index faff8995babb..707dfe0c4eb4 100644 --- a/frontend/src/views/website/ssl/dns-account/create/index.vue +++ b/frontend/src/views/website/ssl/dns-account/create/index.vue @@ -11,10 +11,14 @@ + + {{ $t('ssl.dnsPodRemovedTip') }} +