mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 10:25:52 +02:00
feat: revoke certificate #293
This commit is contained in:
parent
52a23750b5
commit
c073801794
35 changed files with 1785 additions and 550 deletions
|
@ -79,6 +79,7 @@ func autoCert(certModel *model.Cert) {
|
|||
NotBefore: certInfo.NotBefore,
|
||||
MustStaple: certModel.MustStaple,
|
||||
LegoDisableCNAMESupport: certModel.LegoDisableCNAMESupport,
|
||||
RevokeOld: certModel.RevokeOld,
|
||||
}
|
||||
|
||||
if certModel.Resource != nil {
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/0xJacky/Nginx-UI/internal/cert/dns"
|
||||
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
||||
"github.com/0xJacky/Nginx-UI/internal/transport"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/0xJacky/Nginx-UI/query"
|
||||
"github.com/0xJacky/Nginx-UI/settings"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
|
@ -162,6 +163,20 @@ func IssueCert(payload *ConfigPayload, logChan chan string, errChan chan error)
|
|||
}()
|
||||
}
|
||||
|
||||
// Backup current certificate and key if RevokeOld is true
|
||||
var oldResource *model.CertificateResource
|
||||
|
||||
if payload.RevokeOld && payload.Resource != nil && payload.Resource.Certificate != nil {
|
||||
l.Println("[INFO] [Nginx UI] Backing up current certificate for later revocation")
|
||||
|
||||
// Save a copy of the old certificate and key
|
||||
oldResource = &model.CertificateResource{
|
||||
Resource: payload.Resource.Resource,
|
||||
Certificate: payload.Resource.Certificate,
|
||||
PrivateKey: payload.Resource.PrivateKey,
|
||||
}
|
||||
}
|
||||
|
||||
if time.Now().Sub(payload.NotBefore).Hours()/24 <= 21 &&
|
||||
payload.Resource != nil && payload.Resource.Certificate != nil {
|
||||
renew(payload, client, l, errChan)
|
||||
|
@ -180,6 +195,25 @@ func IssueCert(payload *ConfigPayload, logChan chan string, errChan chan error)
|
|||
ReloadServerTLSCertificate()
|
||||
}
|
||||
|
||||
// Revoke old certificate if requested and we have a backup
|
||||
if payload.RevokeOld && oldResource != nil && len(oldResource.Certificate) > 0 {
|
||||
l.Println("[INFO] [Nginx UI] Revoking old certificate")
|
||||
|
||||
// Create a payload for revocation using old certificate
|
||||
revokePayload := &ConfigPayload{
|
||||
CertID: payload.CertID,
|
||||
ServerName: payload.ServerName,
|
||||
ChallengeMethod: payload.ChallengeMethod,
|
||||
DNSCredentialID: payload.DNSCredentialID,
|
||||
ACMEUserID: payload.ACMEUserID,
|
||||
KeyType: payload.KeyType,
|
||||
Resource: oldResource,
|
||||
}
|
||||
|
||||
// Revoke the old certificate
|
||||
revoke(revokePayload, client, l, errChan)
|
||||
}
|
||||
|
||||
// Wait log to be written
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
|
@ -1,6 +1,12 @@
|
|||
package cert
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/0xJacky/Nginx-UI/internal/helper"
|
||||
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
|
@ -8,11 +14,6 @@ import (
|
|||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uozi-tech/cosy/logger"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ConfigPayload struct {
|
||||
|
@ -29,6 +30,7 @@ type ConfigPayload struct {
|
|||
CertificateDir string `json:"-"`
|
||||
SSLCertificatePath string `json:"-"`
|
||||
SSLCertificateKeyPath string `json:"-"`
|
||||
RevokeOld bool `json:"revoke_old"`
|
||||
}
|
||||
|
||||
func (c *ConfigPayload) GetACMEUser() (user *model.AcmeUser, err error) {
|
||||
|
@ -110,6 +112,7 @@ func (c *ConfigPayload) WriteFile(l *log.Logger, errChan chan error) {
|
|||
db.Where("id = ?", c.CertID).Updates(&model.Cert{
|
||||
SSLCertificatePath: c.GetCertificatePath(),
|
||||
SSLCertificateKeyPath: c.GetCertificateKeyPath(),
|
||||
Resource: c.Resource,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
105
internal/cert/revoke.go
Normal file
105
internal/cert/revoke.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package cert
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/0xJacky/Nginx-UI/internal/transport"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
legolog "github.com/go-acme/lego/v4/log"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uozi-tech/cosy/logger"
|
||||
cSettings "github.com/uozi-tech/cosy/settings"
|
||||
)
|
||||
|
||||
// RevokeCert revokes a certificate and provides log messages through channels
|
||||
func RevokeCert(payload *ConfigPayload, logChan chan string, errChan chan error) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Initialize a channel writer to receive logs
|
||||
cw := NewChannelWriter()
|
||||
defer close(errChan)
|
||||
defer close(cw.Ch)
|
||||
|
||||
// Initialize a logger
|
||||
l := log.New(os.Stderr, "", log.LstdFlags)
|
||||
l.SetOutput(cw)
|
||||
|
||||
// Hijack the logger of lego
|
||||
oldLogger := legolog.Logger
|
||||
legolog.Logger = l
|
||||
// Restore the original logger
|
||||
defer func() {
|
||||
legolog.Logger = oldLogger
|
||||
}()
|
||||
|
||||
// Start a goroutine to fetch and process logs from channel
|
||||
go func() {
|
||||
for msg := range cw.Ch {
|
||||
logChan <- string(msg)
|
||||
}
|
||||
}()
|
||||
|
||||
// Create client for communication with CA server
|
||||
l.Println("[INFO] [Nginx UI] Preparing for certificate revocation")
|
||||
user, err := payload.GetACMEUser()
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "get ACME user error")
|
||||
return
|
||||
}
|
||||
|
||||
config := lego.NewConfig(user)
|
||||
config.CADirURL = user.CADir
|
||||
|
||||
// Skip TLS check if proxy is configured
|
||||
if config.HTTPClient != nil {
|
||||
t, err := transport.NewTransport(
|
||||
transport.WithProxy(user.Proxy))
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "create transport error")
|
||||
return
|
||||
}
|
||||
config.HTTPClient.Transport = t
|
||||
}
|
||||
|
||||
config.Certificate.KeyType = payload.GetKeyType()
|
||||
|
||||
// Create the client
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "create client error")
|
||||
return
|
||||
}
|
||||
|
||||
revoke(payload, client, l, errChan)
|
||||
|
||||
// If the revoked certificate was used for the server itself, reload server TLS certificate
|
||||
if payload.GetCertificatePath() == cSettings.ServerSettings.SSLCert &&
|
||||
payload.GetCertificateKeyPath() == cSettings.ServerSettings.SSLKey {
|
||||
l.Println("[INFO] [Nginx UI] Certificate was used for server, reloading server TLS certificate")
|
||||
ReloadServerTLSCertificate()
|
||||
}
|
||||
|
||||
l.Println("[INFO] [Nginx UI] Revocation completed")
|
||||
|
||||
// Wait for logs to be written
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
// revoke implements the internal certificate revocation logic
|
||||
func revoke(payload *ConfigPayload, client *lego.Client, l *log.Logger, errChan chan error) {
|
||||
l.Println("[INFO] [Nginx UI] Revoking certificate")
|
||||
err := client.Certificate.Revoke(payload.Resource.Certificate)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "revoke certificate error")
|
||||
return
|
||||
}
|
||||
|
||||
l.Println("[INFO] [Nginx UI] Certificate successfully revoked")
|
||||
return
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue