feat: 证书手动导入增加选择本地文件 (#1543)

This commit is contained in:
zhengkunwang223 2023-07-05 16:24:19 +08:00 committed by GitHub
parent 35334c1650
commit 9fa9772c07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 163 additions and 49 deletions

View File

@ -115,15 +115,18 @@ type WebsiteDomainDelete struct {
}
type WebsiteHTTPSOp struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Enable bool `json:"enable" validate:"required"`
WebsiteSSLID uint `json:"websiteSSLId"`
Type string `json:"type" validate:"oneof=existed auto manual"`
PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"`
HttpConfig string `json:"HttpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
WebsiteID uint `json:"websiteId" validate:"required"`
Enable bool `json:"enable" validate:"required"`
WebsiteSSLID uint `json:"websiteSSLId"`
Type string `json:"type" validate:"oneof=existed auto manual"`
PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"`
PrivateKeyPath string `json:"privateKeyPath"`
CertificatePath string `json:"certificatePath"`
ImportType string `json:"importType"`
HttpConfig string `json:"httpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
}
type WebsiteNginxUpdate struct {

View File

@ -58,7 +58,7 @@ type IWebsiteService interface {
UpdateNginxConfigByScope(req request.NginxConfigUpdate) error
GetWebsiteNginxConfig(websiteId uint, configType string) (response.FileInfo, error)
GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error)
OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (response.WebsiteHTTPS, error)
OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error)
PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error)
GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error)
UpdateWafConfig(req request.WebsiteWafUpdate) error
@ -619,10 +619,10 @@ func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS,
return res, nil
}
func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (response.WebsiteHTTPS, error) {
func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error) {
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
if err != nil {
return response.WebsiteHTTPS{}, err
return nil, err
}
var (
res response.WebsiteHTTPS
@ -635,7 +635,7 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
website.Protocol = constant.ProtocolHTTP
website.WebsiteSSLID = 0
if err := deleteListenAndServerName(website, []string{"443", "[::]:443"}, []string{}); err != nil {
return response.WebsiteHTTPS{}, err
return nil, err
}
nginxParams := getNginxParamsFromStaticFile(dto.SSL, nil)
nginxParams = append(nginxParams,
@ -656,28 +656,64 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
Name: "ssl_ciphers",
},
)
if err := deleteNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil {
return response.WebsiteHTTPS{}, err
if err = deleteNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil {
return nil, err
}
if err := websiteRepo.Save(ctx, &website); err != nil {
return response.WebsiteHTTPS{}, err
if err = websiteRepo.Save(ctx, &website); err != nil {
return nil, err
}
return res, nil
return nil, nil
}
if req.Type == constant.SSLExisted {
websiteSSL, err = websiteSSLRepo.GetFirst(commonRepo.WithByID(req.WebsiteSSLID))
if err != nil {
return response.WebsiteHTTPS{}, err
return nil, err
}
website.WebsiteSSLID = websiteSSL.ID
res.SSL = websiteSSL
}
if req.Type == constant.SSLManual {
certBlock, _ := pem.Decode([]byte(req.Certificate))
var (
certificate string
privateKey string
)
switch req.ImportType {
case "paste":
certificate = req.Certificate
privateKey = req.PrivateKey
case "local":
fileOp := files.NewFileOp()
if !fileOp.Stat(req.PrivateKeyPath) {
return nil, buserr.New("ErrSSLKeyNotFound")
}
if !fileOp.Stat(req.CertificatePath) {
return nil, buserr.New("ErrSSLCertificateNotFound")
}
if content, err := fileOp.GetContent(req.PrivateKeyPath); err != nil {
return nil, err
} else {
privateKey = string(content)
}
if content, err := fileOp.GetContent(req.CertificatePath); err != nil {
return nil, err
} else {
certificate = string(content)
}
}
privateKeyCertBlock, _ := pem.Decode([]byte(privateKey))
if privateKeyCertBlock == nil {
return nil, buserr.New("ErrSSLCertificateFormat")
}
certBlock, _ := pem.Decode([]byte(certificate))
if certBlock == nil {
return nil, buserr.New("ErrSSLCertificateFormat")
}
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return response.WebsiteHTTPS{}, err
return nil, err
}
websiteSSL.ExpireDate = cert.NotAfter
websiteSSL.StartDate = cert.NotBefore
@ -691,28 +727,28 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
websiteSSL.PrimaryDomain = cert.DNSNames[0]
websiteSSL.Domains = strings.Join(cert.DNSNames, ",")
}
websiteSSL.Provider = constant.Manual
websiteSSL.PrivateKey = req.PrivateKey
websiteSSL.Pem = req.Certificate
websiteSSL.PrivateKey = privateKey
websiteSSL.Pem = certificate
res.SSL = websiteSSL
}
website.Protocol = constant.ProtocolHTTPS
if err := applySSL(website, websiteSSL, req); err != nil {
return response.WebsiteHTTPS{}, err
return nil, err
}
website.HttpConfig = req.HttpConfig
if websiteSSL.ID == 0 {
if err := websiteSSLRepo.Create(ctx, &websiteSSL); err != nil {
return response.WebsiteHTTPS{}, err
return nil, err
}
website.WebsiteSSLID = websiteSSL.ID
}
if err := websiteRepo.Save(ctx, &website); err != nil {
return response.WebsiteHTTPS{}, err
return nil, err
}
return res, nil
return &res, nil
}
func (w WebsiteService) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error) {

View File

@ -63,6 +63,10 @@ ErrSSLCannotDelete: "The certificate is being used by the website and cannot be
ErrAccountCannotDelete: "The certificate associated with the account cannot be deleted"
ErrSSLApply: "The certificate continues to be signed successfully, but openresty reload fails, please check the configuration"
ErrEmailIsExist: 'Email is already exist'
ErrSSLKeyNotFound: 'The private key file does not exist'
ErrSSLCertificateNotFound: 'The certificate file does not exist'
ErrSSLKeyFormat: 'Private key file format error, please use pem format'
ErrSSLCertificateFormat: 'Certificate file format error, please use pem format'
#mysql
ErrUserIsExist: "The current user already exists. Please enter a new user"

View File

@ -63,6 +63,10 @@ ErrSSLCannotDelete: "證書正在被網站使用,無法刪除"
ErrAccountCannotDelete: "帳號關聯證書,無法刪除"
ErrSSLApply: "證書續簽成功openresty reload失敗請檢查配置"
ErrEmailIsExist: '郵箱已存在'
ErrSSLKeyNotFound: '私鑰文件不存在'
ErrSSLCertificateNotFound: '證書文件不存在'
ErrSSLKeyFormat: '私鑰文件格式錯誤,請使用 pem 格式'
ErrSSLCertificateFormat: '證書文件格式錯誤,請使用 pem 格式'
#mysql
ErrUserIsExist: "當前用戶已存在,請重新輸入"

View File

@ -63,6 +63,10 @@ ErrSSLCannotDelete: "证书正在被网站使用,无法删除"
ErrAccountCannotDelete: "账号关联证书,无法删除"
ErrSSLApply: "证书续签成功openresty reload失败请检查配置"
ErrEmailIsExist: '邮箱已存在'
ErrSSLKeyNotFound: '私钥文件不存在'
ErrSSLCertificateNotFound: '证书文件不存在'
ErrSSLKeyFormat: '私钥文件格式错误,请使用 pem 格式'
ErrSSLCertificateFormat: '证书文件格式错误,请使用 pem 格式'
#mysql
ErrUserIsExist: "当前用户已存在,请重新输入"

View File

@ -41,7 +41,6 @@ export namespace App {
readme: string;
params: AppParams;
dockerCompose: string;
enbale: boolean;
image: string;
}

View File

@ -55,7 +55,12 @@
<el-table :data="data" highlight-current-row height="40vh">
<el-table-column width="40" fix>
<template #default="{ row }">
<el-checkbox v-model="rowName" :true-label="row.name" @click="checkFile(row)" />
<el-checkbox
v-model="rowName"
:true-label="row.name"
:disabled="disabledDir(row)"
@change="checkFile(row)"
/>
</template>
</el-table-column>
<el-table-column show-overflow-tooltip fix>
@ -100,7 +105,6 @@ const loading = ref(false);
const paths = ref<string[]>([]);
const req = reactive({ path: '/', expand: true, page: 1, pageSize: 300 });
const selectRow = ref();
const popoverVisible = ref(false);
const props = defineProps({
@ -133,6 +137,13 @@ const closePage = () => {
selectRow.value = {};
};
const disabledDir = (row: File.File) => {
if (!props.dir) {
return row.isDir;
}
return false;
};
const open = async (row: File.File) => {
if (row.isDir) {
const name = row.name;

View File

@ -1325,8 +1325,8 @@ const message = {
HTTPToHTTPS: 'Access HTTP automatically jumps to HTTPS',
HTTPAlso: 'HTTP can be accessed directly',
sslConfig: 'SSL options',
disbaleHTTPS: 'Disable HTTPS',
disbaleHTTPSHelper:
disableHTTPS: 'Disable HTTPS',
disableHTTPSHelper:
'Disabling HTTPS will delete the certificate related configuration, Do you want to continue?',
SSLHelper:
'Note: Do not use SSL certificates for illegal websites \n If HTTPS access cannot be used after opening, please check whether the security group has correctly released port 443',
@ -1417,7 +1417,12 @@ const message = {
ipv6: 'Listen IPV6',
leechReturnError: 'Please fill in the HTTP status code',
selectAcme: 'Select Acme account',
localSSL: 'Imported',
imported: 'Imported',
importType: 'Import Type',
pasteSSL: 'Paste code',
localSSL: 'Select local file',
privateKeyPath: 'Private key file',
certificatePath: 'Certificate file',
},
php: {
short_open_tag: 'Short tag support',

View File

@ -1254,15 +1254,15 @@ const message = {
manualSSL: '手動導入證書',
select: '選擇',
selectSSL: '選擇證書',
privateKey: '(KEY)',
privateKey: '(KEY)',
certificate: '證書(PEM格式)',
HTTPConfig: 'HTTP 選項',
HTTPSOnly: '禁止 HTTP',
HTTPToHTTPS: '訪問HTTP自動跳轉到HTTPS',
HTTPAlso: 'HTTP可直接訪問',
sslConfig: 'SSL 選項',
disbaleHTTPS: '禁用 HTTPS',
disbaleHTTPSHelper: '禁用 HTTPS會刪除證書相關配置是否繼續',
disableHTTPS: '禁用 HTTPS',
disableHTTPSHelper: '禁用 HTTPS會刪除證書相關配置是否繼續',
SSLHelper: '註意請勿將SSL證書用於非法網站 \n 如開啟後無法使用HTTPS訪問請檢查安全組是否正確放行443端口',
SSLConfig: '證書設置',
SSLProConfig: 'SSL 協議設置',
@ -1348,7 +1348,12 @@ const message = {
ipv6: '監聽 IPV6 端口',
leechReturnError: '請填寫 HTTP 狀態碼',
selectAcme: '選擇 Acme 賬號',
localSSL: '已導入',
imported: '已導入',
importType: '導入方式',
pasteSSL: '粘貼代碼',
localSSL: '選擇本地文件',
privateKeyPath: '私鑰文件',
certificatePath: '證書文件',
},
php: {
short_open_tag: '短標簽支持',

View File

@ -1260,15 +1260,15 @@ const message = {
manualSSL: '手动导入证书',
select: '选择',
selectSSL: '选择证书',
privateKey: '(KEY)',
privateKey: '(KEY)',
certificate: '证书(PEM格式)',
HTTPConfig: 'HTTP 选项',
HTTPSOnly: '禁止 HTTP',
HTTPToHTTPS: '访问HTTP自动跳转到HTTPS',
HTTPAlso: 'HTTP可直接访问',
sslConfig: 'SSL 选项',
disbaleHTTPS: '禁用 HTTPS',
disbaleHTTPSHelper: '禁用 HTTPS会删除证书相关配置是否继续',
disableHTTPS: '禁用 HTTPS',
disableHTTPSHelper: '禁用 HTTPS会删除证书相关配置是否继续',
SSLHelper: '注意请勿将SSL证书用于非法网站 \n 如开启后无法使用HTTPS访问请检查安全组是否正确放行443端口',
SSLConfig: '证书设置',
SSLProConfig: 'SSL 协议设置',
@ -1354,7 +1354,12 @@ const message = {
ipv6: '监听 IPV6 端口',
leechReturnError: '请填写 HTTP 状态码',
selectAcme: '选择 acme 账号',
localSSL: '已导入',
imported: '已导入',
importType: '导入方式',
pasteSSL: '粘贴代码',
localSSL: '选择本地文件',
privateKeyPath: '私钥文件',
certificatePath: '证书文件',
},
php: {
short_open_tag: '短标签支持',

View File

@ -34,7 +34,7 @@
:placeholder="$t('website.selectAcme')"
@change="listSSL"
>
<el-option :key="0" :label="$t('website.localSSL')" :value="0"></el-option>
<el-option :key="0" :label="$t('website.imported')" :value="0"></el-option>
<el-option
v-for="(acme, index) in acmeAccounts"
:key="index"
@ -59,12 +59,36 @@
</el-form-item>
</div>
<div v-if="form.type === 'manual'">
<el-form-item :label="$t('website.privateKey')" prop="privateKey">
<el-input v-model="form.privateKey" :rows="6" type="textarea" />
</el-form-item>
<el-form-item :label="$t('website.certificate')" prop="certificate">
<el-input v-model="form.certificate" :rows="6" type="textarea" />
<el-form-item :label="$t('website.importType')" prop="type">
<el-select v-model="form.importType">
<el-option :label="$t('website.pasteSSL')" :value="'paste'"></el-option>
<el-option :label="$t('website.localSSL')" :value="'local'"></el-option>
</el-select>
</el-form-item>
<div v-if="form.importType === 'paste'">
<el-form-item :label="$t('website.privateKey')" prop="privateKey">
<el-input v-model="form.privateKey" :rows="6" type="textarea" />
</el-form-item>
<el-form-item :label="$t('website.certificate')" prop="certificate">
<el-input v-model="form.certificate" :rows="6" type="textarea" />
</el-form-item>
</div>
<div v-if="form.importType === 'local'">
<el-form-item :label="$t('website.privateKeyPath')" prop="privateKeyPath">
<el-input v-model="form.privateKeyPath">
<template #prepend>
<FileList @choose="getPrivateKeyPath" :dir="false"></FileList>
</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('website.certificatePath')" prop="certificatePath">
<el-input v-model="form.certificatePath">
<template #prepend>
<FileList @choose="getCertificatePath" :dir="false"></FileList>
</template>
</el-input>
</el-form-item>
</div>
</div>
<el-form-item :label="' '" v-if="websiteSSL && websiteSSL.id > 0">
<el-descriptions :column="5" border direction="vertical">
@ -137,6 +161,7 @@ import i18n from '@/lang';
import { Rules } from '@/global/form-rules';
import { dateFormatSimple, getProvider } from '@/utils/util';
import { MsgSuccess } from '@/utils/message';
import FileList from '@/components/file-list/index.vue';
const props = defineProps({
id: {
@ -154,8 +179,11 @@ const form = reactive({
websiteId: id.value,
websiteSSLId: undefined,
type: 'existed',
importType: 'paste',
privateKey: '',
certificate: '',
privateKeyPath: '',
certificatePath: '',
httpConfig: 'HTTPToHTTPS',
algorithm:
'EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5',
@ -169,6 +197,8 @@ const rules = ref({
type: [Rules.requiredSelect],
privateKey: [Rules.requiredInput],
certificate: [Rules.requiredInput],
privateKeyPath: [Rules.requiredInput],
certificatePath: [Rules.requiredInput],
websiteSSLId: [Rules.requiredSelect],
httpConfig: [Rules.requiredSelect],
SSLProtocol: [Rules.requiredSelect],
@ -180,6 +210,13 @@ const sslReq = reactive({
acmeAccountID: 0,
});
const getPrivateKeyPath = (path: string) => {
form.privateKeyPath = path;
};
const getCertificatePath = (path: string) => {
form.certificatePath = path;
};
const listSSL = () => {
sslReq.acmeAccountID = form.acmeAccountID;
ListSSL(sslReq).then((res) => {
@ -217,6 +254,7 @@ const changeType = (type: string) => {
const get = () => {
GetHTTPSConfig(id.value).then((res) => {
if (res.data) {
form.type = 'existed';
resData.value = res.data;
form.enable = res.data.enable;
if (res.data.httpConfig != '') {
@ -263,7 +301,7 @@ const changeEnable = (enable: boolean) => {
listSSL();
}
if (resData.value.enable && !enable) {
ElMessageBox.confirm(i18n.global.t('website.disbaleHTTPSHelper'), i18n.global.t('website.disbaleHTTPS'), {
ElMessageBox.confirm(i18n.global.t('website.disableHTTPSHelper'), i18n.global.t('website.disableHTTPS'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'error',