nginx-ui/api/certificate/certificate.go

263 lines
7.7 KiB
Go

package certificate
import (
"net/http"
"os"
"github.com/0xJacky/Nginx-UI/internal/cert"
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/internal/notification"
"github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/spf13/cast"
"github.com/uozi-tech/cosy"
)
type APICertificate struct {
*model.Cert
SSLCertificate string `json:"ssl_certificate,omitempty"`
SSLCertificateKey string `json:"ssl_certificate_key,omitempty"`
CertificateInfo *cert.Info `json:"certificate_info,omitempty"`
}
func Transformer(certModel *model.Cert) (certificate *APICertificate) {
var sslCertificationBytes, sslCertificationKeyBytes []byte
var certificateInfo *cert.Info
if certModel.SSLCertificatePath != "" &&
helper.IsUnderDirectory(certModel.SSLCertificatePath, nginx.GetConfPath()) {
if _, err := os.Stat(certModel.SSLCertificatePath); err == nil {
sslCertificationBytes, _ = os.ReadFile(certModel.SSLCertificatePath)
if !cert.IsCertificate(string(sslCertificationBytes)) {
sslCertificationBytes = []byte{}
}
}
certificateInfo, _ = cert.GetCertInfo(certModel.SSLCertificatePath)
}
if certModel.SSLCertificateKeyPath != "" &&
helper.IsUnderDirectory(certModel.SSLCertificateKeyPath, nginx.GetConfPath()) {
if _, err := os.Stat(certModel.SSLCertificateKeyPath); err == nil {
sslCertificationKeyBytes, _ = os.ReadFile(certModel.SSLCertificateKeyPath)
if !cert.IsPrivateKey(string(sslCertificationKeyBytes)) {
sslCertificationKeyBytes = []byte{}
}
}
}
return &APICertificate{
Cert: certModel,
SSLCertificate: string(sslCertificationBytes),
SSLCertificateKey: string(sslCertificationKeyBytes),
CertificateInfo: certificateInfo,
}
}
func GetCertList(c *gin.Context) {
cosy.Core[model.Cert](c).SetFussy("name", "domain").
SetTransformer(func(m *model.Cert) any {
info, _ := cert.GetCertInfo(m.SSLCertificatePath)
return APICertificate{
Cert: m,
CertificateInfo: info,
}
}).PagingList()
}
func GetCert(c *gin.Context) {
q := query.Cert
id := cast.ToUint64(c.Param("id"))
if contextId, ok := c.Get("id"); ok {
id = cast.ToUint64(contextId)
}
certModel, err := q.FirstByID(id)
if err != nil {
cosy.ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, Transformer(certModel))
}
func AddCert(c *gin.Context) {
cosy.Core[model.Cert](c).
SetValidRules(gin.H{
"name": "omitempty",
"ssl_certificate_path": "required,certificate_path",
"ssl_certificate_key_path": "required,privatekey_path",
"ssl_certificate": "omitempty,certificate",
"ssl_certificate_key": "omitempty,privatekey",
"key_type": "omitempty,auto_cert_key_type",
"challenge_method": "omitempty,oneof=http01 dns01",
"dns_credential_id": "omitempty",
"acme_user_id": "omitempty",
"sync_node_ids": "omitempty",
"must_staple": "omitempty",
"lego_disable_cname_support": "omitempty",
"revoke_old": "omitempty",
}).
BeforeExecuteHook(func(ctx *cosy.Ctx[model.Cert]) {
sslCertificate := cast.ToString(ctx.Payload["ssl_certificate"])
// Detect and set certificate type
if sslCertificate != "" {
keyType, err := cert.GetKeyType(sslCertificate)
if err == nil && keyType != "" {
// Set KeyType based on certificate type
switch keyType {
case "2048":
ctx.Model.KeyType = certcrypto.RSA2048
case "3072":
ctx.Model.KeyType = certcrypto.RSA3072
case "4096":
ctx.Model.KeyType = certcrypto.RSA4096
case "P256":
ctx.Model.KeyType = certcrypto.EC256
case "P384":
ctx.Model.KeyType = certcrypto.EC384
}
}
}
}).
ExecutedHook(func(ctx *cosy.Ctx[model.Cert]) {
sslCertificate := cast.ToString(ctx.Payload["ssl_certificate"])
sslCertificateKey := cast.ToString(ctx.Payload["ssl_certificate_key"])
if sslCertificate != "" && sslCertificateKey != "" {
content := &cert.Content{
SSLCertificatePath: ctx.Model.SSLCertificatePath,
SSLCertificateKeyPath: ctx.Model.SSLCertificateKeyPath,
SSLCertificate: sslCertificate,
SSLCertificateKey: sslCertificateKey,
}
err := content.WriteFile()
if err != nil {
ctx.AbortWithError(err)
return
}
}
err := cert.SyncToRemoteServer(&ctx.Model)
if err != nil {
notification.Error("Sync Certificate Error", err.Error(), nil)
return
}
ctx.Context.Set("id", ctx.Model.ID)
}).
SetNextHandler(GetCert).
Create()
}
func ModifyCert(c *gin.Context) {
cosy.Core[model.Cert](c).
SetValidRules(gin.H{
"name": "omitempty",
"ssl_certificate_path": "required,certificate_path",
"ssl_certificate_key_path": "required,privatekey_path",
"ssl_certificate": "omitempty,certificate",
"ssl_certificate_key": "omitempty,privatekey",
"key_type": "omitempty,auto_cert_key_type",
"challenge_method": "omitempty,oneof=http01 dns01",
"dns_credential_id": "omitempty",
"acme_user_id": "omitempty",
"sync_node_ids": "omitempty",
"must_staple": "omitempty",
"lego_disable_cname_support": "omitempty",
"revoke_old": "omitempty",
}).
BeforeExecuteHook(func(ctx *cosy.Ctx[model.Cert]) {
sslCertificate := cast.ToString(ctx.Payload["ssl_certificate"])
// Detect and set certificate type
if sslCertificate != "" {
keyType, err := cert.GetKeyType(sslCertificate)
if err == nil && keyType != "" {
// Set KeyType based on certificate type
switch keyType {
case "2048":
ctx.Model.KeyType = certcrypto.RSA2048
case "3072":
ctx.Model.KeyType = certcrypto.RSA3072
case "4096":
ctx.Model.KeyType = certcrypto.RSA4096
case "P256":
ctx.Model.KeyType = certcrypto.EC256
case "P384":
ctx.Model.KeyType = certcrypto.EC384
}
}
}
}).
ExecutedHook(func(ctx *cosy.Ctx[model.Cert]) {
sslCertificate := cast.ToString(ctx.Payload["ssl_certificate"])
sslCertificateKey := cast.ToString(ctx.Payload["ssl_certificate_key"])
content := &cert.Content{
SSLCertificatePath: ctx.Model.SSLCertificatePath,
SSLCertificateKeyPath: ctx.Model.SSLCertificateKeyPath,
SSLCertificate: sslCertificate,
SSLCertificateKey: sslCertificateKey,
}
err := content.WriteFile()
if err != nil {
ctx.AbortWithError(err)
return
}
err = cert.SyncToRemoteServer(&ctx.Model)
if err != nil {
notification.Error("Sync Certificate Error", err.Error(), nil)
return
}
}).
SetNextHandler(GetCert).
Modify()
}
func RemoveCert(c *gin.Context) {
cosy.Core[model.Cert](c).Destroy()
}
func SyncCertificate(c *gin.Context) {
var json cert.SyncCertificatePayload
if !cosy.BindAndValid(c, &json) {
return
}
certModel := &model.Cert{
Name: json.Name,
SSLCertificatePath: json.SSLCertificatePath,
SSLCertificateKeyPath: json.SSLCertificateKeyPath,
KeyType: json.KeyType,
AutoCert: model.AutoCertSync,
}
db := model.UseDB()
err := db.Where(certModel).FirstOrCreate(certModel).Error
if err != nil {
cosy.ErrHandler(c, err)
return
}
content := &cert.Content{
SSLCertificatePath: json.SSLCertificatePath,
SSLCertificateKeyPath: json.SSLCertificateKeyPath,
SSLCertificate: json.SSLCertificate,
SSLCertificateKey: json.SSLCertificateKey,
}
err = content.WriteFile()
if err != nil {
cosy.ErrHandler(c, err)
return
}
nginx.Reload()
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
}