2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 13:47:15 +08:00
Files
panel/pkg/acme/client.go
2025-01-01 15:33:47 +08:00

144 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package acme
import (
"context"
"sort"
"github.com/libdns/libdns"
"github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v3/acme"
"github.com/tnb-labs/panel/pkg/cert"
)
type Certificate struct {
PrivateKey []byte
acme.Certificate
}
type Client struct {
Account acme.Account
zClient acmez.Client
// 手动 DNS 所需的信号通道
manualDNSSolver
}
// UseDns 使用 DNS 接口验证
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{},
},
}
}
// UseManualDns 使用手动 DNS 验证
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{},
},
}
}
// UseHTTP 使用 HTTP 验证
// conf nginx 配置文件路径
// path 验证文件存放路径
func (c *Client) UseHTTP(conf string) {
c.zClient.ChallengeSolvers = map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: httpSolver{
conf: conf,
},
}
}
// ObtainCertificate 签发 SSL 证书
func (c *Client) ObtainCertificate(ctx context.Context, domains []string, keyType KeyType) (Certificate, error) {
certPrivateKey, err := generatePrivateKey(keyType)
if err != nil {
return Certificate{}, err
}
pemPrivateKey, err := cert.EncodeKey(certPrivateKey)
if err != nil {
return Certificate{}, err
}
certs, err := c.zClient.ObtainCertificateForSANs(ctx, c.Account, certPrivateKey, domains)
if err != nil {
return Certificate{}, err
}
crt := c.selectPreferredChain(certs)
return Certificate{PrivateKey: pemPrivateKey, Certificate: crt}, nil
}
// ObtainCertificateManual 手动验证 SSL 证书
func (c *Client) ObtainCertificateManual() (Certificate, error) {
// 发送信号,开始验证
c.controlChan <- struct{}{}
// 等待验证完成
data := <-c.dataChan
if err, ok := data.(error); ok {
return Certificate{}, err
}
return data.(Certificate), nil
}
// RenewCertificate 续签 SSL 证书
func (c *Client) RenewCertificate(ctx context.Context, certUrl string, domains []string, keyType KeyType) (Certificate, error) {
_, err := c.zClient.GetCertificateChain(ctx, c.Account, certUrl)
if err != nil {
return Certificate{}, err
}
return c.ObtainCertificate(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.ObtainCertificate(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)
})
return certChains[0]
}