mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-10 18:05:48 +02:00
feat: using renewal api to renew certificate #319
This commit is contained in:
parent
e3876cffaf
commit
e16b077d20
11 changed files with 173 additions and 71 deletions
|
@ -83,6 +83,14 @@ func IssueCert(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
certInfo, err := cert.GetCertInfo(certModel.SSLCertificatePath)
|
||||
if err != nil {
|
||||
logger.Error("get certificate info error", err)
|
||||
return
|
||||
}
|
||||
payload.Resource = certModel.Resource
|
||||
payload.NotBefore = certInfo.NotBefore
|
||||
|
||||
logChan := make(chan string, 1)
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
|
@ -126,6 +134,7 @@ func IssueCert(c *gin.Context) {
|
|||
KeyType: payload.KeyType,
|
||||
ChallengeMethod: payload.ChallengeMethod,
|
||||
DnsCredentialID: payload.DNSCredentialID,
|
||||
Resource: payload.Resource,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
|
Binary file not shown.
|
@ -442,7 +442,7 @@ msgstr "DNS 凭证"
|
|||
#: src/views/certificate/DNSChallenge.vue:74
|
||||
#: src/views/domain/cert/components/DNSChallenge.vue:95
|
||||
msgid "DNS Provider"
|
||||
msgstr "DNS供应商"
|
||||
msgstr "DNS 提供商"
|
||||
|
||||
#: src/views/domain/cert/components/AutoCertStepOne.vue:104
|
||||
msgid "DNS01"
|
||||
|
@ -1181,8 +1181,8 @@ msgid ""
|
|||
"Please first add credentials in Certification > DNS Credentials, and then "
|
||||
"select one of the credentialsbelow to request the API of the DNS provider."
|
||||
msgstr ""
|
||||
"请首先在 “证书”> “DNS凭证” 中添加凭证,然后在下方选择一个凭证,请求DNS提供商"
|
||||
"的API。"
|
||||
"请首先在 “证书”> “DNS 凭证” 中添加凭证,然后在下方选择一个凭证,请求 DNS 提供"
|
||||
"商的 API。"
|
||||
|
||||
#: src/views/domain/components/SiteDuplicate.vue:40
|
||||
#: src/views/stream/components/StreamDuplicate.vue:40
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
func AutoObtain() {
|
||||
func AutoCert() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
buf := make([]byte, 1024)
|
||||
|
@ -22,12 +22,12 @@ func AutoObtain() {
|
|||
logger.Info("AutoCert Worker Started")
|
||||
autoCertList := model.GetAutoCertList()
|
||||
for _, certModel := range autoCertList {
|
||||
renew(certModel)
|
||||
autoCert(certModel)
|
||||
}
|
||||
logger.Info("AutoCert Worker End")
|
||||
}
|
||||
|
||||
func renew(certModel *model.Cert) {
|
||||
func autoCert(certModel *model.Cert) {
|
||||
confName := certModel.Filename
|
||||
|
||||
log := &Logger{}
|
||||
|
@ -75,6 +75,14 @@ func renew(certModel *model.Cert) {
|
|||
ChallengeMethod: certModel.ChallengeMethod,
|
||||
DNSCredentialID: certModel.DnsCredentialID,
|
||||
KeyType: certModel.GetKeyType(),
|
||||
Resource: &model.CertificateResource{
|
||||
Resource: certModel.Resource.Resource,
|
||||
PrivateKey: certModel.Resource.PrivateKey,
|
||||
Certificate: certModel.Resource.Certificate,
|
||||
IssuerCertificate: certModel.Resource.IssuerCertificate,
|
||||
CSR: certModel.Resource.CSR,
|
||||
},
|
||||
NotBefore: cert.NotBefore,
|
||||
}
|
||||
|
||||
// errChan will be closed inside IssueCert
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
||||
"github.com/0xJacky/Nginx-UI/query"
|
||||
"github.com/0xJacky/Nginx-UI/settings"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/challenge/http01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
legolog "github.com/go-acme/lego/v4/log"
|
||||
|
@ -16,8 +15,6 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -44,15 +41,13 @@ func IssueCert(payload *ConfigPayload, logChan chan string, errChan chan error)
|
|||
// Hijack the (logger) of lego
|
||||
legolog.Logger = l
|
||||
|
||||
domain := payload.ServerName
|
||||
|
||||
l.Println("[INFO] [Nginx UI] Preparing lego configurations")
|
||||
user, err := payload.GetACMEUser()
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "issue cert get acme user error")
|
||||
return
|
||||
}
|
||||
l.Printf("[INFO] [Nginx UI] ACME User: %s, CA Dir: %s\n", user.Email, user.CADir)
|
||||
l.Printf("[INFO] [Nginx UI] ACME User: %s, Email: %s, CA Dir: %s\n", user.Name, user.Email, user.CADir)
|
||||
|
||||
// Start a goroutine to fetch and process logs from channel
|
||||
go func() {
|
||||
|
@ -134,45 +129,11 @@ func IssueCert(payload *ConfigPayload, logChan chan string, errChan chan error)
|
|||
return
|
||||
}
|
||||
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: domain,
|
||||
Bundle: true,
|
||||
}
|
||||
|
||||
l.Println("[INFO] [Nginx UI] Obtaining certificate")
|
||||
certificates, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "obtain certificate error")
|
||||
return
|
||||
}
|
||||
name := strings.Join(domain, "_")
|
||||
saveDir := nginx.GetConfPath("ssl/" + name + "_" + string(payload.KeyType))
|
||||
if _, err = os.Stat(saveDir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(saveDir, 0755)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "mkdir error")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Each certificate comes back with the cert bytes, the bytes of the client's
|
||||
// private key, and a certificate URL. SAVE THESE TO DISK.
|
||||
l.Println("[INFO] [Nginx UI] Writing certificate to disk")
|
||||
err = os.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
|
||||
certificates.Certificate, 0644)
|
||||
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "write fullchain.cer error")
|
||||
return
|
||||
}
|
||||
|
||||
l.Println("[INFO] [Nginx UI] Writing certificate private key to disk")
|
||||
err = os.WriteFile(filepath.Join(saveDir, "private.key"),
|
||||
certificates.PrivateKey, 0644)
|
||||
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "write private.key error")
|
||||
return
|
||||
if time.Now().Sub(payload.NotBefore).Hours()/24 <= 21 &&
|
||||
payload.Resource != nil && payload.Resource.Certificate != nil {
|
||||
renew(payload, client, l, errChan)
|
||||
} else {
|
||||
obtain(payload, client, l, errChan)
|
||||
}
|
||||
|
||||
l.Println("[INFO] [Nginx UI] Reloading nginx")
|
||||
|
|
63
internal/cert/obtain.go
Normal file
63
internal/cert/obtain.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package cert
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/pkg/errors"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func obtain(payload *ConfigPayload, client *lego.Client, l *log.Logger, errChan chan error) {
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: payload.ServerName,
|
||||
Bundle: true,
|
||||
}
|
||||
|
||||
l.Println("[INFO] [Nginx UI] Obtaining certificate")
|
||||
certificates, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "obtain certificate error")
|
||||
return
|
||||
}
|
||||
payload.Resource = &model.CertificateResource{
|
||||
Resource: certificates,
|
||||
PrivateKey: certificates.PrivateKey,
|
||||
Certificate: certificates.Certificate,
|
||||
IssuerCertificate: certificates.IssuerCertificate,
|
||||
CSR: certificates.CSR,
|
||||
}
|
||||
name := strings.Join(payload.ServerName, "_")
|
||||
saveDir := nginx.GetConfPath("ssl/" + name + "_" + string(payload.KeyType))
|
||||
if _, err = os.Stat(saveDir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(saveDir, 0755)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "mkdir error")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Each certificate comes back with the cert bytes, the bytes of the client's
|
||||
// private key, and a certificate URL. SAVE THESE TO DISK.
|
||||
l.Println("[INFO] [Nginx UI] Writing certificate to disk")
|
||||
err = os.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
|
||||
certificates.Certificate, 0644)
|
||||
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "write fullchain.cer error")
|
||||
return
|
||||
}
|
||||
|
||||
l.Println("[INFO] [Nginx UI] Writing certificate private key to disk")
|
||||
err = os.WriteFile(filepath.Join(saveDir, "private.key"),
|
||||
certificates.PrivateKey, 0644)
|
||||
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "write private.key error")
|
||||
return
|
||||
}
|
||||
}
|
|
@ -6,14 +6,17 @@ import (
|
|||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/0xJacky/Nginx-UI/query"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ConfigPayload struct {
|
||||
ServerName []string `json:"server_name"`
|
||||
ChallengeMethod string `json:"challenge_method"`
|
||||
DNSCredentialID int `json:"dns_credential_id"`
|
||||
ACMEUserID int `json:"acme_user_id"`
|
||||
KeyType certcrypto.KeyType `json:"key_type"`
|
||||
ServerName []string `json:"server_name"`
|
||||
ChallengeMethod string `json:"challenge_method"`
|
||||
DNSCredentialID int `json:"dns_credential_id"`
|
||||
ACMEUserID int `json:"acme_user_id"`
|
||||
KeyType certcrypto.KeyType `json:"key_type"`
|
||||
Resource *model.CertificateResource `json:"resource,omitempty"`
|
||||
NotBefore time.Time
|
||||
}
|
||||
|
||||
func (c *ConfigPayload) GetACMEUser() (user *model.AcmeUser, err error) {
|
||||
|
|
36
internal/cert/renew.go
Normal file
36
internal/cert/renew.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package cert
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/pkg/errors"
|
||||
"log"
|
||||
)
|
||||
|
||||
func renew(payload *ConfigPayload, client *lego.Client, l *log.Logger, errChan chan error) {
|
||||
if payload.Resource == nil {
|
||||
errChan <- errors.New("resource is nil")
|
||||
return
|
||||
}
|
||||
|
||||
options := &certificate.RenewOptions{
|
||||
Bundle: true,
|
||||
}
|
||||
|
||||
cert, err := client.Certificate.RenewWithOptions(payload.Resource.GetResource(), options)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "renew cert error")
|
||||
return
|
||||
}
|
||||
|
||||
payload.Resource = &model.CertificateResource{
|
||||
Resource: cert,
|
||||
PrivateKey: cert.PrivateKey,
|
||||
Certificate: cert.Certificate,
|
||||
IssuerCertificate: cert.IssuerCertificate,
|
||||
CSR: cert.CSR,
|
||||
}
|
||||
|
||||
l.Println("[INFO] [Nginx UI] Certificate renewed successfully")
|
||||
}
|
|
@ -18,7 +18,7 @@ func init() {
|
|||
var logrotateJob *gocron.Job
|
||||
|
||||
func InitCronJobs() {
|
||||
job, err := s.Every(30).Minute().SingletonMode().Do(cert.AutoObtain)
|
||||
job, err := s.Every(30).Minute().SingletonMode().Do(cert.AutoCert)
|
||||
|
||||
if err != nil {
|
||||
logger.Fatalf("AutoCert Job: %v, Err: %v\n", job, err)
|
||||
|
|
|
@ -90,7 +90,7 @@ func InitJsExtensionType() {
|
|||
|
||||
func InitCronJobs() {
|
||||
s := gocron.NewScheduler(time.UTC)
|
||||
job, err := s.Every(30).Minute().SingletonMode().Do(cert.AutoObtain)
|
||||
job, err := s.Every(30).Minute().SingletonMode().Do(cert.AutoCert)
|
||||
|
||||
if err != nil {
|
||||
logger.Fatalf("AutoCert Job: %v, Err: %v\n", job, err)
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"github.com/0xJacky/Nginx-UI/internal/helper"
|
||||
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/lib/pq"
|
||||
"os"
|
||||
)
|
||||
|
@ -17,21 +18,30 @@ const (
|
|||
|
||||
type CertDomains []string
|
||||
|
||||
type CertificateResource struct {
|
||||
*certificate.Resource
|
||||
PrivateKey []byte `json:"private_key"`
|
||||
Certificate []byte `json:"certificate"`
|
||||
IssuerCertificate []byte `json:"issuerCertificate"`
|
||||
CSR []byte `json:"csr"`
|
||||
}
|
||||
|
||||
type Cert struct {
|
||||
Model
|
||||
Name string `json:"name"`
|
||||
Domains pq.StringArray `json:"domains" gorm:"type:text[]"`
|
||||
Filename string `json:"filename"`
|
||||
SSLCertificatePath string `json:"ssl_certificate_path"`
|
||||
SSLCertificateKeyPath string `json:"ssl_certificate_key_path"`
|
||||
AutoCert int `json:"auto_cert"`
|
||||
ChallengeMethod string `json:"challenge_method"`
|
||||
DnsCredentialID int `json:"dns_credential_id"`
|
||||
DnsCredential *DnsCredential `json:"dns_credential,omitempty"`
|
||||
ACMEUserID int `json:"acme_user_id"`
|
||||
ACMEUser *AcmeUser `json:"acme_user,omitempty"`
|
||||
KeyType certcrypto.KeyType `json:"key_type"`
|
||||
Log string `json:"log"`
|
||||
Name string `json:"name"`
|
||||
Domains pq.StringArray `json:"domains" gorm:"type:text[]"`
|
||||
Filename string `json:"filename"`
|
||||
SSLCertificatePath string `json:"ssl_certificate_path"`
|
||||
SSLCertificateKeyPath string `json:"ssl_certificate_key_path"`
|
||||
AutoCert int `json:"auto_cert"`
|
||||
ChallengeMethod string `json:"challenge_method"`
|
||||
DnsCredentialID int `json:"dns_credential_id"`
|
||||
DnsCredential *DnsCredential `json:"dns_credential,omitempty"`
|
||||
ACMEUserID int `json:"acme_user_id"`
|
||||
ACMEUser *AcmeUser `json:"acme_user,omitempty"`
|
||||
KeyType certcrypto.KeyType `json:"key_type"`
|
||||
Log string `json:"log"`
|
||||
Resource *CertificateResource `json:"-" gorm:"serializer:json"`
|
||||
}
|
||||
|
||||
func FirstCert(confName string) (c Cert, err error) {
|
||||
|
@ -99,3 +109,15 @@ func (c *Cert) Remove() error {
|
|||
func (c *Cert) GetKeyType() certcrypto.KeyType {
|
||||
return helper.GetKeyType(c.KeyType)
|
||||
}
|
||||
|
||||
func (c *CertificateResource) GetResource() certificate.Resource {
|
||||
return certificate.Resource{
|
||||
Domain: c.Resource.Domain,
|
||||
CertURL: c.Resource.CertURL,
|
||||
CertStableURL: c.Resource.CertStableURL,
|
||||
PrivateKey: c.PrivateKey,
|
||||
Certificate: c.Certificate,
|
||||
IssuerCertificate: c.IssuerCertificate,
|
||||
CSR: c.CSR,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue