mirror of https://github.com/1Panel-dev/1Panel
ssongliu
2 years ago
committed by
GitHub
17 changed files with 731 additions and 20 deletions
@ -0,0 +1,112 @@
|
||||
package ssl |
||||
|
||||
import ( |
||||
"crypto/rand" |
||||
"crypto/rsa" |
||||
"crypto/x509" |
||||
"crypto/x509/pkix" |
||||
"encoding/pem" |
||||
"math/big" |
||||
"net" |
||||
"os" |
||||
"time" |
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global" |
||||
) |
||||
|
||||
func GenerateSSL(domain string) error { |
||||
rootPrivateKey, _ := rsa.GenerateKey(rand.Reader, 2048) |
||||
ipItem := net.ParseIP(domain) |
||||
isIP := false |
||||
if len(ipItem) != 0 { |
||||
isIP = true |
||||
} |
||||
|
||||
rootTemplate := x509.Certificate{ |
||||
SerialNumber: big.NewInt(1), |
||||
Subject: pkix.Name{CommonName: "1Panel Root CA"}, |
||||
NotBefore: time.Now(), |
||||
NotAfter: time.Now().AddDate(10, 0, 0), |
||||
BasicConstraintsValid: true, |
||||
IsCA: true, |
||||
KeyUsage: x509.KeyUsageCertSign, |
||||
} |
||||
if isIP { |
||||
rootTemplate.IPAddresses = []net.IP{ipItem} |
||||
} else { |
||||
rootTemplate.DNSNames = []string{domain} |
||||
} |
||||
|
||||
rootCertBytes, _ := x509.CreateCertificate(rand.Reader, &rootTemplate, &rootTemplate, &rootPrivateKey.PublicKey, rootPrivateKey) |
||||
rootCertBlock := &pem.Block{ |
||||
Type: "CERTIFICATE", |
||||
Bytes: rootCertBytes, |
||||
} |
||||
|
||||
interPrivateKey, _ := rsa.GenerateKey(rand.Reader, 2048) |
||||
interTemplate := x509.Certificate{ |
||||
SerialNumber: big.NewInt(2), |
||||
Subject: pkix.Name{CommonName: "1Panel Intermediate CA"}, |
||||
NotBefore: time.Now(), |
||||
NotAfter: time.Now().AddDate(10, 0, 0), |
||||
BasicConstraintsValid: true, |
||||
IsCA: true, |
||||
KeyUsage: x509.KeyUsageCertSign, |
||||
} |
||||
if isIP { |
||||
interTemplate.IPAddresses = []net.IP{ipItem} |
||||
} else { |
||||
interTemplate.DNSNames = []string{domain} |
||||
} |
||||
|
||||
interCertBytes, _ := x509.CreateCertificate(rand.Reader, &interTemplate, &rootTemplate, &interPrivateKey.PublicKey, rootPrivateKey) |
||||
interCertBlock := &pem.Block{ |
||||
Type: "CERTIFICATE", |
||||
Bytes: interCertBytes, |
||||
} |
||||
|
||||
clientPrivateKey, _ := rsa.GenerateKey(rand.Reader, 2048) |
||||
clientTemplate := x509.Certificate{ |
||||
SerialNumber: big.NewInt(3), |
||||
Subject: pkix.Name{CommonName: domain}, |
||||
NotBefore: time.Now(), |
||||
NotAfter: time.Now().AddDate(10, 0, 0), |
||||
KeyUsage: x509.KeyUsageDigitalSignature, |
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, |
||||
} |
||||
if isIP { |
||||
clientTemplate.IPAddresses = []net.IP{ipItem} |
||||
} else { |
||||
clientTemplate.DNSNames = []string{domain} |
||||
} |
||||
|
||||
clientCertBytes, _ := x509.CreateCertificate(rand.Reader, &clientTemplate, &interTemplate, &clientPrivateKey.PublicKey, interPrivateKey) |
||||
clientCertBlock := &pem.Block{ |
||||
Type: "CERTIFICATE", |
||||
Bytes: clientCertBytes, |
||||
} |
||||
|
||||
pemBytes := []byte{} |
||||
pemBytes = append(pemBytes, pem.EncodeToMemory(clientCertBlock)...) |
||||
pemBytes = append(pemBytes, pem.EncodeToMemory(interCertBlock)...) |
||||
pemBytes = append(pemBytes, pem.EncodeToMemory(rootCertBlock)...) |
||||
certOut, err := os.OpenFile(global.CONF.System.BaseDir+"/1panel/secret/server.crt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer certOut.Close() |
||||
if _, err := certOut.Write(pemBytes); err != nil { |
||||
return err |
||||
} |
||||
|
||||
keyOut, err := os.OpenFile(global.CONF.System.BaseDir+"/1panel/secret/server.key", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer keyOut.Close() |
||||
if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientPrivateKey)}); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -0,0 +1,194 @@
|
||||
<template> |
||||
<div> |
||||
<el-card> |
||||
<el-form ref="formRef" label-position="top" :model="form" :rules="rules"> |
||||
<el-radio-group v-model="sslItemType"> |
||||
<el-radio label="self">{{ $t('setting.selfSigned') }}</el-radio> |
||||
<el-radio label="select">{{ $t('setting.select') }}</el-radio> |
||||
<el-radio label="import">{{ $t('setting.import') }}</el-radio> |
||||
</el-radio-group> |
||||
<span class="input-help" v-if="sslItemType === 'self'">{{ $t('setting.selfSignedHelper') }}</span> |
||||
<div v-if="sslInfo.timeout"> |
||||
<el-tag>{{ $t('setting.domainOrIP') }} {{ sslInfo.domain }}</el-tag> |
||||
<el-tag style="margin-left: 5px">{{ $t('setting.timeOut') }} {{ sslInfo.timeout }}</el-tag> |
||||
<el-button |
||||
@click="onDownload" |
||||
style="margin-left: 5px" |
||||
v-if="sslItemType === 'self'" |
||||
type="primary" |
||||
link |
||||
icon="Download" |
||||
> |
||||
{{ $t('setting.rootCrtDownload') }} |
||||
</el-button> |
||||
</div> |
||||
|
||||
<div v-if="sslItemType === 'import'"> |
||||
<el-form-item :label="$t('setting.primaryKey')" prop="key"> |
||||
<el-input v-model="form.key" :autosize="{ minRows: 2, maxRows: 6 }" type="textarea" /> |
||||
</el-form-item> |
||||
<el-form-item class="margintop" :label="$t('setting.certificate')" prop="cert"> |
||||
<el-input v-model="form.cert" :autosize="{ minRows: 2, maxRows: 6 }" type="textarea" /> |
||||
</el-form-item> |
||||
</div> |
||||
|
||||
<div v-if="sslItemType === 'select'"> |
||||
<el-form-item :label="$t('setting.certificate')" prop="sslID"> |
||||
<el-select v-model="form.sslID" @change="changeSSl(form.sslID)"> |
||||
<el-option |
||||
v-for="(item, index) in sslList" |
||||
:key="index" |
||||
:label="item.primaryDomain" |
||||
:value="item.id" |
||||
></el-option> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-descriptions |
||||
class="margintop" |
||||
:column="5" |
||||
border |
||||
direction="vertical" |
||||
v-if="form.sslID > 0 && itemSSL" |
||||
> |
||||
<el-descriptions-item :label="$t('website.primaryDomain')"> |
||||
{{ itemSSL.primaryDomain }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item :label="$t('website.otherDomains')"> |
||||
{{ itemSSL.domains }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item :label="$t('ssl.provider')"> |
||||
{{ getProvider(itemSSL.provider) }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item |
||||
:label="$t('ssl.acmeAccount')" |
||||
v-if="itemSSL.acmeAccount?.email && itemSSL.provider !== 'manual'" |
||||
> |
||||
{{ itemSSL.acmeAccount.email }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item :label="$t('website.expireDate')"> |
||||
{{ dateFormatSimple(itemSSL.expireDate) }} |
||||
</el-descriptions-item> |
||||
</el-descriptions> |
||||
</div> |
||||
<el-button style="margin-top: 20px" type="primary" @click="onSaveSSL(formRef)"> |
||||
{{ $t('commons.button.saveAndEnable') }} |
||||
</el-button> |
||||
</el-form> |
||||
</el-card> |
||||
</div> |
||||
</template> |
||||
<script lang="ts" setup> |
||||
import { Website } from '@/api/interface/Website'; |
||||
import { loadSSLInfo } from '@/api/modules/setting'; |
||||
import { dateFormatSimple, getProvider } from '@/utils/util'; |
||||
import { ListSSL } from '@/api/modules/website'; |
||||
import { nextTick, onMounted, reactive, ref } from 'vue'; |
||||
import i18n from '@/lang'; |
||||
import { MsgSuccess } from '@/utils/message'; |
||||
import { updateSSL } from '@/api/modules/setting'; |
||||
import { DownloadByPath } from '@/api/modules/files'; |
||||
import { Rules } from '@/global/form-rules'; |
||||
import { FormInstance } from 'element-plus'; |
||||
|
||||
const form = reactive({ |
||||
ssl: 'enable', |
||||
domain: '', |
||||
sslType: 'self', |
||||
sslID: null as number, |
||||
cert: '', |
||||
key: '', |
||||
rootPath: '', |
||||
}); |
||||
|
||||
const rules = reactive({ |
||||
cert: [Rules.requiredInput], |
||||
key: [Rules.requiredInput], |
||||
sslID: [Rules.requiredSelect], |
||||
}); |
||||
|
||||
const formRef = ref<FormInstance>(); |
||||
|
||||
const props = defineProps({ |
||||
type: { |
||||
type: String, |
||||
default: 'self', |
||||
}, |
||||
}); |
||||
|
||||
const sslInfo = reactive({ |
||||
domain: '', |
||||
timeout: '', |
||||
}); |
||||
const sslList = ref(); |
||||
const itemSSL = ref(); |
||||
const sslItemType = ref('self'); |
||||
|
||||
const loadInfo = async () => { |
||||
await loadSSLInfo().then(async (res) => { |
||||
sslInfo.domain = res.data.domain || ''; |
||||
sslInfo.timeout = res.data.timeout || ''; |
||||
form.cert = res.data.cert; |
||||
form.key = res.data.key; |
||||
form.rootPath = res.data.rootPath; |
||||
if (res.data.sslID) { |
||||
form.sslID = res.data.sslID; |
||||
const ssls = await ListSSL({}); |
||||
sslList.value = ssls.data || []; |
||||
changeSSl(form.sslID); |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
const loadSSLs = async () => { |
||||
const res = await ListSSL({}); |
||||
sslList.value = res.data || []; |
||||
}; |
||||
|
||||
const changeSSl = (sslid: number) => { |
||||
const res = sslList.value.filter((element: Website.SSL) => { |
||||
return element.id == sslid; |
||||
}); |
||||
itemSSL.value = res[0]; |
||||
}; |
||||
|
||||
const onDownload = async () => { |
||||
const file = await DownloadByPath(form.rootPath); |
||||
const downloadUrl = window.URL.createObjectURL(new Blob([file])); |
||||
const a = document.createElement('a'); |
||||
a.style.display = 'none'; |
||||
a.href = downloadUrl; |
||||
a.download = 'server.crt'; |
||||
const event = new MouseEvent('click'); |
||||
a.dispatchEvent(event); |
||||
}; |
||||
|
||||
const onSaveSSL = async (formEl: FormInstance | undefined) => { |
||||
if (!formEl) return; |
||||
formEl.validate(async (valid) => { |
||||
if (!valid) return; |
||||
form.sslType = sslItemType.value; |
||||
let href = window.location.href; |
||||
form.domain = href.split('//')[1].split(':')[0]; |
||||
await updateSSL(form).then(() => { |
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); |
||||
let href = window.location.href; |
||||
let address = href.split('://')[1]; |
||||
window.open(`https://${address}/`, '_self'); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
onMounted(() => { |
||||
nextTick(() => { |
||||
sslItemType.value = props.type; |
||||
loadInfo(); |
||||
}); |
||||
loadSSLs(); |
||||
}); |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
.margintop { |
||||
margin-top: 10px; |
||||
} |
||||
</style> |
Loading…
Reference in new issue