mirror of
https://github.com/acepanel/panel.git
synced 2026-02-05 02:07:18 +08:00
193 lines
4.5 KiB
Go
193 lines
4.5 KiB
Go
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/v2/acme"
|
|
"golang.org/x/net/publicsuffix"
|
|
)
|
|
|
|
type httpSolver struct {
|
|
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())
|
|
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(ctx, 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
|
|
|
|
_, 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"`
|
|
}
|