2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 17:17:13 +08:00
Files
panel/pkg/acme/solvers.go
2024-10-12 19:13:33 +08:00

204 lines
4.9 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"
"fmt"
"os"
"path/filepath"
"time"
"github.com/libdns/alidns"
"github.com/libdns/cloudflare"
"github.com/libdns/dnspod"
"github.com/libdns/libdns"
"github.com/libdns/tencentcloud"
"github.com/mholt/acmez/v2/acme"
"golang.org/x/net/publicsuffix"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
)
type httpSolver struct {
conf string
path string
}
func (s httpSolver) Present(_ context.Context, challenge acme.Challenge) error {
var err error
if s.path == "" {
return nil
}
challengeFilePath := filepath.Join(s.path, challenge.HTTP01ResourcePath())
if err = os.MkdirAll(filepath.Dir(challengeFilePath), 0755); err != nil {
return fmt.Errorf("无法在网站目录创建HTTP挑战所需的目录: %w", err)
}
if err = os.WriteFile(challengeFilePath, []byte(challenge.KeyAuthorization), 0644); err != nil {
return fmt.Errorf("无法在网站目录创建HTTP挑战所需的文件: %w", err)
}
conf := fmt.Sprintf(`location = /.well-known/acme-challenge/%s {
default_type text/plain;
return 200 %q;
}
`, challenge.Token, challenge.KeyAuthorization)
if err = os.WriteFile(s.conf, []byte(conf), 0644); err != nil {
return fmt.Errorf("无法写入OpenResty配置文件: %w", err)
}
if err = systemctl.Reload("nginx"); err != nil {
_, err = shell.Execf("nginx -t")
return fmt.Errorf("无法重载OpenResty: %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
}
_ = os.Remove(filepath.Join(s.path, challenge.HTTP01ResourcePath()))
_ = os.WriteFile(s.conf, []byte{}, 0644)
_ = systemctl.Reload("nginx")
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, _ := publicsuffix.EffectiveTLDPlusOne(dnsName)
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
_, _ = provider.DeleteRecords(ctx, zone+".", *s.records)
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 Tencent:
dns = &tencentcloud.Provider{
SecretId: s.param.AccessKey,
SecretKey: s.param.SecretKey,
}
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"
Tencent DnsType = "tencent"
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()
*s.records = append(*s.records, DNSRecord{
Key: dnsName,
Value: keyAuth,
})
s.dataChan <- *s.records
_, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
<-s.controlChan
return nil
}
func (s manualDNSSolver) CleanUp(_ context.Context, _ acme.Challenge) error {
return nil
}
type DNSRecord struct {
Key string `json:"key"`
Value string `json:"value"`
}