feat: using renewal api to renew certificate #319

This commit is contained in:
Jacky 2024-04-30 19:48:48 +08:00
parent e3876cffaf
commit e16b077d20
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
11 changed files with 173 additions and 71 deletions

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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
View 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
}
}

View file

@ -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
View 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")
}

View file

@ -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)

View file

@ -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)

View file

@ -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,
}
}