feat: SSL management support different types of certificates of a same doamin name #309

This commit is contained in:
Jacky 2024-04-30 16:05:25 +08:00
parent 464e84a64f
commit 3e90b838fd
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
11 changed files with 54 additions and 24 deletions

View file

@ -6,6 +6,7 @@ import (
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/model"
"github.com/gin-gonic/gin"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/gorilla/websocket"
"net/http"
"strings"
@ -18,10 +19,11 @@ const (
)
type IssueCertResponse struct {
Status string `json:"status"`
Message string `json:"message"`
SSLCertificate string `json:"ssl_certificate,omitempty"`
SSLCertificateKey string `json:"ssl_certificate_key,omitempty"`
Status string `json:"status"`
Message string `json:"message"`
SSLCertificate string `json:"ssl_certificate,omitempty"`
SSLCertificateKey string `json:"ssl_certificate_key,omitempty"`
KeyType certcrypto.KeyType `json:"key_type"`
}
func handleIssueCertLogChan(conn *websocket.Conn, log *cert.Logger, logChan chan string) {
@ -75,8 +77,7 @@ func IssueCert(c *gin.Context) {
return
}
certModel, err := model.FirstOrCreateCert(c.Param("name"))
certModel, err := model.FirstOrCreateCert(c.Param("name"), payload.GetKeyType())
if err != nil {
logger.Error(err)
return
@ -113,7 +114,7 @@ func IssueCert(c *gin.Context) {
return
}
certDirName := strings.Join(payload.ServerName, "_")
certDirName := strings.Join(payload.ServerName, "_") + "_" + string(payload.GetKeyType())
sslCertificatePath := nginx.GetConfPath("ssl", certDirName, "fullchain.cer")
sslCertificateKeyPath := nginx.GetConfPath("ssl", certDirName, "private.key")
@ -144,6 +145,7 @@ func IssueCert(c *gin.Context) {
Message: "Issued certificate successfully",
SSLCertificate: sslCertificatePath,
SSLCertificateKey: sslCertificateKeyPath,
KeyType: payload.GetKeyType(),
})
if err != nil {

View file

@ -2,8 +2,10 @@ package sites
import (
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/model"
"github.com/gin-gonic/gin"
"github.com/go-acme/lego/v4/certcrypto"
"net/http"
)
@ -11,16 +13,17 @@ func AddDomainToAutoCert(c *gin.Context) {
name := c.Param("name")
var json struct {
DnsCredentialID int `json:"dns_credential_id"`
ChallengeMethod string `json:"challenge_method"`
Domains []string `json:"domains"`
DnsCredentialID int `json:"dns_credential_id"`
ChallengeMethod string `json:"challenge_method"`
Domains []string `json:"domains"`
KeyType certcrypto.KeyType `json:"key_type"`
}
if !api.BindAndValid(c, &json) {
return
}
certModel, err := model.FirstOrCreateCert(name)
certModel, err := model.FirstOrCreateCert(name, helper.GetKeyType(json.KeyType))
if err != nil {
api.ErrHandler(c, err)

View file

@ -2,6 +2,7 @@ import type { ModelBase } from '@/api/curd'
import Curd from '@/api/curd'
import type { DnsCredential } from '@/api/dns_credential'
import type { AcmeUser } from '@/api/acme_user'
import type { PrivateKeyType } from '@/constants'
export interface Cert extends ModelBase {
name: string
@ -32,6 +33,7 @@ export interface CertificateInfo {
export interface CertificateResult {
ssl_certificate: string
ssl_certificate_key: string
key_type: PrivateKeyType
}
const cert: Curd<Cert> = new Curd('/cert')

View file

@ -3,6 +3,7 @@ import http from '@/lib/http'
import type { ChatComplicationMessage } from '@/api/openai'
import type { CertificateInfo } from '@/api/cert'
import type { NgxConfig } from '@/api/ngx'
import type { PrivateKeyType } from '@/constants'
export interface Site {
modified_at: string
@ -22,6 +23,7 @@ export interface AutoCertRequest {
dns_credential_id: number | null
challenge_method: string
domains: string[]
key_type: PrivateKeyType
}
class Domain extends Curd<Site> {

View file

@ -36,3 +36,5 @@ export const PrivateKeyTypeMask = {
} as const
export const PrivateKeyTypeList = Object.entries(PrivateKeyTypeMask).map(([key, name]) => ({ key, name }))
export type PrivateKeyType = keyof typeof PrivateKeyTypeMask

View file

@ -8,6 +8,7 @@ import type { Props } from '@/views/domain/cert/IssueCert.vue'
import type { DnsChallenge } from '@/api/auto_cert'
import ObtainCertLive from '@/views/domain/cert/components/ObtainCertLive.vue'
import type { CertificateResult } from '@/api/cert'
import type { PrivateKeyType } from '@/constants'
const emit = defineEmits(['update:auto_cert'])
@ -48,20 +49,21 @@ const issue_cert = (config_name: string, server_name: string) => {
refObtainCertLive.value.issue_cert(config_name, server_name.trim().split(' ')).then(resolveCert)
}
async function resolveCert({ ssl_certificate, ssl_certificate_key }: CertificateResult) {
async function resolveCert({ ssl_certificate, ssl_certificate_key, key_type }: CertificateResult) {
directivesMap.value.ssl_certificate[0].params = ssl_certificate
directivesMap.value.ssl_certificate_key[0].params = ssl_certificate_key
await save_config()
change_auto_cert(true)
change_auto_cert(true, key_type)
emit('update:auto_cert', true)
}
function change_auto_cert(status: boolean) {
function change_auto_cert(status: boolean, key_type?: PrivateKeyType) {
if (status) {
domain.add_auto_cert(props.configName, {
domains: name.value.trim().split(' '),
challenge_method: data.value.challenge_method,
dns_credential_id: data.value.dns_credential_id,
key_type: key_type!,
}).then(() => {
message.success($gettext('Auto-renewal enabled for %{name}', { name: name.value }))
}).catch(e => {

View file

@ -106,7 +106,11 @@ const issue_cert = async (config_name: string, server_name: string[], key_type:
if (r.status === 'success' && r.ssl_certificate !== undefined && r.ssl_certificate_key !== undefined) {
progressStatus.value = 'success'
progressPercent.value = 100
resolve({ ssl_certificate: r.ssl_certificate, ssl_certificate_key: r.ssl_certificate_key })
resolve({
ssl_certificate: r.ssl_certificate,
ssl_certificate_key: r.ssl_certificate_key,
key_type: r.key_type,
})
}
else {
progressStatus.value = 'exception'

View file

@ -167,7 +167,7 @@ func IssueCert(payload *ConfigPayload, logChan chan string, errChan chan error)
return
}
name := strings.Join(domain, "_")
saveDir := nginx.GetConfPath("ssl/" + name)
saveDir := nginx.GetConfPath("ssl/" + name + "_" + string(payload.KeyType))
if _, err = os.Stat(saveDir); os.IsNotExist(err) {
err = os.MkdirAll(saveDir, 0755)
if err != nil {

View file

@ -1,6 +1,7 @@
package cert
import (
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/go-acme/lego/v4/certcrypto"
)
@ -10,3 +11,7 @@ type ConfigPayload struct {
DNSCredentialID int `json:"dns_credential_id"`
KeyType certcrypto.KeyType `json:"key_type"`
}
func (c *ConfigPayload) GetKeyType() certcrypto.KeyType {
return helper.GetKeyType(c.KeyType)
}

View file

@ -0,0 +1,12 @@
package helper
import "github.com/go-acme/lego/v4/certcrypto"
func GetKeyType(keyType certcrypto.KeyType) certcrypto.KeyType {
switch keyType {
case certcrypto.RSA2048, certcrypto.RSA3072, certcrypto.RSA4096,
certcrypto.EC256, certcrypto.EC384:
return keyType
}
return certcrypto.RSA2048
}

View file

@ -1,6 +1,7 @@
package model
import (
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/lib/pq"
@ -41,9 +42,9 @@ func FirstCert(confName string) (c Cert, err error) {
return
}
func FirstOrCreateCert(confName string) (c Cert, err error) {
func FirstOrCreateCert(confName string, keyType certcrypto.KeyType) (c Cert, err error) {
// Filename is used to check whether this site is enabled
err = db.FirstOrCreate(&c, &Cert{Name: confName, Filename: confName}).Error
err = db.FirstOrCreate(&c, &Cert{Name: confName, Filename: confName, KeyType: keyType}).Error
return
}
@ -96,10 +97,5 @@ func (c *Cert) Remove() error {
}
func (c *Cert) GetKeyType() certcrypto.KeyType {
switch c.KeyType {
case certcrypto.RSA2048, certcrypto.RSA3072, certcrypto.RSA4096,
certcrypto.EC256, certcrypto.EC384:
return c.KeyType
}
return certcrypto.RSA2048
return helper.GetKeyType(c.KeyType)
}