mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
refactor(cert): introducing new management page
1. User can now view the latest renew logs of the certain certificate. 2. Add manually renew button in certificate modify page for managed certificate (auto cert)
This commit is contained in:
parent
7c47f08a72
commit
ac68fd05c9
36 changed files with 1908 additions and 1286 deletions
|
@ -1,61 +1,13 @@
|
|||
package cert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/0xJacky/Nginx-UI/internal/logger"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/pkg/errors"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func handleIssueCertLogChan(logChan chan string) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
for logString := range logChan {
|
||||
logger.Info("Auto Cert", logString)
|
||||
}
|
||||
}
|
||||
|
||||
type AutoCertErrorLog struct {
|
||||
buffer []string
|
||||
cert *model.Cert
|
||||
}
|
||||
|
||||
func (t *AutoCertErrorLog) SetCertModel(cert *model.Cert) {
|
||||
t.cert = cert
|
||||
}
|
||||
|
||||
func (t *AutoCertErrorLog) Push(text string, err error) {
|
||||
t.buffer = append(t.buffer, text+" "+err.Error())
|
||||
logger.Error("AutoCert", text, err)
|
||||
}
|
||||
|
||||
func (t *AutoCertErrorLog) Exit(text string, err error) {
|
||||
t.buffer = append(t.buffer, text+" "+err.Error())
|
||||
logger.Error("AutoCert", text, err)
|
||||
|
||||
if t.cert == nil {
|
||||
return
|
||||
}
|
||||
|
||||
_ = t.cert.Updates(&model.Cert{
|
||||
Log: t.ToString(),
|
||||
})
|
||||
}
|
||||
|
||||
func (t *AutoCertErrorLog) ToString() (content string) {
|
||||
|
||||
for _, v := range t.buffer {
|
||||
content += fmt.Sprintf("[Error] %s\n", v)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func AutoObtain() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
|
@ -65,64 +17,64 @@ func AutoObtain() {
|
|||
logger.Info("AutoCert Worker Started")
|
||||
autoCertList := model.GetAutoCertList()
|
||||
for _, certModel := range autoCertList {
|
||||
confName := certModel.Filename
|
||||
|
||||
errLog := &AutoCertErrorLog{}
|
||||
errLog.SetCertModel(certModel)
|
||||
|
||||
if len(certModel.Filename) == 0 {
|
||||
errLog.Exit("", errors.New("filename is empty"))
|
||||
continue
|
||||
}
|
||||
|
||||
if len(certModel.Domains) == 0 {
|
||||
errLog.Exit(confName, errors.New("domains list is empty, "+
|
||||
"try to reopen auto-cert for this config:"+confName))
|
||||
continue
|
||||
}
|
||||
|
||||
if certModel.SSLCertificatePath != "" {
|
||||
cert, err := GetCertInfo(certModel.SSLCertificatePath)
|
||||
if err != nil {
|
||||
errLog.Push("get cert info", err)
|
||||
// Get certificate info error, ignore this domain
|
||||
continue
|
||||
}
|
||||
// every week
|
||||
if time.Now().Sub(cert.NotBefore).Hours()/24 < 7 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// after 1 mo, reissue certificate
|
||||
logChan := make(chan string, 1)
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
// support SAN certification
|
||||
payload := &ConfigPayload{
|
||||
ServerName: certModel.Domains,
|
||||
ChallengeMethod: certModel.ChallengeMethod,
|
||||
DNSCredentialID: certModel.DnsCredentialID,
|
||||
}
|
||||
|
||||
// logChan and errChan should be closed inside IssueCert
|
||||
go IssueCert(payload, logChan, errChan)
|
||||
|
||||
go handleIssueCertLogChan(logChan)
|
||||
|
||||
// block, unless errChan closed
|
||||
for err := range errChan {
|
||||
errLog.Push("issue cert", err)
|
||||
}
|
||||
|
||||
logStr := errLog.ToString()
|
||||
if logStr != "" {
|
||||
// store error log to db
|
||||
_ = certModel.Updates(&model.Cert{
|
||||
Log: errLog.ToString(),
|
||||
})
|
||||
} else {
|
||||
certModel.ClearLog()
|
||||
}
|
||||
certModel := certModel
|
||||
renew(certModel)
|
||||
}
|
||||
logger.Info("AutoCert Worker End")
|
||||
}
|
||||
|
||||
func renew(certModel *model.Cert) {
|
||||
confName := certModel.Filename
|
||||
|
||||
log := &Logger{}
|
||||
log.SetCertModel(certModel)
|
||||
defer log.Exit()
|
||||
|
||||
if len(certModel.Filename) == 0 {
|
||||
log.Error(errors.New("filename is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
if len(certModel.Domains) == 0 {
|
||||
log.Error(errors.New("domains list is empty, " +
|
||||
"try to reopen auto-cert for this config:" + confName))
|
||||
return
|
||||
}
|
||||
|
||||
if certModel.SSLCertificatePath != "" {
|
||||
cert, err := GetCertInfo(certModel.SSLCertificatePath)
|
||||
if err != nil {
|
||||
// Get certificate info error, ignore this certificate
|
||||
log.Error(errors.Wrap(err, "get certificate info error"))
|
||||
return
|
||||
}
|
||||
if time.Now().Sub(cert.NotBefore).Hours()/24 < 7 {
|
||||
// not between 1 week, ignore this certificate
|
||||
return
|
||||
}
|
||||
}
|
||||
// after 1 mo, reissue certificate
|
||||
logChan := make(chan string, 1)
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
// support SAN certification
|
||||
payload := &ConfigPayload{
|
||||
ServerName: certModel.Domains,
|
||||
ChallengeMethod: certModel.ChallengeMethod,
|
||||
DNSCredentialID: certModel.DnsCredentialID,
|
||||
}
|
||||
|
||||
// errChan will be closed inside IssueCert
|
||||
go IssueCert(payload, logChan, errChan)
|
||||
|
||||
go func() {
|
||||
for logString := range logChan {
|
||||
log.Info(strings.TrimSpace(logString))
|
||||
}
|
||||
}()
|
||||
|
||||
// block, unless errChan closed
|
||||
for err := range errChan {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,248 +1,216 @@
|
|||
package cert
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"github.com/0xJacky/Nginx-UI/internal/cert/dns"
|
||||
"github.com/0xJacky/Nginx-UI/internal/logger"
|
||||
"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/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/challenge/http01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
lego_log "github.com/go-acme/lego/v4/log"
|
||||
dns_providers "github.com/go-acme/lego/v4/providers/dns"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"github.com/0xJacky/Nginx-UI/internal/cert/dns"
|
||||
"github.com/0xJacky/Nginx-UI/internal/logger"
|
||||
"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/certcrypto"
|
||||
"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"
|
||||
dnsproviders "github.com/go-acme/lego/v4/providers/dns"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"github.com/pkg/errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
HTTP01 = "http01"
|
||||
DNS01 = "dns01"
|
||||
HTTP01 = "http01"
|
||||
DNS01 = "dns01"
|
||||
)
|
||||
|
||||
// MyUser You'll need a user or account type that implements acme.User
|
||||
type MyUser struct {
|
||||
Email string
|
||||
Registration *registration.Resource
|
||||
Key crypto.PrivateKey
|
||||
}
|
||||
|
||||
func (u *MyUser) GetEmail() string {
|
||||
return u.Email
|
||||
}
|
||||
|
||||
func (u *MyUser) GetRegistration() *registration.Resource {
|
||||
return u.Registration
|
||||
}
|
||||
|
||||
func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
|
||||
return u.Key
|
||||
}
|
||||
|
||||
type ConfigPayload struct {
|
||||
ServerName []string `json:"server_name"`
|
||||
ChallengeMethod string `json:"challenge_method"`
|
||||
DNSCredentialID int `json:"dns_credential_id"`
|
||||
}
|
||||
|
||||
type channelWriter struct {
|
||||
ch chan []byte
|
||||
}
|
||||
|
||||
func (cw *channelWriter) Write(p []byte) (n int, err error) {
|
||||
n = len(p)
|
||||
temp := make([]byte, n)
|
||||
copy(temp, p)
|
||||
cw.ch <- temp
|
||||
return n, nil
|
||||
ServerName []string `json:"server_name"`
|
||||
ChallengeMethod string `json:"challenge_method"`
|
||||
DNSCredentialID int `json:"dns_credential_id"`
|
||||
}
|
||||
|
||||
func IssueCert(payload *ConfigPayload, logChan chan string, errChan chan error) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
defer close(logChan)
|
||||
defer close(errChan)
|
||||
// initial a channelWriter to receive logs
|
||||
cw := NewChannelWriter()
|
||||
defer close(errChan)
|
||||
|
||||
// Use a channel to receive lego log
|
||||
logChannel := make(chan []byte, 1024)
|
||||
defer close(logChannel)
|
||||
// initial a logger
|
||||
l := log.New(os.Stderr, "", log.LstdFlags)
|
||||
l.SetOutput(cw)
|
||||
|
||||
domain := payload.ServerName
|
||||
// Hijack the (logger) of lego
|
||||
legolog.Logger = l
|
||||
|
||||
// Create a user. New accounts need an email and private key to start.
|
||||
logChan <- "Generating private key for registering account"
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "issue cert generate key error")
|
||||
return
|
||||
}
|
||||
domain := payload.ServerName
|
||||
|
||||
logChan <- "Preparing lego configurations"
|
||||
myUser := MyUser{
|
||||
Email: settings.ServerSettings.Email,
|
||||
Key: privateKey,
|
||||
}
|
||||
// Create a user. New accounts need an email and private key to start.
|
||||
l.Println("[INFO] [Nginx UI] Generating private key for registering account")
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "issue cert generate key error")
|
||||
return
|
||||
}
|
||||
|
||||
// Hijack the (logger) of lego
|
||||
cw := &channelWriter{ch: logChannel}
|
||||
multiWriter := io.MultiWriter(os.Stderr, cw)
|
||||
l := log.New(os.Stderr, "", log.LstdFlags)
|
||||
l.SetOutput(multiWriter)
|
||||
lego_log.Logger = l
|
||||
l.Println("[INFO] [Nginx UI] Preparing lego configurations")
|
||||
user := User{
|
||||
Email: settings.ServerSettings.Email,
|
||||
Key: privateKey,
|
||||
}
|
||||
|
||||
// Start a goroutine to fetch and process logs from channel
|
||||
go func() {
|
||||
for msg := range logChannel {
|
||||
logChan <- string(msg)
|
||||
}
|
||||
}()
|
||||
// Start a goroutine to fetch and process logs from channel
|
||||
go func() {
|
||||
for msg := range cw.Ch {
|
||||
logChan <- string(msg)
|
||||
}
|
||||
}()
|
||||
|
||||
config := lego.NewConfig(&myUser)
|
||||
config := lego.NewConfig(&user)
|
||||
|
||||
if settings.ServerSettings.Demo {
|
||||
config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
}
|
||||
if settings.ServerSettings.Demo {
|
||||
config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
}
|
||||
|
||||
if settings.ServerSettings.CADir != "" {
|
||||
config.CADirURL = settings.ServerSettings.CADir
|
||||
if config.HTTPClient != nil {
|
||||
config.HTTPClient.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
}
|
||||
}
|
||||
if settings.ServerSettings.CADir != "" {
|
||||
config.CADirURL = settings.ServerSettings.CADir
|
||||
if config.HTTPClient != nil {
|
||||
config.HTTPClient.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.Certificate.KeyType = certcrypto.RSA2048
|
||||
config.Certificate.KeyType = certcrypto.RSA2048
|
||||
|
||||
logChan <- "Creating client facilitates communication with the CA server"
|
||||
// A client facilitates communication with the CA server.
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "issue cert new client error")
|
||||
return
|
||||
}
|
||||
l.Println("[INFO] [Nginx UI] Creating client facilitates communication with the CA server")
|
||||
// A client facilitates communication with the CA server.
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "issue cert new client error")
|
||||
return
|
||||
}
|
||||
|
||||
switch payload.ChallengeMethod {
|
||||
default:
|
||||
fallthrough
|
||||
case HTTP01:
|
||||
logChan <- "Using HTTP01 challenge provider"
|
||||
err = client.Challenge.SetHTTP01Provider(
|
||||
http01.NewProviderServer("",
|
||||
settings.ServerSettings.HTTPChallengePort,
|
||||
),
|
||||
)
|
||||
case DNS01:
|
||||
d := query.DnsCredential
|
||||
dnsCredential, err := d.FirstByID(payload.DNSCredentialID)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "get dns credential error")
|
||||
return
|
||||
}
|
||||
switch payload.ChallengeMethod {
|
||||
default:
|
||||
fallthrough
|
||||
case HTTP01:
|
||||
l.Println("[INFO] [Nginx UI] Setting HTTP01 challenge provider")
|
||||
err = client.Challenge.SetHTTP01Provider(
|
||||
http01.NewProviderServer("",
|
||||
settings.ServerSettings.HTTPChallengePort,
|
||||
),
|
||||
)
|
||||
case DNS01:
|
||||
d := query.DnsCredential
|
||||
dnsCredential, err := d.FirstByID(payload.DNSCredentialID)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "get dns credential error")
|
||||
return
|
||||
}
|
||||
|
||||
logChan <- "Using DNS01 challenge provider"
|
||||
code := dnsCredential.Config.Code
|
||||
pConfig, ok := dns.GetProvider(code)
|
||||
l.Println("[INFO] [Nginx UI] Setting DNS01 challenge provider")
|
||||
code := dnsCredential.Config.Code
|
||||
pConfig, ok := dns.GetProvider(code)
|
||||
|
||||
if !ok {
|
||||
errChan <- errors.Wrap(err, "provider not found")
|
||||
}
|
||||
logChan <- "Setting environment variables"
|
||||
if dnsCredential.Config.Configuration != nil {
|
||||
err = pConfig.SetEnv(*dnsCredential.Config.Configuration)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
defer func() {
|
||||
logChan <- "Cleaning environment variables"
|
||||
pConfig.CleanEnv()
|
||||
}()
|
||||
provider, err := dns_providers.NewDNSChallengeProviderByName(code)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
err = client.Challenge.SetDNS01Provider(provider)
|
||||
} else {
|
||||
errChan <- errors.Wrap(err, "environment configuration is empty")
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
errChan <- errors.Wrap(err, "provider not found")
|
||||
}
|
||||
l.Println("[INFO] [Nginx UI] Setting environment variables")
|
||||
if dnsCredential.Config.Configuration != nil {
|
||||
err = pConfig.SetEnv(*dnsCredential.Config.Configuration)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
defer func() {
|
||||
pConfig.CleanEnv()
|
||||
l.Println("[INFO] [Nginx UI] Cleaned environment variables")
|
||||
}()
|
||||
provider, err := dnsproviders.NewDNSChallengeProviderByName(code)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
err = client.Challenge.SetDNS01Provider(provider)
|
||||
} else {
|
||||
errChan <- errors.Wrap(err, "environment configuration is empty")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "fail to challenge")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "challenge error")
|
||||
return
|
||||
}
|
||||
|
||||
// New users will need to register
|
||||
logChan <- "Registering user"
|
||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "fail to register")
|
||||
return
|
||||
}
|
||||
myUser.Registration = reg
|
||||
// New users will need to register
|
||||
l.Println("[INFO] [Nginx UI] Registering user")
|
||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "register error")
|
||||
return
|
||||
}
|
||||
user.Registration = reg
|
||||
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: domain,
|
||||
Bundle: true,
|
||||
}
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: domain,
|
||||
Bundle: true,
|
||||
}
|
||||
|
||||
logChan <- "Obtaining certificate"
|
||||
certificates, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "fail to obtain")
|
||||
return
|
||||
}
|
||||
name := strings.Join(domain, "_")
|
||||
saveDir := nginx.GetConfPath("ssl/" + name)
|
||||
if _, err = os.Stat(saveDir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(saveDir, 0755)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "fail to mkdir")
|
||||
return
|
||||
}
|
||||
}
|
||||
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)
|
||||
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.
|
||||
logChan <- "Writing certificate to disk"
|
||||
err = os.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
|
||||
certificates.Certificate, 0644)
|
||||
// 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, "error issue cert write fullchain.cer")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "write fullchain.cer error")
|
||||
return
|
||||
}
|
||||
|
||||
logChan <- "Writing certificate private key to disk"
|
||||
err = os.WriteFile(filepath.Join(saveDir, "private.key"),
|
||||
certificates.PrivateKey, 0644)
|
||||
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, "fail to write key")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "write private.key error")
|
||||
return
|
||||
}
|
||||
|
||||
logChan <- "Reloading nginx"
|
||||
l.Println("[INFO] [Nginx UI] Reloading nginx")
|
||||
|
||||
nginx.Reload()
|
||||
nginx.Reload()
|
||||
|
||||
logChan <- "Finished"
|
||||
l.Println("[INFO] [Nginx UI] Finished")
|
||||
|
||||
// Wait log to be written
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
|
|
@ -5,9 +5,17 @@ import (
|
|||
"encoding/pem"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetCertInfo(sslCertificatePath string) (cert *x509.Certificate, err error) {
|
||||
type Info struct {
|
||||
SubjectName string `json:"subject_name"`
|
||||
IssuerName string `json:"issuer_name"`
|
||||
NotAfter time.Time `json:"not_after"`
|
||||
NotBefore time.Time `json:"not_before"`
|
||||
}
|
||||
|
||||
func GetCertInfo(sslCertificatePath string) (info *Info, err error) {
|
||||
certData, err := os.ReadFile(sslCertificatePath)
|
||||
|
||||
if err != nil {
|
||||
|
@ -22,12 +30,19 @@ func GetCertInfo(sslCertificatePath string) (cert *x509.Certificate, err error)
|
|||
return
|
||||
}
|
||||
|
||||
cert, err = x509.ParseCertificate(block.Bytes)
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "certificate parsing error")
|
||||
return
|
||||
}
|
||||
|
||||
info = &Info{
|
||||
SubjectName: cert.Subject.CommonName,
|
||||
IssuerName: cert.Issuer.CommonName,
|
||||
NotAfter: cert.NotAfter,
|
||||
NotBefore: cert.NotBefore,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
43
internal/cert/issue.go
Normal file
43
internal/cert/issue.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package cert
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
)
|
||||
|
||||
type ChannelWriter struct {
|
||||
Ch chan []byte
|
||||
}
|
||||
|
||||
func NewChannelWriter() *ChannelWriter {
|
||||
return &ChannelWriter{
|
||||
Ch: make(chan []byte, 1024),
|
||||
}
|
||||
}
|
||||
|
||||
func (cw *ChannelWriter) Write(p []byte) (n int, err error) {
|
||||
n = len(p)
|
||||
temp := make([]byte, n)
|
||||
copy(temp, p)
|
||||
cw.Ch <- temp
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// User You'll need a user or account type that implements acme.User
|
||||
type User struct {
|
||||
Email string
|
||||
Registration *registration.Resource
|
||||
Key crypto.PrivateKey
|
||||
}
|
||||
|
||||
func (u *User) GetEmail() string {
|
||||
return u.Email
|
||||
}
|
||||
|
||||
func (u *User) GetRegistration() *registration.Resource {
|
||||
return u.Registration
|
||||
}
|
||||
|
||||
func (u *User) GetPrivateKey() crypto.PrivateKey {
|
||||
return u.Key
|
||||
}
|
46
internal/cert/logger.go
Normal file
46
internal/cert/logger.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package cert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/0xJacky/Nginx-UI/internal/logger"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
buffer []string
|
||||
cert *model.Cert
|
||||
}
|
||||
|
||||
func (t *Logger) SetCertModel(cert *model.Cert) {
|
||||
t.cert = cert
|
||||
}
|
||||
|
||||
func (t *Logger) Info(text string) {
|
||||
t.buffer = append(t.buffer, strings.TrimSpace(text))
|
||||
logger.Info("AutoCert", strings.TrimSpace(text))
|
||||
}
|
||||
|
||||
func (t *Logger) Error(err error) {
|
||||
t.buffer = append(t.buffer, fmt.Sprintf("%s [Error] %s",
|
||||
time.Now().Format("2006/01/02 15:04:05"),
|
||||
strings.TrimSpace(err.Error()),
|
||||
))
|
||||
logger.Error("AutoCert", err)
|
||||
}
|
||||
|
||||
func (t *Logger) Exit() {
|
||||
if t.cert == nil {
|
||||
return
|
||||
}
|
||||
|
||||
_ = t.cert.Updates(&model.Cert{
|
||||
Log: t.ToString(),
|
||||
})
|
||||
}
|
||||
|
||||
func (t *Logger) ToString() (content string) {
|
||||
content = strings.Join(t.buffer, "\n")
|
||||
return
|
||||
}
|
46
internal/cert/write_file.go
Normal file
46
internal/cert/write_file.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package cert
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Content struct {
|
||||
SSLCertificatePath string `json:"ssl_certificate_path" binding:"required"`
|
||||
SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
|
||||
SSLCertificate string `json:"ssl_certificate"`
|
||||
SSLCertificateKey string `json:"ssl_certificate_key"`
|
||||
}
|
||||
|
||||
func (c *Content) WriteFile() (err error) {
|
||||
// MkdirAll creates a directory named path, along with any necessary parents,
|
||||
// and returns nil, or else returns an error.
|
||||
// The permission bits perm (before umask) are used for all directories that MkdirAll creates.
|
||||
// If path is already a directory, MkdirAll does nothing and returns nil.
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(c.SSLCertificatePath), 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(c.SSLCertificateKeyPath), 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if c.SSLCertificate != "" {
|
||||
err = os.WriteFile(c.SSLCertificatePath, []byte(c.SSLCertificate), 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if c.SSLCertificateKey != "" {
|
||||
err = os.WriteFile(c.SSLCertificateKeyPath, []byte(c.SSLCertificateKey), 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue