mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
feat: SSL management support different types of certificates of a same doamin name #309
This commit is contained in:
parent
464e84a64f
commit
3e90b838fd
11 changed files with 54 additions and 24 deletions
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
||||||
"github.com/0xJacky/Nginx-UI/model"
|
"github.com/0xJacky/Nginx-UI/model"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -22,6 +23,7 @@ type IssueCertResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
SSLCertificate string `json:"ssl_certificate,omitempty"`
|
SSLCertificate string `json:"ssl_certificate,omitempty"`
|
||||||
SSLCertificateKey string `json:"ssl_certificate_key,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) {
|
func handleIssueCertLogChan(conn *websocket.Conn, log *cert.Logger, logChan chan string) {
|
||||||
|
@ -75,8 +77,7 @@ func IssueCert(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
certModel, err := model.FirstOrCreateCert(c.Param("name"))
|
certModel, err := model.FirstOrCreateCert(c.Param("name"), payload.GetKeyType())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
return
|
return
|
||||||
|
@ -113,7 +114,7 @@ func IssueCert(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
certDirName := strings.Join(payload.ServerName, "_")
|
certDirName := strings.Join(payload.ServerName, "_") + "_" + string(payload.GetKeyType())
|
||||||
sslCertificatePath := nginx.GetConfPath("ssl", certDirName, "fullchain.cer")
|
sslCertificatePath := nginx.GetConfPath("ssl", certDirName, "fullchain.cer")
|
||||||
sslCertificateKeyPath := nginx.GetConfPath("ssl", certDirName, "private.key")
|
sslCertificateKeyPath := nginx.GetConfPath("ssl", certDirName, "private.key")
|
||||||
|
|
||||||
|
@ -144,6 +145,7 @@ func IssueCert(c *gin.Context) {
|
||||||
Message: "Issued certificate successfully",
|
Message: "Issued certificate successfully",
|
||||||
SSLCertificate: sslCertificatePath,
|
SSLCertificate: sslCertificatePath,
|
||||||
SSLCertificateKey: sslCertificateKeyPath,
|
SSLCertificateKey: sslCertificateKeyPath,
|
||||||
|
KeyType: payload.GetKeyType(),
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,8 +2,10 @@ package sites
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/0xJacky/Nginx-UI/api"
|
"github.com/0xJacky/Nginx-UI/api"
|
||||||
|
"github.com/0xJacky/Nginx-UI/internal/helper"
|
||||||
"github.com/0xJacky/Nginx-UI/model"
|
"github.com/0xJacky/Nginx-UI/model"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,13 +16,14 @@ func AddDomainToAutoCert(c *gin.Context) {
|
||||||
DnsCredentialID int `json:"dns_credential_id"`
|
DnsCredentialID int `json:"dns_credential_id"`
|
||||||
ChallengeMethod string `json:"challenge_method"`
|
ChallengeMethod string `json:"challenge_method"`
|
||||||
Domains []string `json:"domains"`
|
Domains []string `json:"domains"`
|
||||||
|
KeyType certcrypto.KeyType `json:"key_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if !api.BindAndValid(c, &json) {
|
if !api.BindAndValid(c, &json) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
certModel, err := model.FirstOrCreateCert(name)
|
certModel, err := model.FirstOrCreateCert(name, helper.GetKeyType(json.KeyType))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.ErrHandler(c, err)
|
api.ErrHandler(c, err)
|
||||||
|
|
|
@ -2,6 +2,7 @@ import type { ModelBase } from '@/api/curd'
|
||||||
import Curd from '@/api/curd'
|
import Curd from '@/api/curd'
|
||||||
import type { DnsCredential } from '@/api/dns_credential'
|
import type { DnsCredential } from '@/api/dns_credential'
|
||||||
import type { AcmeUser } from '@/api/acme_user'
|
import type { AcmeUser } from '@/api/acme_user'
|
||||||
|
import type { PrivateKeyType } from '@/constants'
|
||||||
|
|
||||||
export interface Cert extends ModelBase {
|
export interface Cert extends ModelBase {
|
||||||
name: string
|
name: string
|
||||||
|
@ -32,6 +33,7 @@ export interface CertificateInfo {
|
||||||
export interface CertificateResult {
|
export interface CertificateResult {
|
||||||
ssl_certificate: string
|
ssl_certificate: string
|
||||||
ssl_certificate_key: string
|
ssl_certificate_key: string
|
||||||
|
key_type: PrivateKeyType
|
||||||
}
|
}
|
||||||
|
|
||||||
const cert: Curd<Cert> = new Curd('/cert')
|
const cert: Curd<Cert> = new Curd('/cert')
|
||||||
|
|
|
@ -3,6 +3,7 @@ import http from '@/lib/http'
|
||||||
import type { ChatComplicationMessage } from '@/api/openai'
|
import type { ChatComplicationMessage } from '@/api/openai'
|
||||||
import type { CertificateInfo } from '@/api/cert'
|
import type { CertificateInfo } from '@/api/cert'
|
||||||
import type { NgxConfig } from '@/api/ngx'
|
import type { NgxConfig } from '@/api/ngx'
|
||||||
|
import type { PrivateKeyType } from '@/constants'
|
||||||
|
|
||||||
export interface Site {
|
export interface Site {
|
||||||
modified_at: string
|
modified_at: string
|
||||||
|
@ -22,6 +23,7 @@ export interface AutoCertRequest {
|
||||||
dns_credential_id: number | null
|
dns_credential_id: number | null
|
||||||
challenge_method: string
|
challenge_method: string
|
||||||
domains: string[]
|
domains: string[]
|
||||||
|
key_type: PrivateKeyType
|
||||||
}
|
}
|
||||||
|
|
||||||
class Domain extends Curd<Site> {
|
class Domain extends Curd<Site> {
|
||||||
|
|
|
@ -36,3 +36,5 @@ export const PrivateKeyTypeMask = {
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const PrivateKeyTypeList = Object.entries(PrivateKeyTypeMask).map(([key, name]) => ({ key, name }))
|
export const PrivateKeyTypeList = Object.entries(PrivateKeyTypeMask).map(([key, name]) => ({ key, name }))
|
||||||
|
|
||||||
|
export type PrivateKeyType = keyof typeof PrivateKeyTypeMask
|
||||||
|
|
|
@ -8,6 +8,7 @@ import type { Props } from '@/views/domain/cert/IssueCert.vue'
|
||||||
import type { DnsChallenge } from '@/api/auto_cert'
|
import type { DnsChallenge } from '@/api/auto_cert'
|
||||||
import ObtainCertLive from '@/views/domain/cert/components/ObtainCertLive.vue'
|
import ObtainCertLive from '@/views/domain/cert/components/ObtainCertLive.vue'
|
||||||
import type { CertificateResult } from '@/api/cert'
|
import type { CertificateResult } from '@/api/cert'
|
||||||
|
import type { PrivateKeyType } from '@/constants'
|
||||||
|
|
||||||
const emit = defineEmits(['update:auto_cert'])
|
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)
|
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[0].params = ssl_certificate
|
||||||
directivesMap.value.ssl_certificate_key[0].params = ssl_certificate_key
|
directivesMap.value.ssl_certificate_key[0].params = ssl_certificate_key
|
||||||
await save_config()
|
await save_config()
|
||||||
change_auto_cert(true)
|
change_auto_cert(true, key_type)
|
||||||
emit('update:auto_cert', true)
|
emit('update:auto_cert', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function change_auto_cert(status: boolean) {
|
function change_auto_cert(status: boolean, key_type?: PrivateKeyType) {
|
||||||
if (status) {
|
if (status) {
|
||||||
domain.add_auto_cert(props.configName, {
|
domain.add_auto_cert(props.configName, {
|
||||||
domains: name.value.trim().split(' '),
|
domains: name.value.trim().split(' '),
|
||||||
challenge_method: data.value.challenge_method,
|
challenge_method: data.value.challenge_method,
|
||||||
dns_credential_id: data.value.dns_credential_id,
|
dns_credential_id: data.value.dns_credential_id,
|
||||||
|
key_type: key_type!,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
message.success($gettext('Auto-renewal enabled for %{name}', { name: name.value }))
|
message.success($gettext('Auto-renewal enabled for %{name}', { name: name.value }))
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
|
|
|
@ -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) {
|
if (r.status === 'success' && r.ssl_certificate !== undefined && r.ssl_certificate_key !== undefined) {
|
||||||
progressStatus.value = 'success'
|
progressStatus.value = 'success'
|
||||||
progressPercent.value = 100
|
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 {
|
else {
|
||||||
progressStatus.value = 'exception'
|
progressStatus.value = 'exception'
|
||||||
|
|
|
@ -167,7 +167,7 @@ func IssueCert(payload *ConfigPayload, logChan chan string, errChan chan error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
name := strings.Join(domain, "_")
|
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) {
|
if _, err = os.Stat(saveDir); os.IsNotExist(err) {
|
||||||
err = os.MkdirAll(saveDir, 0755)
|
err = os.MkdirAll(saveDir, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package cert
|
package cert
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/0xJacky/Nginx-UI/internal/helper"
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,3 +11,7 @@ type ConfigPayload struct {
|
||||||
DNSCredentialID int `json:"dns_credential_id"`
|
DNSCredentialID int `json:"dns_credential_id"`
|
||||||
KeyType certcrypto.KeyType `json:"key_type"`
|
KeyType certcrypto.KeyType `json:"key_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ConfigPayload) GetKeyType() certcrypto.KeyType {
|
||||||
|
return helper.GetKeyType(c.KeyType)
|
||||||
|
}
|
||||||
|
|
12
internal/helper/key_type.go
Normal file
12
internal/helper/key_type.go
Normal 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
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/0xJacky/Nginx-UI/internal/helper"
|
||||||
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
|
@ -41,9 +42,9 @@ func FirstCert(confName string) (c Cert, err error) {
|
||||||
return
|
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
|
// 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,10 +97,5 @@ func (c *Cert) Remove() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cert) GetKeyType() certcrypto.KeyType {
|
func (c *Cert) GetKeyType() certcrypto.KeyType {
|
||||||
switch c.KeyType {
|
return helper.GetKeyType(c.KeyType)
|
||||||
case certcrypto.RSA2048, certcrypto.RSA3072, certcrypto.RSA4096,
|
|
||||||
certcrypto.EC256, certcrypto.EC384:
|
|
||||||
return c.KeyType
|
|
||||||
}
|
|
||||||
return certcrypto.RSA2048
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue