auto_cert.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. package cert
  2. import (
  3. "runtime"
  4. "strings"
  5. "time"
  6. "github.com/0xJacky/Nginx-UI/internal/notification"
  7. "github.com/0xJacky/Nginx-UI/model"
  8. "github.com/0xJacky/Nginx-UI/settings"
  9. "github.com/pkg/errors"
  10. "github.com/uozi-tech/cosy/logger"
  11. )
  12. func AutoCert() {
  13. defer func() {
  14. if err := recover(); err != nil {
  15. buf := make([]byte, 1024)
  16. runtime.Stack(buf, false)
  17. logger.Errorf("%s\n%s", err, buf)
  18. }
  19. }()
  20. logger.Info("AutoCert Worker Started")
  21. autoCertList := model.GetAutoCertList()
  22. for _, certModel := range autoCertList {
  23. autoCert(certModel)
  24. }
  25. logger.Info("AutoCert Worker End")
  26. }
  27. func autoCert(certModel *model.Cert) {
  28. confName := certModel.Filename
  29. log := NewLogger()
  30. log.SetCertModel(certModel)
  31. defer log.Close()
  32. if len(certModel.Filename) == 0 {
  33. log.Error(ErrCertModelFilenameEmpty)
  34. return
  35. }
  36. if len(certModel.Domains) == 0 {
  37. log.Error(errors.New("domains list is empty, " +
  38. "try to reopen auto-cert for this config:" + confName))
  39. notification.Error("Renew Certificate Error", confName, nil)
  40. return
  41. }
  42. if certModel.SSLCertificatePath == "" {
  43. log.Error(errors.New("ssl certificate path is empty, " +
  44. "try to reopen auto-cert for this config:" + confName))
  45. notification.Error("Renew Certificate Error", confName, nil)
  46. return
  47. }
  48. certInfo, err := GetCertInfo(certModel.SSLCertificatePath)
  49. if err != nil {
  50. // Get certificate info error, ignore this certificate
  51. log.Error(errors.Wrap(err, "get certificate info error"))
  52. notification.Error("Renew Certificate Error", strings.Join(certModel.Domains, ", "), nil)
  53. return
  54. }
  55. // Calculate certificate age (days since NotBefore)
  56. certAge := int(time.Since(certInfo.NotBefore).Hours() / 24)
  57. // Calculate days until expiration
  58. daysUntilExpiration := int(time.Until(certInfo.NotAfter).Hours() / 24)
  59. // Calculate total certificate validity period
  60. totalValidityDays := int(certInfo.NotAfter.Sub(certInfo.NotBefore).Hours() / 24)
  61. renewalInterval := settings.CertSettings.GetCertRenewalInterval()
  62. // For certificates with short validity periods (less than renewal interval),
  63. // use early renewal logic to prevent expiration
  64. if totalValidityDays < renewalInterval {
  65. // Renew when 2/3 of the certificate's lifetime remains
  66. // This provides a safety buffer for short-lived certificates
  67. earlyRenewalThreshold := 2 * totalValidityDays / 3
  68. if daysUntilExpiration > earlyRenewalThreshold {
  69. return
  70. }
  71. // If we reach here, proceed with renewal for short-lived certificate
  72. } else {
  73. // For normal certificates with validity >= renewal interval:
  74. // Skip renewal if certificate age is less than the configured renewal interval
  75. // This ensures we don't renew certificates too frequently
  76. if certAge < renewalInterval {
  77. return
  78. }
  79. }
  80. // after 1 mo, reissue certificate
  81. // support SAN certification
  82. payload := &ConfigPayload{
  83. CertID: certModel.ID,
  84. ServerName: certModel.Domains,
  85. ChallengeMethod: certModel.ChallengeMethod,
  86. DNSCredentialID: certModel.DnsCredentialID,
  87. KeyType: certModel.GetKeyType(),
  88. NotBefore: certInfo.NotBefore,
  89. MustStaple: certModel.MustStaple,
  90. LegoDisableCNAMESupport: certModel.LegoDisableCNAMESupport,
  91. RevokeOld: certModel.RevokeOld,
  92. }
  93. if certModel.Resource != nil {
  94. payload.Resource = &model.CertificateResource{
  95. Resource: certModel.Resource.Resource,
  96. PrivateKey: certModel.Resource.PrivateKey,
  97. Certificate: certModel.Resource.Certificate,
  98. IssuerCertificate: certModel.Resource.IssuerCertificate,
  99. CSR: certModel.Resource.CSR,
  100. }
  101. }
  102. err = IssueCert(payload, log)
  103. if err != nil {
  104. log.Error(err)
  105. notification.Error("Renew Certificate Error", strings.Join(payload.ServerName, ", "), nil)
  106. return
  107. }
  108. notification.Success("Renew Certificate Success", strings.Join(payload.ServerName, ", "), nil)
  109. err = SyncToRemoteServer(certModel)
  110. if err != nil {
  111. notification.Error("Sync Certificate Error", err.Error(), nil)
  112. return
  113. }
  114. }