mirror of
https://gitee.com/fit2cloud-feizhiyun/1Panel.git
synced 2024-12-04 12:59:52 +08:00
feat: 证书手动导入增加选择本地文件 (#1543)
This commit is contained in:
parent
35334c1650
commit
9fa9772c07
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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"
|
||||
|
@ -63,6 +63,10 @@ ErrSSLCannotDelete: "證書正在被網站使用,無法刪除"
|
||||
ErrAccountCannotDelete: "帳號關聯證書,無法刪除"
|
||||
ErrSSLApply: "證書續簽成功,openresty reload失敗,請檢查配置!"
|
||||
ErrEmailIsExist: '郵箱已存在'
|
||||
ErrSSLKeyNotFound: '私鑰文件不存在'
|
||||
ErrSSLCertificateNotFound: '證書文件不存在'
|
||||
ErrSSLKeyFormat: '私鑰文件格式錯誤,請使用 pem 格式'
|
||||
ErrSSLCertificateFormat: '證書文件格式錯誤,請使用 pem 格式'
|
||||
|
||||
#mysql
|
||||
ErrUserIsExist: "當前用戶已存在,請重新輸入"
|
||||
|
@ -63,6 +63,10 @@ ErrSSLCannotDelete: "证书正在被网站使用,无法删除"
|
||||
ErrAccountCannotDelete: "账号关联证书,无法删除"
|
||||
ErrSSLApply: "证书续签成功,openresty reload失败,请检查配置!"
|
||||
ErrEmailIsExist: '邮箱已存在'
|
||||
ErrSSLKeyNotFound: '私钥文件不存在'
|
||||
ErrSSLCertificateNotFound: '证书文件不存在'
|
||||
ErrSSLKeyFormat: '私钥文件格式错误,请使用 pem 格式'
|
||||
ErrSSLCertificateFormat: '证书文件格式错误,请使用 pem 格式'
|
||||
|
||||
#mysql
|
||||
ErrUserIsExist: "当前用户已存在,请重新输入"
|
||||
|
@ -41,7 +41,6 @@ export namespace App {
|
||||
readme: string;
|
||||
params: AppParams;
|
||||
dockerCompose: string;
|
||||
enbale: boolean;
|
||||
image: string;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
|
@ -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: '短標簽支持',
|
||||
|
@ -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: '短标签支持',
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user