mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15: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
|
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)
|
logChan := make(chan string, 1)
|
||||||
errChan := make(chan error, 1)
|
errChan := make(chan error, 1)
|
||||||
|
|
||||||
|
@ -126,6 +134,7 @@ func IssueCert(c *gin.Context) {
|
||||||
KeyType: payload.KeyType,
|
KeyType: payload.KeyType,
|
||||||
ChallengeMethod: payload.ChallengeMethod,
|
ChallengeMethod: payload.ChallengeMethod,
|
||||||
DnsCredentialID: payload.DNSCredentialID,
|
DnsCredentialID: payload.DNSCredentialID,
|
||||||
|
Resource: payload.Resource,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Binary file not shown.
|
@ -442,7 +442,7 @@ msgstr "DNS 凭证"
|
||||||
#: src/views/certificate/DNSChallenge.vue:74
|
#: src/views/certificate/DNSChallenge.vue:74
|
||||||
#: src/views/domain/cert/components/DNSChallenge.vue:95
|
#: src/views/domain/cert/components/DNSChallenge.vue:95
|
||||||
msgid "DNS Provider"
|
msgid "DNS Provider"
|
||||||
msgstr "DNS供应商"
|
msgstr "DNS 提供商"
|
||||||
|
|
||||||
#: src/views/domain/cert/components/AutoCertStepOne.vue:104
|
#: src/views/domain/cert/components/AutoCertStepOne.vue:104
|
||||||
msgid "DNS01"
|
msgid "DNS01"
|
||||||
|
@ -1181,8 +1181,8 @@ msgid ""
|
||||||
"Please first add credentials in Certification > DNS Credentials, and then "
|
"Please first add credentials in Certification > DNS Credentials, and then "
|
||||||
"select one of the credentialsbelow to request the API of the DNS provider."
|
"select one of the credentialsbelow to request the API of the DNS provider."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"请首先在 “证书”> “DNS凭证” 中添加凭证,然后在下方选择一个凭证,请求DNS提供商"
|
"请首先在 “证书”> “DNS 凭证” 中添加凭证,然后在下方选择一个凭证,请求 DNS 提供"
|
||||||
"的API。"
|
"商的 API。"
|
||||||
|
|
||||||
#: src/views/domain/components/SiteDuplicate.vue:40
|
#: src/views/domain/components/SiteDuplicate.vue:40
|
||||||
#: src/views/stream/components/StreamDuplicate.vue:40
|
#: src/views/stream/components/StreamDuplicate.vue:40
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AutoObtain() {
|
func AutoCert() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
|
@ -22,12 +22,12 @@ func AutoObtain() {
|
||||||
logger.Info("AutoCert Worker Started")
|
logger.Info("AutoCert Worker Started")
|
||||||
autoCertList := model.GetAutoCertList()
|
autoCertList := model.GetAutoCertList()
|
||||||
for _, certModel := range autoCertList {
|
for _, certModel := range autoCertList {
|
||||||
renew(certModel)
|
autoCert(certModel)
|
||||||
}
|
}
|
||||||
logger.Info("AutoCert Worker End")
|
logger.Info("AutoCert Worker End")
|
||||||
}
|
}
|
||||||
|
|
||||||
func renew(certModel *model.Cert) {
|
func autoCert(certModel *model.Cert) {
|
||||||
confName := certModel.Filename
|
confName := certModel.Filename
|
||||||
|
|
||||||
log := &Logger{}
|
log := &Logger{}
|
||||||
|
@ -75,6 +75,14 @@ func renew(certModel *model.Cert) {
|
||||||
ChallengeMethod: certModel.ChallengeMethod,
|
ChallengeMethod: certModel.ChallengeMethod,
|
||||||
DNSCredentialID: certModel.DnsCredentialID,
|
DNSCredentialID: certModel.DnsCredentialID,
|
||||||
KeyType: certModel.GetKeyType(),
|
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
|
// errChan will be closed inside IssueCert
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
||||||
"github.com/0xJacky/Nginx-UI/query"
|
"github.com/0xJacky/Nginx-UI/query"
|
||||||
"github.com/0xJacky/Nginx-UI/settings"
|
"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/challenge/http01"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
legolog "github.com/go-acme/lego/v4/log"
|
legolog "github.com/go-acme/lego/v4/log"
|
||||||
|
@ -16,8 +15,6 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,15 +41,13 @@ func IssueCert(payload *ConfigPayload, logChan chan string, errChan chan error)
|
||||||
// Hijack the (logger) of lego
|
// Hijack the (logger) of lego
|
||||||
legolog.Logger = l
|
legolog.Logger = l
|
||||||
|
|
||||||
domain := payload.ServerName
|
|
||||||
|
|
||||||
l.Println("[INFO] [Nginx UI] Preparing lego configurations")
|
l.Println("[INFO] [Nginx UI] Preparing lego configurations")
|
||||||
user, err := payload.GetACMEUser()
|
user, err := payload.GetACMEUser()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- errors.Wrap(err, "issue cert get acme user error")
|
errChan <- errors.Wrap(err, "issue cert get acme user error")
|
||||||
return
|
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
|
// Start a goroutine to fetch and process logs from channel
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -134,45 +129,11 @@ func IssueCert(payload *ConfigPayload, logChan chan string, errChan chan error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
request := certificate.ObtainRequest{
|
if time.Now().Sub(payload.NotBefore).Hours()/24 <= 21 &&
|
||||||
Domains: domain,
|
payload.Resource != nil && payload.Resource.Certificate != nil {
|
||||||
Bundle: true,
|
renew(payload, client, l, errChan)
|
||||||
}
|
} else {
|
||||||
|
obtain(payload, client, l, errChan)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Println("[INFO] [Nginx UI] Reloading nginx")
|
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/model"
|
||||||
"github.com/0xJacky/Nginx-UI/query"
|
"github.com/0xJacky/Nginx-UI/query"
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigPayload struct {
|
type ConfigPayload struct {
|
||||||
ServerName []string `json:"server_name"`
|
ServerName []string `json:"server_name"`
|
||||||
ChallengeMethod string `json:"challenge_method"`
|
ChallengeMethod string `json:"challenge_method"`
|
||||||
DNSCredentialID int `json:"dns_credential_id"`
|
DNSCredentialID int `json:"dns_credential_id"`
|
||||||
ACMEUserID int `json:"acme_user_id"`
|
ACMEUserID int `json:"acme_user_id"`
|
||||||
KeyType certcrypto.KeyType `json:"key_type"`
|
KeyType certcrypto.KeyType `json:"key_type"`
|
||||||
|
Resource *model.CertificateResource `json:"resource,omitempty"`
|
||||||
|
NotBefore time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfigPayload) GetACMEUser() (user *model.AcmeUser, err error) {
|
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
|
var logrotateJob *gocron.Job
|
||||||
|
|
||||||
func InitCronJobs() {
|
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 {
|
if err != nil {
|
||||||
logger.Fatalf("AutoCert Job: %v, Err: %v\n", job, err)
|
logger.Fatalf("AutoCert Job: %v, Err: %v\n", job, err)
|
||||||
|
|
|
@ -90,7 +90,7 @@ func InitJsExtensionType() {
|
||||||
|
|
||||||
func InitCronJobs() {
|
func InitCronJobs() {
|
||||||
s := gocron.NewScheduler(time.UTC)
|
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 {
|
if err != nil {
|
||||||
logger.Fatalf("AutoCert Job: %v, Err: %v\n", job, err)
|
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/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/go-acme/lego/v4/certificate"
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
@ -17,21 +18,30 @@ const (
|
||||||
|
|
||||||
type CertDomains []string
|
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 {
|
type Cert struct {
|
||||||
Model
|
Model
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Domains pq.StringArray `json:"domains" gorm:"type:text[]"`
|
Domains pq.StringArray `json:"domains" gorm:"type:text[]"`
|
||||||
Filename string `json:"filename"`
|
Filename string `json:"filename"`
|
||||||
SSLCertificatePath string `json:"ssl_certificate_path"`
|
SSLCertificatePath string `json:"ssl_certificate_path"`
|
||||||
SSLCertificateKeyPath string `json:"ssl_certificate_key_path"`
|
SSLCertificateKeyPath string `json:"ssl_certificate_key_path"`
|
||||||
AutoCert int `json:"auto_cert"`
|
AutoCert int `json:"auto_cert"`
|
||||||
ChallengeMethod string `json:"challenge_method"`
|
ChallengeMethod string `json:"challenge_method"`
|
||||||
DnsCredentialID int `json:"dns_credential_id"`
|
DnsCredentialID int `json:"dns_credential_id"`
|
||||||
DnsCredential *DnsCredential `json:"dns_credential,omitempty"`
|
DnsCredential *DnsCredential `json:"dns_credential,omitempty"`
|
||||||
ACMEUserID int `json:"acme_user_id"`
|
ACMEUserID int `json:"acme_user_id"`
|
||||||
ACMEUser *AcmeUser `json:"acme_user,omitempty"`
|
ACMEUser *AcmeUser `json:"acme_user,omitempty"`
|
||||||
KeyType certcrypto.KeyType `json:"key_type"`
|
KeyType certcrypto.KeyType `json:"key_type"`
|
||||||
Log string `json:"log"`
|
Log string `json:"log"`
|
||||||
|
Resource *CertificateResource `json:"-" gorm:"serializer:json"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func FirstCert(confName string) (c Cert, err error) {
|
func FirstCert(confName string) (c Cert, err error) {
|
||||||
|
@ -99,3 +109,15 @@ func (c *Cert) Remove() error {
|
||||||
func (c *Cert) GetKeyType() certcrypto.KeyType {
|
func (c *Cert) GetKeyType() certcrypto.KeyType {
|
||||||
return helper.GetKeyType(c.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