feat: 证书增加申请日志 (#3002)

This commit is contained in:
zhengkunwang 2023-11-20 16:16:20 +08:00 committed by GitHub
parent cda900e04a
commit 52b16f0cb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 122 additions and 40 deletions

View File

@ -4,6 +4,7 @@ import "github.com/1Panel-dev/1Panel/backend/app/model"
type WebsiteSSLDTO struct {
model.WebsiteSSL
LogPath string `json:"logPath"`
}
type WebsiteDNSRes struct {

View File

@ -4,6 +4,7 @@ import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/app/model"
@ -11,11 +12,15 @@ import (
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/ssl"
"github.com/go-acme/lego/v4/certcrypto"
legoLogger "github.com/go-acme/lego/v4/log"
"github.com/jinzhu/gorm"
"log"
"os"
"path"
"strconv"
"strings"
@ -37,6 +42,7 @@ type IWebsiteSSLService interface {
Update(update request.WebsiteSSLUpdate) error
Upload(req request.WebsiteSSLUpload) error
ObtainSSL(apply request.WebsiteSSLApply) error
SyncForRestart() error
}
func NewIWebsiteSSLService() IWebsiteSSLService {
@ -51,9 +57,10 @@ func (w WebsiteSSLService) Page(search request.WebsiteSSLSearch) (int64, []respo
if err != nil {
return 0, nil, err
}
for _, sslModel := range sslList {
for _, model := range sslList {
result = append(result, response.WebsiteSSLDTO{
WebsiteSSL: sslModel,
WebsiteSSL: model,
LogPath: path.Join(constant.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", model.PrimaryDomain, model.ID)),
})
}
return total, result, err
@ -141,11 +148,19 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
}
func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
websiteSSL, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(apply.ID))
var (
err error
websiteSSL model.WebsiteSSL
acmeAccount *model.WebsiteAcmeAccount
dnsAccount *model.WebsiteDnsAccount
)
websiteSSL, err = websiteSSLRepo.GetFirst(commonRepo.WithByID(apply.ID))
if err != nil {
return err
}
acmeAccount, err := websiteAcmeRepo.GetFirst(commonRepo.WithByID(websiteSSL.AcmeAccountID))
acmeAccount, err = websiteAcmeRepo.GetFirst(commonRepo.WithByID(websiteSSL.AcmeAccountID))
if err != nil {
return err
}
@ -156,11 +171,11 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
switch websiteSSL.Provider {
case constant.DNSAccount:
dnsAccount, err := websiteDnsRepo.GetFirst(commonRepo.WithByID(websiteSSL.DnsAccountID))
dnsAccount, err = websiteDnsRepo.GetFirst(commonRepo.WithByID(websiteSSL.DnsAccountID))
if err != nil {
return err
}
if err := client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization); err != nil {
if err = client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization); err != nil {
return err
}
case constant.Http:
@ -181,7 +196,9 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
}
domains := []string{websiteSSL.PrimaryDomain}
domains = append(domains, domains...)
if websiteSSL.Domains != "" {
domains = append(domains, strings.Split(websiteSSL.Domains, ",")...)
}
privateKey, err := certcrypto.GeneratePrivateKey(ssl.KeyType(websiteSSL.KeyType))
if err != nil {
@ -195,6 +212,15 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
}
go func() {
logFile, _ := os.OpenFile(path.Join(constant.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", websiteSSL.PrimaryDomain, websiteSSL.ID)), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
defer logFile.Close()
logger := log.New(logFile, "", log.LstdFlags)
legoLogger.Logger = logger
startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)})
if websiteSSL.Provider == constant.DNSAccount {
startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type})
}
legoLogger.Logger.Println(startMsg)
resource, err := client.ObtainSSL(domains, privateKey)
if err != nil {
handleError(websiteSSL, err)
@ -214,6 +240,7 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
websiteSSL.Type = cert.Issuer.CommonName
websiteSSL.Organization = cert.Issuer.Organization[0]
websiteSSL.Status = constant.SSLReady
legoLogger.Logger.Println(i18n.GetMsgWithMap("ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")}))
err = websiteSSLRepo.Save(websiteSSL)
if err != nil {
return
@ -230,6 +257,7 @@ func handleError(websiteSSL model.WebsiteSSL, err error) {
websiteSSL.Status = constant.SSLApplyError
}
websiteSSL.Message = err.Error()
legoLogger.Logger.Println(i18n.GetErrMsg("ApplySSLFailed", map[string]interface{}{"domain": websiteSSL.PrimaryDomain, "err": err.Error()}))
_ = websiteSSLRepo.Save(websiteSSL)
}
@ -425,3 +453,18 @@ func (w WebsiteSSLService) Upload(req request.WebsiteSSLUpload) error {
return websiteSSLRepo.Create(context.Background(), newSSL)
}
func (w WebsiteSSLService) SyncForRestart() error {
sslList, err := websiteSSLRepo.List()
if err != nil {
return err
}
for _, ssl := range sslList {
if ssl.Status == constant.SSLApply {
ssl.Status = constant.SystemRestart
ssl.Message = "System restart causing interrupt"
_ = websiteSSLRepo.Save(ssl)
}
}
return nil
}

View File

@ -16,4 +16,5 @@ var (
RemoteAppResourceDir = path.Join(AppResourceDir, "remote")
RuntimeDir = path.Join(DataDir, "runtime")
RecycleBinDir = "/.1panel_clash"
SSLLogDir = path.Join(global.CONF.System.DataDir, "log", "ssl")
)

View File

@ -23,7 +23,7 @@ func (ssl *ssl) Run() {
global.LOG.Info("The scheduled certificate update task is currently in progress ...")
now := time.Now().Add(10 * time.Second)
for _, s := range sslList {
if !s.AutoRenew || s.Provider == "manual" || s.Provider == "dnsManual" {
if !s.AutoRenew || s.Provider == "manual" || s.Provider == "dnsManual" || s.Status == "applying" {
continue
}
expireDate := s.ExpireDate.In(nyc)

View File

@ -93,6 +93,13 @@ ErrSSLKeyFormat: 'Private key file verification error'
ErrSSLCertificateFormat: 'Certificate file format error, please use pem format'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid or EabHmacKey cannot be empty'
ErrOpenrestyNotFound: 'Http mode requires Openresty to be installed first'
ApplySSLStart: 'Start applying for certificate, domain name [{{ .domain }}] application method [{{ .type }}] '
dnsAccount: "DNS Automatic"
dnsManual: "DNS Manual"
http: "HTTP"
ApplySSLFailed: 'Application for [{{ .domain }}] certificate failed, {{.detail}} '
ApplySSLSuccess: 'Application for [{{ .domain }}] certificate successful! ! '
DNSAccountName: 'DNS account [{{ .name }}] manufacturer [{{.type}}]'
#mysql

View File

@ -93,6 +93,13 @@ ErrSSLKeyFormat: '私鑰文件校驗錯誤'
ErrSSLCertificateFormat: '證書文件格式錯誤,請使用 pem 格式'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不能為空'
ErrOpenrestyNotFound: 'Http 模式需要先安裝 Openresty'
ApplySSLStart: '開始申請憑證,網域 [{{ .domain }}] 申請方式 [{{ .type }}] '
dnsAccount: "DNS 自動"
dnsManual: "DNS 手排"
http: "HTTP"
ApplySSLFailed: '申請 [{{ .domain }}] 憑證失敗, {{.detail}} '
ApplySSLSuccess: '申請 [{{ .domain }}] 憑證成功! '
DNSAccountName: 'DNS 帳號 [{{ .name }}] 廠商 [{{.type}}]'
#mysql
ErrUserIsExist: "當前用戶已存在,請重新輸入"

View File

@ -93,6 +93,13 @@ ErrSSLKeyFormat: '私钥文件校验失败'
ErrSSLCertificateFormat: '证书文件格式错误,请使用 pem 格式'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不能为空'
ErrOpenrestyNotFound: 'Http 模式需要首先安装 Openresty'
ApplySSLStart: '开始申请证书,域名 [{{ .domain }}] 申请方式 [{{ .type }}] '
dnsAccount: "DNS 自动"
dnsManual: "DNS 手动"
http: "HTTP"
ApplySSLFailed: '申请 [{{ .domain }}] 证书失败, {{.detail}} '
ApplySSLSuccess: '申请 [{{ .domain }}] 证书成功!!'
DNSAccountName: 'DNS 账号 [{{ .name }}] 厂商 [{{.type}}]'
#mysql
ErrUserIsExist: "当前用户已存在,请重新输入"

View File

@ -20,8 +20,10 @@ func Init() {
constant.LocalAppInstallDir = path.Join(constant.AppInstallDir, "local")
constant.RemoteAppResourceDir = path.Join(constant.AppResourceDir, "remote")
constant.SSLLogDir = path.Join(global.CONF.System.DataDir, "log", "ssl")
dirs := []string{constant.DataDir, constant.ResourceDir, constant.AppResourceDir, constant.AppInstallDir,
global.CONF.System.Backup, constant.RuntimeDir, constant.LocalAppResourceDir, constant.RemoteAppResourceDir}
global.CONF.System.Backup, constant.RuntimeDir, constant.LocalAppResourceDir, constant.RemoteAppResourceDir, constant.SSLLogDir}
fileOp := files.NewFileOp()
for _, dir := range dirs {

View File

@ -10,6 +10,7 @@ func Init() {
go syncApp()
go syncInstalledApp()
go syncRuntime()
go syncSSL()
}
func syncApp() {
@ -31,3 +32,9 @@ func syncRuntime() {
global.LOG.Errorf("sync runtime status error : %s", err.Error())
}
}
func syncSSL() {
if err := service.NewIWebsiteSSLService().SyncForRestart(); err != nil {
global.LOG.Errorf("sync ssl status error : %s", err.Error())
}
}

View File

@ -165,7 +165,11 @@ export namespace Website {
autoRenew: boolean;
acmeAccountId?: number;
status: string;
domains?: string;
}
export interface SSLDTO extends SSL {
domains: string;
logPath: string;
}
export interface SSLCreate {

View File

@ -93,11 +93,11 @@ export const DeleteAcmeAccount = (req: Website.DelReq) => {
};
export const SearchSSL = (req: ReqPage) => {
return http.post<ResPage<Website.SSL>>(`/websites/ssl/search`, req);
return http.post<ResPage<Website.SSLDTO>>(`/websites/ssl/search`, req);
};
export const ListSSL = (req: Website.SSLReq) => {
return http.post<Website.SSL[]>(`/websites/ssl/search`, req);
return http.post<Website.SSLDTO[]>(`/websites/ssl/search`, req);
};
export const CreateSSL = (req: Website.SSLCreate) => {

View File

@ -1765,7 +1765,7 @@ const message = {
saveAndReload: 'Save and Reload',
},
ssl: {
create: 'Create Certificate',
create: 'Apply Certificate',
provider: 'Type',
manualCreate: 'manually created',
acmeAccount: 'Acme Account',

View File

@ -1662,7 +1662,7 @@ const message = {
saveAndReload: '保存並重載',
},
ssl: {
create: '創建證書',
create: '申請證書',
provider: '類型',
manualCreate: '手動創建',
acmeAccount: 'Acme 賬號',

View File

@ -1662,7 +1662,7 @@ const message = {
saveAndReload: '保存并重载',
},
ssl: {
create: '创建证书',
create: '申请证书',
provider: '类型',
manualCreate: '手动创建',
acmeAccount: 'Acme 账号',

View File

@ -87,7 +87,7 @@ import CreateRuntime from '@/views/website/runtime/php/create/index.vue';
import Status from '@/components/status/index.vue';
import i18n from '@/lang';
import RouterMenu from '../index.vue';
import Log from '@/views/website/runtime/php/log/index.vue';
import Log from '@/components/log/index.vue';
const paginationConfig = reactive({
cacheSizeKey: 'runtime-page-size',

View File

@ -81,6 +81,11 @@
</div>
</template>
</el-table-column>
<el-table-column :label="$t('website.log')" prop="">
<template #default="{ row }">
<el-button @click="openLog(row)" link type="primary">{{ $t('website.check') }}</el-button>
</template>
</el-table-column>
<el-table-column
:label="$t('website.brand')"
fix
@ -118,6 +123,7 @@
<SSLUpload ref="sslUploadRef" @close="search()"></SSLUpload>
<Apply ref="applyRef" @search="search" />
<OpDialog ref="opRef" @search="search" />
<Log ref="logRef" @close="search()" />
</LayoutContent>
</div>
</template>
@ -137,6 +143,7 @@ import { MsgSuccess } from '@/utils/message';
import { GlobalStore } from '@/store';
import SSLUpload from './upload/index.vue';
import Apply from './apply/index.vue';
import Log from '@/components/log/index.vue';
const globalStore = GlobalStore();
const paginationConfig = reactive({
@ -148,13 +155,13 @@ const paginationConfig = reactive({
const acmeAccountRef = ref();
const dnsAccountRef = ref();
const sslCreateRef = ref();
const renewRef = ref();
const detailRef = ref();
const data = ref();
const loading = ref(false);
const opRef = ref();
const sslUploadRef = ref();
const applyRef = ref();
const logRef = ref();
const routerButton = [
{
@ -166,19 +173,19 @@ const routerButton = [
const buttons = [
{
label: i18n.global.t('ssl.detail'),
disabled: function (row: Website.SSL) {
disabled: function (row: Website.SSLDTO) {
return row.status === 'init' || row.status === 'error';
},
click: function (row: Website.SSL) {
click: function (row: Website.SSLDTO) {
openDetail(row.id);
},
},
{
label: i18n.global.t('ssl.apply'),
disabled: function (row: Website.SSL) {
disabled: function (row: Website.SSLDTO) {
return row.status === 'applying';
},
click: function (row: Website.SSL) {
click: function (row: Website.SSLDTO) {
if (row.provider === 'dnsManual') {
applyRef.value.acceptParams({ ssl: row });
} else {
@ -188,7 +195,7 @@ const buttons = [
},
{
label: i18n.global.t('commons.button.delete'),
click: function (row: Website.SSL) {
click: function (row: Website.SSLDTO) {
deleteSSL(row);
},
},
@ -214,7 +221,7 @@ const search = () => {
});
};
const updateConfig = (row: Website.SSL) => {
const updateConfig = (row: Website.SSLDTO) => {
loading.value = true;
UpdateSSL({ id: row.id, autoRenew: row.autoRenew })
.then(() => {
@ -237,27 +244,23 @@ const openSSL = () => {
const openUpload = () => {
sslUploadRef.value.acceptParams();
};
const openRenewSSL = (id: number, websites: Website.Website[]) => {
renewRef.value.acceptParams({ id: id, websites: websites });
};
const openDetail = (id: number) => {
detailRef.value.acceptParams(id);
};
const openLog = (row: Website.SSLDTO) => {
logRef.value.acceptParams({ path: row.logPath });
};
const applySSL = (row: Website.SSL) => {
if (row.status === 'init' || row.status === 'error') {
loading.value = true;
ObtainSSL({ ID: row.id })
.then(() => {
MsgSuccess(i18n.global.t('ssl.applyStart'));
search();
})
.finally(() => {
loading.value = false;
});
} else {
openRenewSSL(row.id, row.websites);
}
const applySSL = (row: Website.SSLDTO) => {
loading.value = true;
ObtainSSL({ ID: row.id })
.then(() => {
MsgSuccess(i18n.global.t('ssl.applyStart'));
search();
})
.finally(() => {
loading.value = false;
});
};
const deleteSSL = async (row: any) => {