2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 09:13:49 +08:00

feat(HTTPS): 优化证书申请

This commit is contained in:
耗子
2024-10-19 03:03:38 +08:00
parent 0b1554c2c6
commit 389b0a005b
9 changed files with 96 additions and 146 deletions

View File

@@ -93,7 +93,7 @@ func (r *certRepo) ObtainAuto(id uint) (*acme.Certificate, error) {
}
}
conf := fmt.Sprintf("%s/server/vhost/acme/%s.conf", app.Root, cert.Website.Name)
client.UseHTTP(conf, cert.Website.Path)
client.UseHTTP(conf)
}
}
@@ -172,7 +172,7 @@ func (r *certRepo) Renew(id uint) (*acme.Certificate, error) {
}
}
conf := fmt.Sprintf("%s/server/vhost/acme/%s.conf", app.Root, cert.Website.Name)
client.UseHTTP(conf, cert.Website.Path)
client.UseHTTP(conf)
}
}
@@ -251,6 +251,10 @@ func (r *certRepo) Deploy(ID, WebsiteID uint) error {
}
func (r *certRepo) getClient(cert *biz.Cert) (*acme.Client, error) {
if cert.Account == nil {
return nil, errors.New("该证书没有关联账号,无法签发")
}
var ca string
var eab *acme.EAB
switch cert.Account.CA {

View File

@@ -54,10 +54,6 @@ func (s *CertService) CAProviders(w http.ResponseWriter, r *http.Request) {
func (s *CertService) DNSProviders(w http.ResponseWriter, r *http.Request) {
Success(w, []types.LV{
{
Label: "DNSPod",
Value: string(acme.DnsPod),
},
{
Label: "腾讯云",
Value: string(acme.Tencent),

View File

@@ -51,11 +51,10 @@ func (c *Client) UseManualDns(total int, check ...bool) {
// UseHTTP 使用 HTTP 验证
// conf nginx 配置文件路径
// path 验证文件存放路径
func (c *Client) UseHTTP(conf, path string) {
func (c *Client) UseHTTP(conf string) {
c.zClient.ChallengeSolvers = map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: httpSolver{
conf: conf,
path: path,
},
}
}

View File

@@ -20,7 +20,7 @@ func (s *ClientTestSuite) TestObtainSSL() {
client, err := NewRegisterAccount(ctx, "ci@haozi.net", CALetsEncryptStaging, nil, KeyEC256)
s.Nil(err)
client.UseDns(DnsPod, DNSParam{
client.UseDns(AliYun, DNSParam{
ID: "123456",
Token: "654321",
})

View File

@@ -4,12 +4,11 @@ import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"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"
@@ -21,35 +20,20 @@ import (
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 {
conf := fmt.Sprintf(`location = %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("无法写入Nginx配置文件: %w", err)
`, challenge.HTTP01ResourcePath(), challenge.KeyAuthorization)
if err := os.WriteFile(s.conf, []byte(conf), 0644); err != nil {
return fmt.Errorf("无法写入 Nginx 配置文件: %w", err)
}
if err = systemctl.Reload("nginx"); err != nil {
if err := systemctl.Reload("nginx"); err != nil {
_, err = shell.Execf("nginx -t")
return fmt.Errorf("无法重载Nginx: %w", err)
return fmt.Errorf("无法重载 Nginx: %w", err)
}
return nil
@@ -57,11 +41,6 @@ func (s httpSolver) Present(_ context.Context, challenge acme.Challenge) error {
// 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
@@ -78,11 +57,11 @@ func (s dnsSolver) Present(ctx context.Context, challenge acme.Challenge) error
keyAuth := challenge.DNS01KeyAuthorization()
provider, err := s.getDNSProvider()
if err != nil {
return fmt.Errorf("获取DNS提供商失败: %w", err)
return fmt.Errorf("获取 DNS 提供商失败: %w", err)
}
zone, err := publicsuffix.EffectiveTLDPlusOne(dnsName)
if err != nil {
return fmt.Errorf("获取域名%q的顶级域失败: %w", dnsName, err)
return fmt.Errorf("获取域名 %q 的顶级域失败: %w", dnsName, err)
}
rec := libdns.Record{
@@ -91,12 +70,12 @@ func (s dnsSolver) Present(ctx context.Context, challenge acme.Challenge) error
Value: keyAuth,
}
results, err := provider.AppendRecords(ctx, zone+".", []libdns.Record{rec})
results, err := provider.SetRecords(ctx, zone+".", []libdns.Record{rec})
if err != nil {
return fmt.Errorf("域名%q添加临时记录%q失败: %w", zone, dnsName, err)
return fmt.Errorf("域名 %q 添加临时记录 %q 失败: %w", zone, dnsName, err)
}
if len(results) != 1 {
return fmt.Errorf("预期添加1条记录,但实际添加了%d条记录", len(results))
return fmt.Errorf("预期添加 1 条记录,但实际添加了 %d 条记录", len(results))
}
s.records = &results
@@ -107,13 +86,16 @@ 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)
return fmt.Errorf("获取 DNS 提供商失败: %w", err)
}
zone, _ := publicsuffix.EffectiveTLDPlusOne(dnsName)
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
zone, err := publicsuffix.EffectiveTLDPlusOne(dnsName)
if err != nil {
return fmt.Errorf("获取域名 %q 的顶级域失败: %w", dnsName, err)
}
_, _ = provider.DeleteRecords(ctx, zone+".", *s.records)
return nil
}
@@ -122,23 +104,19 @@ 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,
SecretId: s.param.AK,
SecretKey: s.param.SK,
}
case AliYun:
dns = &alidns.Provider{
AccKeyID: s.param.AccessKey,
AccKeySecret: s.param.SecretKey,
AccKeyID: s.param.AK,
AccKeySecret: s.param.SK,
}
case CloudFlare:
dns = &cloudflare.Provider{
APIToken: s.param.APIkey,
APIToken: s.param.AK,
}
default:
return nil, fmt.Errorf("未知的DNS提供商 %q", s.dns)
@@ -150,22 +128,18 @@ func (s dnsSolver) getDNSProvider() (DNSProvider, error) {
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"`
AK string `form:"ak" json:"ak"`
SK string `form:"sk" json:"sk"`
}
type DNSProvider interface {
libdns.RecordAppender
libdns.RecordSetter
libdns.RecordDeleter
}
@@ -177,18 +151,20 @@ type manualDNSSolver struct {
}
func (s manualDNSSolver) Present(ctx context.Context, challenge acme.Challenge) error {
dnsName := challenge.DNS01TXTRecordName()
full := challenge.DNS01TXTRecordName()
keyAuth := challenge.DNS01KeyAuthorization()
domain, err := publicsuffix.EffectiveTLDPlusOne(full)
if err != nil {
return fmt.Errorf("获取 %q 的顶级域失败: %w", full, err)
}
*s.records = append(*s.records, DNSRecord{
Key: dnsName,
Value: keyAuth,
Name: strings.TrimSuffix(full, "."+domain),
Domain: domain,
Value: keyAuth,
})
s.dataChan <- *s.records
_, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
<-s.controlChan
return nil
}
@@ -198,6 +174,7 @@ func (s manualDNSSolver) CleanUp(_ context.Context, _ acme.Challenge) error {
}
type DNSRecord struct {
Key string `json:"key"`
Value string `json:"value"`
Name string `json:"name"`
Domain string `json:"domain"`
Value string `json:"value"`
}

View File

@@ -1,7 +1,7 @@
{
"name": "耗子面板",
"certIndex": {
"title": "TLS 证书"
"title": "HTTPS 证书"
},
"containerIndex": {
"title": "容器管理"

View File

@@ -192,16 +192,26 @@ const certColumns: any = [
window.$message.info('请先前往域名处设置 DNS 解析,再继续签发')
const d = window.$dialog.info({
style: 'width: 60vw',
title: 'DNS 记录列表',
title: '待设置DNS 记录列表',
content: () => {
return h(NTable, [
h('thead', [
h('tr', [h('th', '域名'), h('th', '类型'), h('th', '记录值')])
h('tr', [
h('th', '域名'),
h('th', '类型'),
h('th', '主机记录'),
h('th', '记录值')
])
]),
h(
'tbody',
data.map((item: any) =>
h('tr', [h('td', item?.key), h('td', 'TXT'), h('td', item?.value)])
h('tr', [
h('td', item?.domain),
h('td', 'TXT'),
h('td', item?.name),
h('td', item?.value)
])
)
)
])
@@ -215,7 +225,7 @@ const certColumns: any = [
cert
.obtain(row.id)
.then(() => {
window.$message.success('签发成功请前往网站管理启用HTTPS')
window.$message.success('签发成功')
onCertPageChange(1)
})
.finally(() => {
@@ -228,7 +238,7 @@ const certColumns: any = [
cert
.obtain(row.id)
.then(() => {
window.$message.success('签发成功请前往网站管理启用HTTPS')
window.$message.success('签发成功')
onCertPageChange(1)
})
.finally(() => {
@@ -417,7 +427,7 @@ const handleUpdateCert = async () => {
const handleDeployCert = async () => {
await cert.deploy(deployCertModel.value.id, deployCertModel.value.website_id)
window.$message.success('部署成功请前往网站管理启用HTTPS')
window.$message.success('部署成功')
deployCertModal.value = false
deployCertModel.value.id = 0
deployCertModel.value.website_id = 0

View File

@@ -6,24 +6,18 @@ import type { DNS } from '@/views/cert/types'
const addDNSModel = ref<any>({
data: {
token: '',
id: '',
access_key: '',
api_key: '',
secret_key: ''
ak: '',
sk: ''
},
type: 'dnspod',
type: 'tencent',
name: ''
})
const updateDNSModel = ref<any>({
data: {
token: '',
id: '',
access_key: '',
api_key: '',
secret_key: ''
ak: '',
sk: ''
},
type: 'dnspod',
type: 'tencent',
name: ''
})
const addDNSModal = ref(false)
@@ -56,8 +50,6 @@ const dnsColumns: any = [
{
default: () => {
switch (row.type) {
case 'dnspod':
return 'DnsPod'
case 'tencent':
return '腾讯云'
case 'aliyun':
@@ -87,11 +79,8 @@ const dnsColumns: any = [
type: 'primary',
onClick: () => {
updateDNS.value = row.id
updateDNSModel.value.data.token = row.dns_param.token
updateDNSModel.value.data.id = row.dns_param.id
updateDNSModel.value.data.access_key = row.dns_param.access_key
updateDNSModel.value.data.api_key = row.dns_param.api_key
updateDNSModel.value.data.secret_key = row.dns_param.secret_key
updateDNSModel.value.data.ak = row.dns_param.ak
updateDNSModel.value.data.sk = row.dns_param.sk
updateDNSModel.value.type = row.type
updateDNSModel.value.name = row.name
updateDNSModal.value = true
@@ -169,11 +158,8 @@ const handleAddDNS = async () => {
window.$message.success('添加成功')
addDNSModal.value = false
onDnsPageChange(1)
addDNSModel.value.data.token = ''
addDNSModel.value.data.id = ''
addDNSModel.value.data.access_key = ''
addDNSModel.value.data.api_key = ''
addDNSModel.value.data.secret_key = ''
addDNSModel.value.data.ak = ''
addDNSModel.value.data.sk = ''
addDNSModel.value.name = ''
}
@@ -182,11 +168,8 @@ const handleUpdateDNS = async () => {
window.$message.success('更新成功')
updateDNSModal.value = false
onDnsPageChange(1)
updateDNSModel.value.data.token = ''
updateDNSModel.value.data.id = ''
updateDNSModel.value.data.access_key = ''
updateDNSModel.value.data.api_key = ''
updateDNSModel.value.data.secret_key = ''
updateDNSModel.value.data.ak = ''
updateDNSModel.value.data.sk = ''
updateDNSModel.value.name = ''
}
@@ -245,57 +228,41 @@ onMounted(async () => {
:options="dnsProviders"
/>
</n-form-item>
<n-form-item v-if="addDNSModel.type == 'dnspod'" path="id" label="ID">
<n-form-item v-if="addDNSModel.type == 'tencent'" path="ak" label="SecretId">
<n-input
v-model:value="addDNSModel.data.id"
type="text"
@keydown.enter.prevent
placeholder="输入 DnsPod ID"
/>
</n-form-item>
<n-form-item v-if="addDNSModel.type == 'dnspod'" path="token" label="Token">
<n-input
v-model:value="addDNSModel.data.token"
type="text"
@keydown.enter.prevent
placeholder="输入 DnsPod Token"
/>
</n-form-item>
<n-form-item v-if="addDNSModel.type == 'tencent'" path="access_key" label="SecretId">
<n-input
v-model:value="addDNSModel.data.access_key"
v-model:value="addDNSModel.data.ak"
type="text"
@keydown.enter.prevent
placeholder="输入腾讯云 SecretId"
/>
</n-form-item>
<n-form-item v-if="addDNSModel.type == 'tencent'" path="secret_key" label="SecretKey">
<n-form-item v-if="addDNSModel.type == 'tencent'" path="sk" label="SecretKey">
<n-input
v-model:value="addDNSModel.data.secret_key"
v-model:value="addDNSModel.data.sk"
type="text"
@keydown.enter.prevent
placeholder="输入腾讯云 SecretKey"
/>
</n-form-item>
<n-form-item v-if="addDNSModel.type == 'aliyun'" path="access_key" label="Access Key">
<n-form-item v-if="addDNSModel.type == 'aliyun'" path="ak" label="Access Key">
<n-input
v-model:value="addDNSModel.data.access_key"
v-model:value="addDNSModel.data.ak"
type="text"
@keydown.enter.prevent
placeholder="输入阿里云 Access Key"
/>
</n-form-item>
<n-form-item v-if="addDNSModel.type == 'aliyun'" path="secret_key" label="Secret Key">
<n-form-item v-if="addDNSModel.type == 'aliyun'" path="sk" label="Secret Key">
<n-input
v-model:value="addDNSModel.data.secret_key"
v-model:value="addDNSModel.data.sk"
type="text"
@keydown.enter.prevent
placeholder="输入阿里云 Secret Key"
/>
</n-form-item>
<n-form-item v-if="addDNSModel.type == 'cloudflare'" path="api_key" label="API Key">
<n-form-item v-if="addDNSModel.type == 'cloudflare'" path="ak" label="API Key">
<n-input
v-model:value="addDNSModel.data.api_key"
v-model:value="addDNSModel.data.ak"
type="text"
@keydown.enter.prevent
placeholder="输入 Cloudflare API Key"
@@ -332,33 +299,33 @@ onMounted(async () => {
:options="dnsProviders"
/>
</n-form-item>
<n-form-item v-if="updateDNSModel.type == 'dnspod'" path="id" label="ID">
<n-form-item v-if="updateDNSModel.type == 'tencent'" path="ak" label="SecretId">
<n-input
v-model:value="updateDNSModel.data.id"
v-model:value="updateDNSModel.data.ak"
type="text"
@keydown.enter.prevent
placeholder="输入 DnsPod ID"
placeholder="输入腾讯云 SecretId"
/>
</n-form-item>
<n-form-item v-if="updateDNSModel.type == 'dnspod'" path="token" label="Token">
<n-form-item v-if="updateDNSModel.type == 'tencent'" path="sk" label="SecretKey">
<n-input
v-model:value="updateDNSModel.data.token"
v-model:value="updateDNSModel.data.sk"
type="text"
@keydown.enter.prevent
placeholder="输入 DnsPod Token"
placeholder="输入腾讯云 SecretKey"
/>
</n-form-item>
<n-form-item v-if="updateDNSModel.type == 'aliyun'" path="access_key" label="Access Key">
<n-form-item v-if="updateDNSModel.type == 'aliyun'" path="ak" label="Access Key">
<n-input
v-model:value="updateDNSModel.data.access_key"
v-model:value="updateDNSModel.data.ak"
type="text"
@keydown.enter.prevent
placeholder="输入阿里云 Access Key"
/>
</n-form-item>
<n-form-item v-if="updateDNSModel.type == 'aliyun'" path="secret_key" label="Secret Key">
<n-form-item v-if="updateDNSModel.type == 'aliyun'" path="sk" label="Secret Key">
<n-input
v-model:value="updateDNSModel.data.secret_key"
v-model:value="updateDNSModel.data.sk"
type="text"
@keydown.enter.prevent
placeholder="输入阿里云 Secret Key"
@@ -366,7 +333,7 @@ onMounted(async () => {
</n-form-item>
<n-form-item v-if="updateDNSModel.type == 'cloudflare'" path="api_key" label="API Key">
<n-input
v-model:value="updateDNSModel.data.api_key"
v-model:value="updateDNSModel.data.ak"
type="text"
@keydown.enter.prevent
placeholder="输入 Cloudflare API Key"

View File

@@ -33,11 +33,8 @@ export interface DNS {
type: string
name: string
data: {
id: string
token: string
access_key: string
secret_key: string
api_key: string
ak: string
sk: string
}
created_at: string
updated_at: string