From 5390ef3140e39d3065de730e89391fede7ee6af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sat, 23 Mar 2024 00:50:59 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=20lego?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/http/controllers/cert_controller.go | 2 +- app/http/requests/cert/dns_store.go | 1 - docs/docs.go | 14 +- docs/swagger.json | 14 +- docs/swagger.yaml | 12 +- go.mod | 22 +- go.sum | 61 ++--- internal/cert.go | 10 +- internal/services/cert.go | 242 +++++++++----------- pkg/acme/acme.go | 292 +++++++++++++----------- pkg/acme/client.go | 261 +++++++++------------ pkg/acme/client_test.go | 24 +- pkg/acme/dns_manual.go | 19 -- pkg/acme/solvers.go | 189 +++++++++++++++ 14 files changed, 615 insertions(+), 548 deletions(-) delete mode 100644 pkg/acme/dns_manual.go create mode 100644 pkg/acme/solvers.go diff --git a/app/http/controllers/cert_controller.go b/app/http/controllers/cert_controller.go index d385bbb8..749bb703 100644 --- a/app/http/controllers/cert_controller.go +++ b/app/http/controllers/cert_controller.go @@ -650,7 +650,7 @@ func (r *CertController) Renew(ctx http.Context) http.Response { // @Produce json // @Security BearerToken // @Param data body requests.Obtain true "request" -// @Success 200 {object} SuccessResponse{data=map[string]acme.Resolve} +// @Success 200 {object} SuccessResponse{data=[]acme.DNSRecord} // @Router /panel/cert/manualDNS [post] func (r *CertController) ManualDNS(ctx http.Context) http.Response { var obtainRequest requests.Obtain diff --git a/app/http/requests/cert/dns_store.go b/app/http/requests/cert/dns_store.go index 043831a8..2d3bcfa0 100644 --- a/app/http/requests/cert/dns_store.go +++ b/app/http/requests/cert/dns_store.go @@ -26,7 +26,6 @@ func (r *DNSStore) Rules(ctx http.Context) map[string]string { "data.token": "required_if:type,dnspod", "data.access_key": "required_if:type,aliyun", "data.secret_key": "required_if:type,aliyun", - "data.email": "required_if:type,cloudflare", "data.api_key": "required_if:type,cloudflare", } } diff --git a/docs/docs.go b/docs/docs.go index 6235d248..9f05b61e 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -535,9 +535,9 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/acme.Resolve" + "type": "array", + "items": { + "$ref": "#/definitions/acme.DNSRecord" } } } @@ -3696,9 +3696,6 @@ const docTemplate = `{ "api_key": { "type": "string" }, - "email": { - "type": "string" - }, "id": { "type": "string" }, @@ -3710,12 +3707,9 @@ const docTemplate = `{ } } }, - "acme.Resolve": { + "acme.DNSRecord": { "type": "object", "properties": { - "err": { - "type": "string" - }, "key": { "type": "string" }, diff --git a/docs/swagger.json b/docs/swagger.json index 17027feb..e0efe1b9 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -528,9 +528,9 @@ "type": "object", "properties": { "data": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/acme.Resolve" + "type": "array", + "items": { + "$ref": "#/definitions/acme.DNSRecord" } } } @@ -3689,9 +3689,6 @@ "api_key": { "type": "string" }, - "email": { - "type": "string" - }, "id": { "type": "string" }, @@ -3703,12 +3700,9 @@ } } }, - "acme.Resolve": { + "acme.DNSRecord": { "type": "object", "properties": { - "err": { - "type": "string" - }, "key": { "type": "string" }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 58c4a27a..15f1233e 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -6,8 +6,6 @@ definitions: type: string api_key: type: string - email: - type: string id: type: string secret_key: @@ -15,10 +13,8 @@ definitions: token: type: string type: object - acme.Resolve: + acme.DNSRecord: properties: - err: - type: string key: type: string value: @@ -955,9 +951,9 @@ paths: - $ref: '#/definitions/controllers.SuccessResponse' - properties: data: - additionalProperties: - $ref: '#/definitions/acme.Resolve' - type: object + items: + $ref: '#/definitions/acme.DNSRecord' + type: array type: object security: - BearerToken: [] diff --git a/go.mod b/go.mod index 30f15ee2..5c8683cd 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/docker/docker v25.0.5+incompatible github.com/docker/go-connections v0.5.0 github.com/gertd/go-pluralize v0.2.1 - github.com/go-acme/lego/v4 v4.16.1 github.com/gookit/color v1.5.4 github.com/gookit/validate v1.5.2 github.com/goravel/framework v1.13.1-0.20240215091018-1a9f352e523c @@ -14,6 +13,11 @@ require ( github.com/gorilla/websocket v1.5.1 github.com/iancoleman/strcase v0.3.0 github.com/imroc/req/v3 v3.43.1 + github.com/libdns/alidns v1.0.3 + github.com/libdns/cloudflare v0.1.1 + github.com/libdns/dnspod v0.0.3 + github.com/libdns/libdns v0.2.2 + github.com/mholt/acmez v1.2.0 github.com/mholt/archiver/v3 v3.5.1 github.com/mojocn/base64Captcha v1.3.6 github.com/shirou/gopsutil v3.21.11+incompatible @@ -21,6 +25,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/swaggo/http-swagger/v2 v2.0.2 github.com/swaggo/swag v1.16.3 + go.uber.org/zap v1.24.0 golang.org/x/crypto v0.21.0 ) @@ -41,7 +46,6 @@ require ( github.com/Microsoft/go-winio v0.6.1 // indirect github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae // indirect github.com/RichardKnop/machinery/v2 v2.0.12-0.20231012204029-bdb94a90ca41 // indirect - github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/aws/aws-sdk-go v1.49.6 // indirect github.com/bytedance/sonic v1.11.0 // indirect @@ -50,7 +54,6 @@ require ( github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/cloudflare/circl v1.3.7 // indirect - github.com/cloudflare/cloudflare-go v0.86.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -59,7 +62,6 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/fatih/color v1.15.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect @@ -67,7 +69,6 @@ require ( github.com/gin-gonic/gin v1.9.1 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect github.com/glebarez/sqlite v1.10.0 // indirect - github.com/go-jose/go-jose/v4 v4.0.1 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect @@ -94,7 +95,6 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/gomodule/redigo v2.0.0+incompatible // indirect - github.com/google/go-querystring v1.1.0 // indirect github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect @@ -106,13 +106,12 @@ require ( github.com/goravel/file-rotatelogs/v2 v2.4.2 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.4.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect + github.com/jackc/pgx/v5 v5.5.5 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -128,7 +127,6 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/microsoft/go-mssqldb v1.6.0 // indirect - github.com/miekg/dns v1.1.58 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -209,7 +207,7 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect google.golang.org/grpc v1.61.1 // indirect - google.golang.org/protobuf v1.32.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 370e1358..60d80c37 100644 --- a/go.sum +++ b/go.sum @@ -47,7 +47,6 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= @@ -71,7 +70,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= github.com/Azure/go-autorest/autorest/adal v0.9.22 h1:/GblQdIudfEM3AWWZ0mrYJQSd7JS4S/Mbzh6F0ov0Xc= github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= @@ -96,8 +94,6 @@ github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae h1:DcFpTQBYQ9C github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae/go.mod h1:rJJ84PyA/Wlmw1hO+xTzV2wsSUon6J5ktg0g8BF2PuU= github.com/RichardKnop/machinery/v2 v2.0.12-0.20231012204029-bdb94a90ca41 h1:7fLtRodG39Hn6AKqKE+ejO0Jj9B8O9RQOto13lkoDac= github.com/RichardKnop/machinery/v2 v2.0.12-0.20231012204029-bdb94a90ca41/go.mod h1:92dLVxckr2Lv7oKxoS3Uci0Of8Cuy+lmPi2dd6Euwkw= -github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass= -github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= @@ -106,6 +102,7 @@ github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/ github.com/aws/aws-sdk-go v1.37.16/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.49.6 h1:yNldzF5kzLBRvKlKz1S0bkvc2+04R1kt13KfBWQBfFA= github.com/aws/aws-sdk-go v1.49.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= @@ -143,8 +140,6 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cloudflare/cloudflare-go v0.86.0 h1:jEKN5VHNYNYtfDL2lUFLTRo+nOVNPFxpXTstVx0rqHI= -github.com/cloudflare/cloudflare-go v0.86.0/go.mod h1:wYW/5UP02TUfBToa/yKbQHV+r6h1NnJ1Je7XjuGM4Jw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -187,8 +182,6 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -210,13 +203,9 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc= github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA= -github.com/go-acme/lego/v4 v4.16.1 h1:JxZ93s4KG0jL27rZ30UsIgxap6VGzKuREsSkkyzeoCQ= -github.com/go-acme/lego/v4 v4.16.1/go.mod h1:AVvwdPned/IWpD/ihHhMsKnveF7HHYAz/CmtXi7OZoE= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= -github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -291,7 +280,6 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= @@ -363,8 +351,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -423,15 +409,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= -github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -448,10 +427,12 @@ github.com/imroc/req/v3 v3.43.1/go.mod h1:SQIz5iYop16MJxbo8ib+4LnostGCok8NQf8Toy github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= -github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= @@ -463,7 +444,6 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -471,7 +451,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -513,22 +492,30 @@ github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2t github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ= +github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE= +github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054= +github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU= +github.com/libdns/dnspod v0.0.3 h1:xJHDIujgLjvZnpB8/rMoCHUqA/KxSGBqRUXxSIzNzAA= +github.com/libdns/dnspod v0.0.3/go.mod h1:XLnqMmK7QlLPEbHwcOxbRvlzRvDgaaUlthRNFOPjXPI= +github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= +github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= +github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= +github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= +github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= github.com/microsoft/go-mssqldb v1.6.0 h1:mM3gYdVwEPFrlg/Dvr2DNVEgYFG7L42l+dGc67NNNpc= github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU= -github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= -github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= @@ -536,7 +523,6 @@ github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= @@ -756,12 +742,16 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= @@ -1168,8 +1158,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1177,7 +1167,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/internal/cert.go b/internal/cert.go index 1df73bbe..36c1c67f 100644 --- a/internal/cert.go +++ b/internal/cert.go @@ -1,8 +1,6 @@ package internal import ( - "github.com/go-acme/lego/v4/certificate" - requests "panel/app/http/requests/cert" "panel/app/models" "panel/pkg/acme" @@ -21,8 +19,8 @@ type Cert interface { CertUpdate(request requests.CertUpdate) error CertShow(ID uint) (models.Cert, error) CertDestroy(ID uint) error - ObtainAuto(ID uint) (certificate.Resource, error) - ObtainManual(ID uint) (certificate.Resource, error) - ManualDNS(ID uint) (map[string]acme.Resolve, error) - Renew(ID uint) (certificate.Resource, error) + ObtainAuto(ID uint) (acme.Certificate, error) + ObtainManual(ID uint) (acme.Certificate, error) + ManualDNS(ID uint) ([]acme.DNSRecord, error) + Renew(ID uint) (acme.Certificate, error) } diff --git a/internal/services/cert.go b/internal/services/cert.go index ccf863bd..57f1f565 100644 --- a/internal/services/cert.go +++ b/internal/services/cert.go @@ -2,10 +2,10 @@ package services import ( + "context" "errors" + "time" - "github.com/go-acme/lego/v4/certcrypto" - "github.com/go-acme/lego/v4/certificate" "github.com/goravel/framework/facades" requests "panel/app/http/requests/cert" @@ -15,6 +15,7 @@ import ( ) type CertImpl struct { + client *acme.Client } func NewCertImpl() *CertImpl { @@ -34,15 +35,15 @@ func (s *CertImpl) UserStore(request requests.UserStore) error { var client *acme.Client switch user.CA { case "letsencrypt": - client, err = acme.NewRegisterClient(user.Email, acme.CALetEncrypt, certcrypto.KeyType(user.KeyType)) + client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CALetsEncrypt, nil, acme.KeyType(user.KeyType)) case "buypass": - client, err = acme.NewRegisterClient(user.Email, acme.CABuypass, certcrypto.KeyType(user.KeyType)) + client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CABuypass, nil, acme.KeyType(user.KeyType)) case "zerossl": - client, err = acme.NewRegisterWithExternalAccountBindingClient(user.Email, *user.Kid, *user.HmacEncoded, acme.CAZeroSSL, certcrypto.KeyType(user.KeyType)) + client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAZeroSSL, &acme.EAB{KeyID: *user.Kid, MACKey: *user.HmacEncoded}, acme.KeyType(user.KeyType)) case "sslcom": - client, err = acme.NewRegisterWithExternalAccountBindingClient(user.Email, *user.Kid, *user.HmacEncoded, acme.CASSLcom, certcrypto.KeyType(user.KeyType)) + client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CASSLcom, &acme.EAB{KeyID: *user.Kid, MACKey: *user.HmacEncoded}, acme.KeyType(user.KeyType)) case "google": - client, err = acme.NewRegisterWithExternalAccountBindingClient(user.Email, *user.Kid, *user.HmacEncoded, acme.CAGoogle, certcrypto.KeyType(user.KeyType)) + client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAGoogle, &acme.EAB{KeyID: *user.Kid, MACKey: *user.HmacEncoded}, acme.KeyType(user.KeyType)) default: return errors.New("CA 提供商不支持") } @@ -51,7 +52,7 @@ func (s *CertImpl) UserStore(request requests.UserStore) error { return errors.New("向 CA 注册账号失败,请检查参数是否正确") } - privateKey, err := acme.GetPrivateKey(client.User.GetPrivateKey(), acme.KeyType(user.KeyType)) + privateKey, err := acme.EncodePrivateKey(client.Account.PrivateKey) if err != nil { return errors.New("获取私钥失败") } @@ -77,15 +78,15 @@ func (s *CertImpl) UserUpdate(request requests.UserUpdate) error { var client *acme.Client switch user.CA { case "letsencrypt": - client, err = acme.NewRegisterClient(user.Email, acme.CALetEncrypt, certcrypto.KeyType(user.KeyType)) + client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CALetsEncrypt, nil, acme.KeyType(user.KeyType)) case "buypass": - client, err = acme.NewRegisterClient(user.Email, acme.CABuypass, certcrypto.KeyType(user.KeyType)) + client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CABuypass, nil, acme.KeyType(user.KeyType)) case "zerossl": - client, err = acme.NewRegisterWithExternalAccountBindingClient(user.Email, *user.Kid, *user.HmacEncoded, acme.CAZeroSSL, certcrypto.KeyType(user.KeyType)) + client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAZeroSSL, &acme.EAB{KeyID: *user.Kid, MACKey: *user.HmacEncoded}, acme.KeyType(user.KeyType)) case "sslcom": - client, err = acme.NewRegisterWithExternalAccountBindingClient(user.Email, *user.Kid, *user.HmacEncoded, acme.CASSLcom, certcrypto.KeyType(user.KeyType)) + client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CASSLcom, &acme.EAB{KeyID: *user.Kid, MACKey: *user.HmacEncoded}, acme.KeyType(user.KeyType)) case "google": - client, err = acme.NewRegisterWithExternalAccountBindingClient(user.Email, *user.Kid, *user.HmacEncoded, acme.CAGoogle, certcrypto.KeyType(user.KeyType)) + client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAGoogle, &acme.EAB{KeyID: *user.Kid, MACKey: *user.HmacEncoded}, acme.KeyType(user.KeyType)) default: return errors.New("CA 提供商不支持") } @@ -94,7 +95,7 @@ func (s *CertImpl) UserUpdate(request requests.UserUpdate) error { return errors.New("向 CA 注册账号失败,请检查参数是否正确") } - privateKey, err := acme.GetPrivateKey(client.User.GetPrivateKey(), acme.KeyType(user.KeyType)) + privateKey, err := acme.EncodePrivateKey(client.Account.PrivateKey) if err != nil { return errors.New("获取私钥失败") } @@ -238,67 +239,50 @@ func (s *CertImpl) CertDestroy(ID uint) error { } // ObtainAuto 自动签发证书 -func (s *CertImpl) ObtainAuto(ID uint) (certificate.Resource, error) { +func (s *CertImpl) ObtainAuto(ID uint) (acme.Certificate, error) { var cert models.Cert err := facades.Orm().Query().With("Website").With("User").With("DNS").Where("id = ?", ID).First(&cert) if err != nil { - return certificate.Resource{}, err + return acme.Certificate{}, err } - var ca string - switch cert.User.CA { - case "letsencrypt": - ca = acme.CALetEncrypt - case "buypass": - ca = acme.CABuypass - case "zerossl": - ca = acme.CAZeroSSL - case "sslcom": - ca = acme.CASSLcom - case "google": - ca = acme.CAGoogle - } - - client, err := acme.NewPrivateKeyClient(cert.User.Email, cert.User.PrivateKey, ca, certcrypto.KeyType(cert.User.KeyType)) + client, err := s.getClient(cert) if err != nil { - return certificate.Resource{}, err + return acme.Certificate{}, err } if cert.DNS != nil { - err = client.UseDns(acme.DnsType(cert.DNS.Type), cert.DNS.Data) + client.UseDns(acme.DnsType(cert.DNS.Type), cert.DNS.Data) } else { if cert.Website == nil { - return certificate.Resource{}, errors.New("该证书没有关联网站,无法自动签发") + return acme.Certificate{}, errors.New("该证书没有关联网站,无法自动签发") } else { - err = client.UseHTTP(cert.Website.Path) + client.UseHTTP(cert.Website.Path) } } + + ssl, err := client.ObtainSSL(context.Background(), cert.Domains, acme.KeyType(cert.Type)) if err != nil { - return certificate.Resource{}, err + return acme.Certificate{}, err } - ssl, err := client.ObtainSSL(cert.Domains) - if err != nil { - return certificate.Resource{}, err - } - - cert.CertURL = &ssl.CertURL - cert.Cert = string(ssl.Certificate) + cert.CertURL = &ssl.URL + cert.Cert = string(ssl.ChainPEM) cert.Key = string(ssl.PrivateKey) err = facades.Orm().Query().Save(&cert) if err != nil { - return certificate.Resource{}, err + return acme.Certificate{}, err } if cert.Website != nil { - if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", string(ssl.Certificate), 0644); err != nil { - return certificate.Resource{}, err + if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil { + return acme.Certificate{}, err } - if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", string(ssl.PrivateKey), 0644); err != nil { - return certificate.Resource{}, err + if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil { + return acme.Certificate{}, err } - if _, err := tools.Exec("systemctl reload openresty"); err != nil { - return certificate.Resource{}, err + if err = tools.ServiceReload("openresty"); err != nil { + return acme.Certificate{}, err } } @@ -306,59 +290,39 @@ func (s *CertImpl) ObtainAuto(ID uint) (certificate.Resource, error) { } // ObtainManual 手动签发证书 -func (s *CertImpl) ObtainManual(ID uint) (certificate.Resource, error) { +func (s *CertImpl) ObtainManual(ID uint) (acme.Certificate, error) { var cert models.Cert err := facades.Orm().Query().With("User").Where("id = ?", ID).First(&cert) if err != nil { - return certificate.Resource{}, err + return acme.Certificate{}, err } - var ca string - switch cert.User.CA { - case "letsencrypt": - ca = acme.CALetEncrypt - case "buypass": - ca = acme.CABuypass - case "zerossl": - ca = acme.CAZeroSSL - case "sslcom": - ca = acme.CASSLcom - case "google": - ca = acme.CAGoogle + if s.client == nil { + return acme.Certificate{}, errors.New("请重新获取 DNS 解析记录") } - client, err := acme.NewPrivateKeyClient(cert.User.Email, cert.User.PrivateKey, ca, certcrypto.KeyType(cert.User.KeyType)) + ssl, err := s.client.ObtainSSLManual() if err != nil { - return certificate.Resource{}, err + return acme.Certificate{}, err } - err = client.UseManualDns() - if err != nil { - return certificate.Resource{}, err - } - - ssl, err := client.ObtainSSL(cert.Domains) - if err != nil { - return certificate.Resource{}, err - } - - cert.CertURL = &ssl.CertURL - cert.Cert = string(ssl.Certificate) + cert.CertURL = &ssl.URL + cert.Cert = string(ssl.ChainPEM) cert.Key = string(ssl.PrivateKey) err = facades.Orm().Query().Save(&cert) if err != nil { - return certificate.Resource{}, err + return acme.Certificate{}, err } if cert.Website != nil { - if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", string(ssl.Certificate), 0644); err != nil { - return certificate.Resource{}, err + if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil { + return acme.Certificate{}, err } - if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", string(ssl.PrivateKey), 0644); err != nil { - return certificate.Resource{}, err + if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil { + return acme.Certificate{}, err } - if _, err := tools.Exec("systemctl reload openresty"); err != nil { - return certificate.Resource{}, err + if err = tools.ServiceReload("openresty"); err != nil { + return acme.Certificate{}, err } } @@ -366,108 +330,106 @@ func (s *CertImpl) ObtainManual(ID uint) (certificate.Resource, error) { } // ManualDNS 获取手动 DNS 解析信息 -func (s *CertImpl) ManualDNS(ID uint) (map[string]acme.Resolve, error) { +func (s *CertImpl) ManualDNS(ID uint) ([]acme.DNSRecord, error) { var cert models.Cert err := facades.Orm().Query().With("User").Where("id = ?", ID).First(&cert) if err != nil { return nil, err } - var ca string - switch cert.User.CA { - case "letsencrypt": - ca = acme.CALetEncrypt - case "buypass": - ca = acme.CABuypass - case "zerossl": - ca = acme.CAZeroSSL - case "sslcom": - ca = acme.CASSLcom - case "google": - ca = acme.CAGoogle - } - - client, err := acme.NewPrivateKeyClient(cert.User.Email, cert.User.PrivateKey, ca, certcrypto.KeyType(cert.User.KeyType)) + client, err := s.getClient(cert) if err != nil { return nil, err } - err = client.UseManualDns() - if err != nil { - return nil, err - } + client.UseManualDns(len(cert.Domains)) + records, err := client.GetDNSRecords(context.Background(), cert.Domains, acme.KeyType(cert.Type)) - return client.GetDNSResolve(cert.Domains) + // 15 分钟后清理客户端 + s.client = client + time.AfterFunc(15*time.Minute, func() { + s.client = nil + }) + + return records, err } // Renew 续签证书 -func (s *CertImpl) Renew(ID uint) (certificate.Resource, error) { +func (s *CertImpl) Renew(ID uint) (acme.Certificate, error) { var cert models.Cert err := facades.Orm().Query().With("Website").With("User").With("DNS").Where("id = ?", ID).First(&cert) if err != nil { - return certificate.Resource{}, err + return acme.Certificate{}, err } - var ca string - switch cert.User.CA { - case "letsencrypt": - ca = acme.CALetEncrypt - case "buypass": - ca = acme.CABuypass - case "zerossl": - ca = acme.CAZeroSSL - case "sslcom": - ca = acme.CASSLcom - case "google": - ca = acme.CAGoogle - } - - client, err := acme.NewPrivateKeyClient(cert.User.Email, cert.User.PrivateKey, ca, certcrypto.KeyType(cert.User.KeyType)) + client, err := s.getClient(cert) if err != nil { - return certificate.Resource{}, err + return acme.Certificate{}, err } if cert.CertURL == nil { - return certificate.Resource{}, errors.New("该证书没有签发成功,无法续签") + return acme.Certificate{}, errors.New("该证书没有签发成功,无法续签") } if cert.DNS != nil { - err = client.UseDns(acme.DnsType(cert.DNS.Type), cert.DNS.Data) + client.UseDns(acme.DnsType(cert.DNS.Type), cert.DNS.Data) } else { if cert.Website == nil { - return certificate.Resource{}, errors.New("该证书没有关联网站,无法续签,可以尝试手动签发") + return acme.Certificate{}, errors.New("该证书没有关联网站,无法续签,可以尝试手动签发") } else { - err = client.UseHTTP(cert.Website.Path) + client.UseHTTP(cert.Website.Path) } } if err != nil { - return certificate.Resource{}, err + return acme.Certificate{}, err } - ssl, err := client.RenewSSL(*cert.CertURL) + ssl, err := client.RenewSSL(context.Background(), *cert.CertURL, cert.Domains, acme.KeyType(cert.Type)) if err != nil { - return certificate.Resource{}, err + return acme.Certificate{}, err } - cert.CertURL = &ssl.CertURL - cert.Cert = string(ssl.Certificate) + cert.CertURL = &ssl.URL + cert.Cert = string(ssl.ChainPEM) cert.Key = string(ssl.PrivateKey) err = facades.Orm().Query().Save(&cert) if err != nil { - return certificate.Resource{}, err + return acme.Certificate{}, err } if cert.Website != nil { - if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", string(ssl.Certificate), 0644); err != nil { - return certificate.Resource{}, err + if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil { + return acme.Certificate{}, err } - if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", string(ssl.PrivateKey), 0644); err != nil { - return certificate.Resource{}, err + if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil { + return acme.Certificate{}, err } - if _, err := tools.Exec("systemctl reload openresty"); err != nil { - return certificate.Resource{}, err + if err = tools.ServiceReload("openresty"); err != nil { + return acme.Certificate{}, err } } return ssl, nil } + +func (s *CertImpl) getClient(cert models.Cert) (*acme.Client, error) { + var ca string + var eab *acme.EAB + switch cert.User.CA { + case "letsencrypt": + ca = acme.CALetsEncrypt + case "buypass": + ca = acme.CABuypass + case "zerossl": + ca = acme.CAZeroSSL + eab = &acme.EAB{KeyID: *cert.User.Kid, MACKey: *cert.User.HmacEncoded} + case "sslcom": + ca = acme.CASSLcom + eab = &acme.EAB{KeyID: *cert.User.Kid, MACKey: *cert.User.HmacEncoded} + case "google": + ca = acme.CAGoogle + eab = &acme.EAB{KeyID: *cert.User.Kid, MACKey: *cert.User.HmacEncoded} + } + + return acme.NewPrivateKeyAccount(cert.User.Email, cert.User.PrivateKey, ca, eab) +} diff --git a/pkg/acme/acme.go b/pkg/acme/acme.go index 0f2bbac1..30ea620a 100644 --- a/pkg/acme/acme.go +++ b/pkg/acme/acme.go @@ -1,177 +1,195 @@ package acme import ( + "context" "crypto" "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" + "errors" + "fmt" + "net/http" + "strings" - "github.com/go-acme/lego/v4/certcrypto" - "github.com/go-acme/lego/v4/lego" - "github.com/go-acme/lego/v4/registration" + "github.com/mholt/acmez" + "github.com/mholt/acmez/acme" + "go.uber.org/zap" ) const ( - CALetEncrypt = "https://acme-v02.api.letsencrypt.org/directory" - CAZeroSSL = "https://acme.zerossl.com/v2/DV90" - CAGoogle = "https://dv.acme-v02.api.pki.goog/directory" - CABuypass = "https://api.buypass.com/acme/directory" - CASSLcom = "https://acme.ssl.com/sslcom-dv-rsa" + CALetsEncryptStaging = "https://acme-staging-v02.api.letsencrypt.org/directory" + CALetsEncrypt = "https://acme-v02.api.letsencrypt.org/directory" + CAZeroSSL = "https://acme.zerossl.com/v2/DV90" + CAGoogle = "https://dv.acme-v02.api.pki.goog/directory" + CABuypass = "https://api.buypass.com/acme/directory" + CASSLcom = "https://acme.ssl.com/sslcom-dv-rsa" ) -type KeyType = certcrypto.KeyType +type KeyType string const ( - KeyEC256 = certcrypto.EC256 - KeyEC384 = certcrypto.EC384 - KeyRSA2048 = certcrypto.RSA2048 - KeyRSA3072 = certcrypto.RSA3072 - KeyRSA4096 = certcrypto.RSA4096 + KeyEC256 = KeyType("P256") + KeyEC384 = KeyType("P384") + KeyRSA2048 = KeyType("2048") + KeyRSA3072 = KeyType("3072") + KeyRSA4096 = KeyType("4096") ) -type domainError struct { - Domain string - Error error -} +type EAB = acme.EAB -type User struct { - Email string - Registration *registration.Resource - Key crypto.PrivateKey -} +func NewRegisterAccount(ctx context.Context, email, CA string, eab *EAB, keyType KeyType) (*Client, error) { + client, err := getClient(CA) + if err != nil { + return nil, err + } -func (u *User) GetEmail() string { - return u.Email -} - -func (u *User) GetRegistration() *registration.Resource { - return u.Registration -} -func (u *User) GetPrivateKey() crypto.PrivateKey { - return u.Key -} - -func GetPrivateKey(priKey crypto.PrivateKey, keyType KeyType) ([]byte, error) { - var marshal []byte - var block *pem.Block - var err error - - switch keyType { - case KeyEC256, KeyEC384: - key := priKey.(*ecdsa.PrivateKey) - marshal, err = x509.MarshalECPrivateKey(key) + accountPrivateKey, err := generatePrivateKey(keyType) + if err != nil { + return nil, err + } + account := acme.Account{ + Contact: []string{"mailto:" + email}, + TermsOfServiceAgreed: true, + PrivateKey: accountPrivateKey, + } + if eab != nil { + err = account.SetExternalAccountBinding(ctx, client.Client, *eab) if err != nil { return nil, err } - block = &pem.Block{ - Type: "PRIVATE KEY", - Bytes: marshal, - } - case KeyRSA2048, KeyRSA3072, KeyRSA4096: - key := priKey.(*rsa.PrivateKey) - marshal = x509.MarshalPKCS1PrivateKey(key) - block = &pem.Block{ - Type: "privateKey", - Bytes: marshal, + } + + account, err = client.NewAccount(ctx, account) + if err != nil { + return nil, err + } + + return &Client{Account: account, zClient: client}, nil +} + +func NewPrivateKeyAccount(email string, privateKey string, CA string, eab *EAB) (*Client, error) { + client, err := getClient(CA) + if err != nil { + return nil, err + } + + key, err := parsePrivateKey([]byte(privateKey)) + if err != nil { + return nil, err + } + + account := acme.Account{ + Contact: []string{"mailto:" + email}, + TermsOfServiceAgreed: true, + PrivateKey: key, + } + if eab != nil { + err = account.SetExternalAccountBinding(context.Background(), client.Client, *eab) + if err != nil { + return nil, err } } - return pem.EncodeToMemory(block), nil + account, err = client.GetAccount(context.Background(), account) + if err != nil { + return nil, err + } + + return &Client{Account: account, zClient: client}, nil } -func NewRegisterClient(email string, CA string, keyType certcrypto.KeyType) (*Client, error) { - privateKey, err := certcrypto.GeneratePrivateKey(keyType) - if err != nil { - return nil, err +func parsePrivateKey(key []byte) (crypto.Signer, error) { + keyBlockDER, _ := pem.Decode(key) + if keyBlockDER == nil { + return nil, errors.New("invalid PEM block") } - user := &User{ - Email: email, - Key: privateKey, - } - config := lego.NewConfig(user) - config.CADirURL = CA - config.Certificate.KeyType = keyType - client, err := lego.NewClient(config) - if err != nil { - return nil, err - } - reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) - if err != nil { - return nil, err - } - user.Registration = reg - - acmeClient := &Client{ - User: user, - Client: client, - Config: config, + if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") { + return nil, fmt.Errorf("unknown PEM header %q", keyBlockDER.Type) } - return acmeClient, nil + if parse, err := x509.ParsePKCS1PrivateKey(keyBlockDER.Bytes); err == nil { + return parse, nil + } + + if parse, err := x509.ParsePKCS8PrivateKey(keyBlockDER.Bytes); err == nil { + switch parse.(type) { + case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey: + return parse.(crypto.Signer), nil + default: + return nil, fmt.Errorf("found unknown private key type in PKCS#8 wrapping: %T", key) + } + } + + if parse, err := x509.ParseECPrivateKey(keyBlockDER.Bytes); err == nil { + return parse, nil + } + + return nil, errors.New("解析私钥失败") } -func NewRegisterWithExternalAccountBindingClient(email, kid, hmac, CA string, keyType certcrypto.KeyType) (*Client, error) { - privateKey, err := certcrypto.GeneratePrivateKey(keyType) - if err != nil { - return nil, err +func generatePrivateKey(keyType KeyType) (crypto.Signer, error) { + switch keyType { + case KeyEC256: + return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + case KeyEC384: + return ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + case KeyRSA2048: + return rsa.GenerateKey(rand.Reader, 2048) + case KeyRSA3072: + return rsa.GenerateKey(rand.Reader, 3072) + case KeyRSA4096: + return rsa.GenerateKey(rand.Reader, 4096) } - user := &User{ - Email: email, - Key: privateKey, - } - config := lego.NewConfig(user) - config.CADirURL = CA - config.Certificate.KeyType = keyType - client, err := lego.NewClient(config) - if err != nil { - return nil, err - } - reg, err := client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{TermsOfServiceAgreed: true, Kid: kid, HmacEncoded: hmac}) - if err != nil { - return nil, err - } - user.Registration = reg - - acmeClient := &Client{ - User: user, - Client: client, - Config: config, - } - - return acmeClient, nil + return nil, errors.New("未知的密钥类型") } -func NewPrivateKeyClient(email string, privateKey string, CA string, keyType certcrypto.KeyType) (*Client, error) { - key, err := certcrypto.ParsePEMPrivateKey([]byte(privateKey)) - if err != nil { - return nil, err +func EncodePrivateKey(key crypto.Signer) ([]byte, error) { + var pemType string + var keyBytes []byte + switch key := key.(type) { + case *ecdsa.PrivateKey: + var err error + pemType = "EC" + keyBytes, err = x509.MarshalECPrivateKey(key) + if err != nil { + return nil, err + } + case *rsa.PrivateKey: + pemType = "RSA" + keyBytes = x509.MarshalPKCS1PrivateKey(key) + case ed25519.PrivateKey: + var err error + pemType = "ED25519" + keyBytes, err = x509.MarshalPKCS8PrivateKey(key) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("未知的密钥类型 %T", key) } - - user := &User{ - Email: email, - Key: key, - } - config := lego.NewConfig(user) - config.CADirURL = CA - config.Certificate.KeyType = keyType - client, err := lego.NewClient(config) - if err != nil { - return nil, err - } - reg, err := client.Registration.ResolveAccountByKey() - if err != nil { - return nil, err - } - user.Registration = reg - - acmeClient := &Client{ - User: user, - Client: client, - Config: config, - } - - return acmeClient, nil + pemKey := pem.Block{Type: pemType + " PRIVATE KEY", Bytes: keyBytes} + return pem.EncodeToMemory(&pemKey), nil +} + +func getClient(CA string) (acmez.Client, error) { + logger, err := zap.NewProduction() + if err != nil { + return acmez.Client{}, err + } + + client := acmez.Client{ + Client: &acme.Client{ + Directory: CA, + HTTPClient: http.DefaultClient, + Logger: logger, + }, + } + + return client, nil } diff --git a/pkg/acme/client.go b/pkg/acme/client.go index f4f8d197..7ad89510 100644 --- a/pkg/acme/client.go +++ b/pkg/acme/client.go @@ -1,192 +1,139 @@ package acme import ( - "time" + "context" + "sort" - "github.com/go-acme/lego/v4/acme" - "github.com/go-acme/lego/v4/acme/api" - "github.com/go-acme/lego/v4/certificate" - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/lego" - "github.com/go-acme/lego/v4/providers/dns/alidns" - "github.com/go-acme/lego/v4/providers/dns/cloudflare" - "github.com/go-acme/lego/v4/providers/dns/dnspod" - "github.com/go-acme/lego/v4/providers/http/webroot" + "github.com/libdns/libdns" + "github.com/mholt/acmez" + "github.com/mholt/acmez/acme" ) -type Client struct { - Config *lego.Config - Client *lego.Client - User *User +type Certificate struct { + PrivateKey []byte + acme.Certificate } -type DnsType string - -const ( - DnsPod DnsType = "dnspod" - AliYun DnsType = "aliyun" - CloudFlare DnsType = "cloudflare" -) - -type DNSParam struct { - ID string `form:"id" json:"id"` - Token string `form:"token" json:"token"` - AccessKey string `form:"access_key" json:"access_key"` - SecretKey string `form:"secret_key" json:"secret_key"` - Email string `form:"email" json:"email"` - APIkey string `form:"api_key" json:"api_key"` +type Client struct { + Account acme.Account + zClient acmez.Client + // 手动 DNS 所需的信号通道 + manualDNSSolver } // UseDns 使用 DNS 接口验证 -func (c *Client) UseDns(dnsType DnsType, param DNSParam) error { - var p challenge.Provider - var err error - if dnsType == DnsPod { - dnsPodConfig := dnspod.NewDefaultConfig() - dnsPodConfig.LoginToken = param.ID + "," + param.Token - p, err = dnspod.NewDNSProviderConfig(dnsPodConfig) - if err != nil { - return err - } +func (c *Client) UseDns(dnsType DnsType, param DNSParam) { + c.zClient.ChallengeSolvers = map[string]acmez.Solver{ + acme.ChallengeTypeDNS01: dnsSolver{ + dns: dnsType, + param: param, + records: &[]libdns.Record{}, + }, } - if dnsType == AliYun { - aliyunConfig := alidns.NewDefaultConfig() - aliyunConfig.SecretKey = param.SecretKey - aliyunConfig.APIKey = param.AccessKey - p, err = alidns.NewDNSProviderConfig(aliyunConfig) - if err != nil { - return err - } - } - if dnsType == CloudFlare { - cloudflareConfig := cloudflare.NewDefaultConfig() - cloudflareConfig.AuthEmail = param.Email - cloudflareConfig.AuthToken = param.APIkey - p, err = cloudflare.NewDNSProviderConfig(cloudflareConfig) - if err != nil { - return err - } - } - - return c.Client.Challenge.SetDNS01Provider(p, dns01.AddDNSTimeout(3*time.Minute)) } // UseManualDns 使用手动 DNS 验证 -func (c *Client) UseManualDns(checkDns ...bool) error { - p := &manualDnsProvider{} - var err error - - if len(checkDns) > 0 && !checkDns[0] { - err = c.Client.Challenge.SetDNS01Provider(p, dns01.DisableCompletePropagationRequirement()) - } else { - err = c.Client.Challenge.SetDNS01Provider(p, dns01.AddDNSTimeout(3*time.Minute)) +func (c *Client) UseManualDns(total int, check ...bool) { + c.controlChan = make(chan struct{}) + c.dataChan = make(chan any) + c.zClient.ChallengeSolvers = map[string]acmez.Solver{ + acme.ChallengeTypeDNS01: manualDNSSolver{ + check: len(check) > 0 && check[0], + controlChan: c.controlChan, + dataChan: c.dataChan, + records: &[]DNSRecord{}, + }, } - - return err } // UseHTTP 使用 HTTP 验证 -func (c *Client) UseHTTP(path string) error { - httpProvider, err := webroot.NewHTTPProvider(path) - if err != nil { - return err +func (c *Client) UseHTTP(path string) { + c.zClient.ChallengeSolvers = map[string]acmez.Solver{ + acme.ChallengeTypeHTTP01: httpSolver{ + path: path, + }, } - - err = c.Client.Challenge.SetHTTP01Provider(httpProvider) - if err != nil { - return err - } - return nil } // ObtainSSL 签发 SSL 证书 -func (c *Client) ObtainSSL(domains []string) (certificate.Resource, error) { - request := certificate.ObtainRequest{ - Domains: domains, - Bundle: true, - MustStaple: false, - } - - certificates, err := c.Client.Certificate.Obtain(request) +func (c *Client) ObtainSSL(ctx context.Context, domains []string, keyType KeyType) (Certificate, error) { + certPrivateKey, err := generatePrivateKey(keyType) if err != nil { - return certificate.Resource{}, err + return Certificate{}, err + } + pemPrivateKey, err := EncodePrivateKey(certPrivateKey) + if err != nil { + return Certificate{}, err } - return *certificates, nil + certs, err := c.zClient.ObtainCertificate(ctx, c.Account, certPrivateKey, domains) + if err != nil { + return Certificate{}, err + } + + cert := c.selectPreferredChain(certs) + return Certificate{PrivateKey: pemPrivateKey, Certificate: cert}, nil +} + +// ObtainSSLManual 手动验证 SSL 证书 +func (c *Client) ObtainSSLManual() (Certificate, error) { + // 发送信号,开始验证 + c.controlChan <- struct{}{} + // 等待验证完成 + data := <-c.dataChan + + if err, ok := data.(error); ok { + return Certificate{}, err + } + + return data.(Certificate), nil } // RenewSSL 续签 SSL 证书 -func (c *Client) RenewSSL(certUrl string) (certificate.Resource, error) { - certificates, err := c.Client.Certificate.Get(certUrl, true) +func (c *Client) RenewSSL(ctx context.Context, certUrl string, domains []string, keyType KeyType) (Certificate, error) { + _, err := c.zClient.GetCertificateChain(ctx, c.Account, certUrl) if err != nil { - return certificate.Resource{}, err + return Certificate{}, err } - certificates, err = c.Client.Certificate.RenewWithOptions(*certificates, &certificate.RenewOptions{ - Bundle: true, - MustStaple: false, + return c.ObtainSSL(ctx, domains, keyType) +} + +// GetDNSRecords 获取 DNS 解析(手动设置) +func (c *Client) GetDNSRecords(ctx context.Context, domains []string, keyType KeyType) ([]DNSRecord, error) { + go func(ctx context.Context, domains []string, keyType KeyType) { + certs, err := c.ObtainSSL(ctx, domains, keyType) + // 将证书和错误信息发送到 dataChan + if err != nil { + c.dataChan <- err + return + } + c.dataChan <- certs + }(ctx, domains, keyType) + + // 这里要少一次循环,因为需要卡住最后一次的 dataChan,等待手动 DNS 验证完成 + for i := 1; i < len(domains); i++ { + <-c.dataChan + c.controlChan <- struct{}{} + } + + // 因为上面少了一次循环,所以这里接收到的即为完整的 DNS 记录切片 + data := <-c.dataChan + if err, ok := data.(error); ok { + return nil, err + } + + return data.([]DNSRecord), nil +} + +func (c *Client) selectPreferredChain(certChains []acme.Certificate) acme.Certificate { + if len(certChains) == 1 { + return certChains[0] + } + + sort.Slice(certChains, func(i, j int) bool { + return len(certChains[i].ChainPEM) < len(certChains[j].ChainPEM) }) - if err != nil { - return certificate.Resource{}, err - } - return *certificates, nil -} - -// GetDNSResolve 获取 DNS 解析(手动设置) -func (c *Client) GetDNSResolve(domains []string) (map[string]Resolve, error) { - core, err := api.New(c.Config.HTTPClient, c.Config.UserAgent, c.Config.CADirURL, c.User.Registration.URI, c.User.Key) - if err != nil { - return nil, err - } - order, err := core.Orders.New(domains) - if err != nil { - return nil, err - } - resolves := make(map[string]Resolve) - resChan, errChan := make(chan acme.Authorization), make(chan domainError) - for _, authzURL := range order.Authorizations { - go func(authzURL string) { - authz, err := core.Authorizations.Get(authzURL) - if err != nil { - errChan <- domainError{Domain: authz.Identifier.Value, Error: err} - return - } - resChan <- authz - }(authzURL) - } - - var responses []acme.Authorization - for i := 0; i < len(order.Authorizations); i++ { - select { - case res := <-resChan: - responses = append(responses, res) - case err := <-errChan: - resolves[err.Domain] = Resolve{Err: err.Error.Error()} - } - } - close(resChan) - close(errChan) - - for _, auth := range responses { - domain := challenge.GetTargetedDomain(auth) - acmeChallenge, err := challenge.FindChallenge(challenge.DNS01, auth) - if err != nil { - resolves[domain] = Resolve{Err: err.Error()} - continue - } - keyAuth, err := core.GetKeyAuthorization(acmeChallenge.Token) - if err != nil { - resolves[domain] = Resolve{Err: err.Error()} - continue - } - challengeInfo := dns01.GetChallengeInfo(domain, keyAuth) - resolves[domain] = Resolve{ - Key: challengeInfo.FQDN, - Value: challengeInfo.Value, - } - } - - return resolves, nil + return certChains[0] } diff --git a/pkg/acme/client_test.go b/pkg/acme/client_test.go index a1b45e33..1a68041f 100644 --- a/pkg/acme/client_test.go +++ b/pkg/acme/client_test.go @@ -1,7 +1,7 @@ package acme import ( - "fmt" + "context" "testing" "github.com/stretchr/testify/suite" @@ -16,24 +16,26 @@ func TestClientTestSuite(t *testing.T) { } func (s *ClientTestSuite) TestObtainSSL() { - client, err := NewRegisterClient("ci@haozi.net", "https://acme-staging-v02.api.letsencrypt.org/directory", KeyEC256) + ctx := context.Background() + client, err := NewRegisterAccount(ctx, "ci@haozi.net", CALetsEncryptStaging, nil, KeyEC256) s.Nil(err) - err = client.UseDns(DnsPod, DNSParam{ - ID: "xxx", - Token: "xxx", + client.UseDns(DnsPod, DNSParam{ + ID: "123456", + Token: "654321", }) - s.Nil(err) - err = client.UseManualDns(false) - s.Nil(err) + /*client.UseManualDns(2) - resolves, err := client.GetDNSResolve([]string{"haozi.dev"}) + resolves, err := client.GetDNSRecords(ctx, []string{"*.haozi.net", "haozi.net"}, KeyEC256) + debug.Dump(resolves) s.Nil(err) s.NotNil(resolves) - ssl, err := client.ObtainSSL([]string{"haozi.dev"}) - fmt.Println(err.Error()) + time.Sleep(2 * time.Minute) + + ssl, err := client.ObtainSSLManual()*/ + ssl, err := client.ObtainSSL(ctx, []string{"*.haozi.net", "haozi.net"}, KeyEC256) s.Error(err) s.NotNil(ssl) } diff --git a/pkg/acme/dns_manual.go b/pkg/acme/dns_manual.go deleted file mode 100644 index 61c0daa4..00000000 --- a/pkg/acme/dns_manual.go +++ /dev/null @@ -1,19 +0,0 @@ -package acme - -type Resolve struct { - Key string `json:"key"` - Value string `json:"value"` - Err string `json:"err"` -} - -type manualDnsProvider struct { - Resolve *Resolve -} - -func (p *manualDnsProvider) Present(domain, token, keyAuth string) error { - return nil -} - -func (p *manualDnsProvider) CleanUp(domain, token, keyAuth string) error { - return nil -} diff --git a/pkg/acme/solvers.go b/pkg/acme/solvers.go new file mode 100644 index 00000000..191e1220 --- /dev/null +++ b/pkg/acme/solvers.go @@ -0,0 +1,189 @@ +package acme + +import ( + "context" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/libdns/alidns" + "github.com/libdns/cloudflare" + "github.com/libdns/dnspod" + "github.com/libdns/libdns" + "github.com/mholt/acmez/acme" + "golang.org/x/net/publicsuffix" +) + +type httpSolver struct { + path string +} + +func (s httpSolver) Present(ctx context.Context, challenge acme.Challenge) error { + var err error + if s.path == "" { + return nil + } + + challengeFilePath := filepath.Join(s.path, challenge.HTTP01ResourcePath()) + err = os.MkdirAll(filepath.Dir(challengeFilePath), 0o755) + if err != nil { + return fmt.Errorf("无法在网站目录创建 HTTP 挑战所需的目录: %w", err) + } + + err = os.WriteFile(challengeFilePath, []byte(challenge.KeyAuthorization), 0o644) + if err != nil { + return fmt.Errorf("无法在网站目录创建 HTTP 挑战所需的文件: %w", err) + } + + return nil +} + +// CleanUp cleans up the HTTP server if it is the last one to finish. +func (s httpSolver) CleanUp(_ context.Context, challenge acme.Challenge) error { + if s.path == "" { + return nil + } + + err := os.Remove(filepath.Join(s.path, challenge.HTTP01ResourcePath())) + if err != nil { + return fmt.Errorf("无法删除 HTTP 挑战文件: %w", err) + } + + return nil +} + +type dnsSolver struct { + dns DnsType + param DNSParam + records *[]libdns.Record +} + +func (s dnsSolver) Present(ctx context.Context, challenge acme.Challenge) error { + dnsName := challenge.DNS01TXTRecordName() + keyAuth := challenge.DNS01KeyAuthorization() + provider, err := s.getDNSProvider() + if err != nil { + return fmt.Errorf("获取 DNS 提供商失败: %w", err) + } + zone, err := publicsuffix.EffectiveTLDPlusOne(dnsName) + if err != nil { + return fmt.Errorf("获取域名 %q 的顶级域失败: %w", dnsName, err) + } + + rec := libdns.Record{ + Type: "TXT", + Name: libdns.RelativeName(dnsName+".", zone+"."), + Value: keyAuth, + } + + results, err := provider.AppendRecords(ctx, zone+".", []libdns.Record{rec}) + if err != nil { + return fmt.Errorf("域名 %q 添加临时记录 %q 失败: %w", zone, dnsName, err) + } + if len(results) != 1 { + return fmt.Errorf("预期添加 1 条记录,但实际添加了 %d 条记录", len(results)) + } + + s.records = &results + return nil +} + +func (s dnsSolver) CleanUp(ctx context.Context, challenge acme.Challenge) error { + dnsName := challenge.DNS01TXTRecordName() + provider, err := s.getDNSProvider() + if err != nil { + return fmt.Errorf("获取 DNS 提供商失败: %w", err) + } + zone, err := publicsuffix.EffectiveTLDPlusOne(dnsName) + if err != nil { + return fmt.Errorf("获取域名 %q 的顶级域失败: %w", dnsName, err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + + _, err = provider.DeleteRecords(ctx, zone+".", *s.records) + if err != nil { + return fmt.Errorf("域名 %q 删除临时记录 %q 失败: %w", zone, dnsName, err) + } + + return nil +} + +func (s dnsSolver) getDNSProvider() (DNSProvider, error) { + var dns DNSProvider + + switch s.dns { + case DnsPod: + dns = &dnspod.Provider{ + APIToken: s.param.ID + "," + s.param.Token, + } + case AliYun: + dns = &alidns.Provider{ + AccKeyID: s.param.AccessKey, + AccKeySecret: s.param.SecretKey, + } + case CloudFlare: + dns = &cloudflare.Provider{ + APIToken: s.param.APIkey, + } + default: + return nil, fmt.Errorf("未知的 DNS 提供商 %q", s.dns) + } + + return dns, nil +} + +type DnsType string + +const ( + DnsPod DnsType = "dnspod" + AliYun DnsType = "aliyun" + CloudFlare DnsType = "cloudflare" +) + +type DNSParam struct { + ID string `form:"id" json:"id"` + Token string `form:"token" json:"token"` + AccessKey string `form:"access_key" json:"access_key"` + SecretKey string `form:"secret_key" json:"secret_key"` + APIkey string `form:"api_key" json:"api_key"` +} + +type DNSProvider interface { + libdns.RecordAppender + libdns.RecordDeleter +} + +type manualDNSSolver struct { + check bool + controlChan chan struct{} + dataChan chan any + records *[]DNSRecord +} + +func (s manualDNSSolver) Present(ctx context.Context, challenge acme.Challenge) error { + dnsName := challenge.DNS01TXTRecordName() + keyAuth := challenge.DNS01KeyAuthorization() + + // 追加记录到 records 中 + *s.records = append(*s.records, DNSRecord{ + Key: dnsName, + Value: keyAuth, + }) + s.dataChan <- *s.records + + // 等待信号以继续 + <-s.controlChan + return nil +} + +func (s manualDNSSolver) CleanUp(ctx context.Context, challenge acme.Challenge) error { + return nil +} + +type DNSRecord struct { + Key string `json:"key"` + Value string `json:"value"` +}