1
0

autorenew.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. package acme
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "net/mail"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "time"
  12. "imuslab.com/zoraxy/mod/info/logger"
  13. "imuslab.com/zoraxy/mod/utils"
  14. )
  15. /*
  16. autorenew.go
  17. This script handle auto renew
  18. */
  19. type AutoRenewConfig struct {
  20. Enabled bool //Automatic renew is enabled
  21. Email string //Email for acme
  22. RenewAll bool //Renew all or selective renew with the slice below
  23. FilesToRenew []string //If RenewAll is false, renew these certificate files
  24. DNSServers string // DNS servers
  25. }
  26. type AutoRenewer struct {
  27. ConfigFilePath string
  28. CertFolder string
  29. AcmeHandler *ACMEHandler
  30. RenewerConfig *AutoRenewConfig
  31. RenewTickInterval int64
  32. EarlyRenewDays int //How many days before cert expire to renew certificate
  33. TickerstopChan chan bool
  34. Logger *logger.Logger //System wide logger
  35. }
  36. type ExpiredCerts struct {
  37. Domains []string
  38. Filepath string
  39. }
  40. // Create an auto renew agent, require config filepath and auto scan & renew interval (seconds)
  41. // Set renew check interval to 0 for auto (1 day)
  42. func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64, earlyRenewDays int, AcmeHandler *ACMEHandler, logger *logger.Logger) (*AutoRenewer, error) {
  43. if renewCheckInterval == 0 {
  44. renewCheckInterval = 86400 //1 day
  45. }
  46. if earlyRenewDays == 0 {
  47. earlyRenewDays = 30
  48. }
  49. //Load the config file. If not found, create one
  50. if !utils.FileExists(config) {
  51. //Create one
  52. os.MkdirAll(filepath.Dir(config), 0775)
  53. newConfig := AutoRenewConfig{
  54. RenewAll: true,
  55. FilesToRenew: []string{},
  56. }
  57. js, _ := json.MarshalIndent(newConfig, "", " ")
  58. err := os.WriteFile(config, js, 0775)
  59. if err != nil {
  60. return nil, errors.New("Failed to create acme auto renewer config: " + err.Error())
  61. }
  62. }
  63. renewerConfig := AutoRenewConfig{}
  64. content, err := os.ReadFile(config)
  65. if err != nil {
  66. return nil, errors.New("Failed to open acme auto renewer config: " + err.Error())
  67. }
  68. err = json.Unmarshal(content, &renewerConfig)
  69. if err != nil {
  70. return nil, errors.New("Malformed acme config file: " + err.Error())
  71. }
  72. //Create an Auto renew object
  73. thisRenewer := AutoRenewer{
  74. ConfigFilePath: config,
  75. CertFolder: certFolder,
  76. AcmeHandler: AcmeHandler,
  77. RenewerConfig: &renewerConfig,
  78. RenewTickInterval: renewCheckInterval,
  79. EarlyRenewDays: earlyRenewDays,
  80. Logger: logger,
  81. }
  82. thisRenewer.Logf("ACME early renew set to "+fmt.Sprint(earlyRenewDays)+" days and check interval set to "+fmt.Sprint(renewCheckInterval)+" seconds", nil)
  83. if thisRenewer.RenewerConfig.Enabled {
  84. //Start the renew ticker
  85. thisRenewer.StartAutoRenewTicker()
  86. //Check and renew certificate on startup
  87. go thisRenewer.CheckAndRenewCertificates()
  88. }
  89. return &thisRenewer, nil
  90. }
  91. func (a *AutoRenewer) Logf(message string, err error) {
  92. a.Logger.PrintAndLog("cert-renew", message, err)
  93. }
  94. func (a *AutoRenewer) StartAutoRenewTicker() {
  95. //Stop the previous ticker if still running
  96. if a.TickerstopChan != nil {
  97. a.TickerstopChan <- true
  98. }
  99. time.Sleep(1 * time.Second)
  100. ticker := time.NewTicker(time.Duration(a.RenewTickInterval) * time.Second)
  101. done := make(chan bool)
  102. //Start the ticker to check and renew every x seconds
  103. go func(a *AutoRenewer) {
  104. for {
  105. select {
  106. case <-done:
  107. return
  108. case <-ticker.C:
  109. a.Logf("Check and renew certificates in progress", nil)
  110. a.CheckAndRenewCertificates()
  111. }
  112. }
  113. }(a)
  114. a.TickerstopChan = done
  115. }
  116. func (a *AutoRenewer) StopAutoRenewTicker() {
  117. if a.TickerstopChan != nil {
  118. a.TickerstopChan <- true
  119. }
  120. a.TickerstopChan = nil
  121. }
  122. // Handle update auto renew domains
  123. // Set opr for different mode of operations
  124. // opr = setSelected -> Enter a list of file names (or matching rules) for auto renew
  125. // opr = setAuto -> Set to use auto detect certificates and renew
  126. func (a *AutoRenewer) HandleSetAutoRenewDomains(w http.ResponseWriter, r *http.Request) {
  127. opr, err := utils.PostPara(r, "opr")
  128. if err != nil {
  129. utils.SendErrorResponse(w, "Operation not set")
  130. return
  131. }
  132. if opr == "setSelected" {
  133. files, err := utils.PostPara(r, "domains")
  134. if err != nil {
  135. utils.SendErrorResponse(w, "Domains is not defined")
  136. return
  137. }
  138. //Parse it int array of string
  139. matchingRuleFiles := []string{}
  140. err = json.Unmarshal([]byte(files), &matchingRuleFiles)
  141. if err != nil {
  142. utils.SendErrorResponse(w, err.Error())
  143. return
  144. }
  145. //Update the configs
  146. a.RenewerConfig.RenewAll = false
  147. a.RenewerConfig.FilesToRenew = matchingRuleFiles
  148. a.saveRenewConfigToFile()
  149. utils.SendOK(w)
  150. } else if opr == "setAuto" {
  151. a.RenewerConfig.RenewAll = true
  152. a.saveRenewConfigToFile()
  153. utils.SendOK(w)
  154. } else {
  155. utils.SendErrorResponse(w, "invalid operation given")
  156. }
  157. }
  158. // if auto renew all is true (aka auto scan), it will return []string{"*"}
  159. func (a *AutoRenewer) HandleLoadAutoRenewDomains(w http.ResponseWriter, r *http.Request) {
  160. results := []string{}
  161. if a.RenewerConfig.RenewAll {
  162. //Auto pick which cert to renew.
  163. results = append(results, "*")
  164. } else {
  165. //Manually set the files to renew
  166. results = a.RenewerConfig.FilesToRenew
  167. }
  168. js, _ := json.Marshal(results)
  169. utils.SendJSONResponse(w, string(js))
  170. }
  171. func (a *AutoRenewer) HandleRenewPolicy(w http.ResponseWriter, r *http.Request) {
  172. //Load the current value
  173. js, _ := json.Marshal(a.RenewerConfig.RenewAll)
  174. utils.SendJSONResponse(w, string(js))
  175. }
  176. func (a *AutoRenewer) HandleRenewNow(w http.ResponseWriter, r *http.Request) {
  177. renewedDomains, err := a.CheckAndRenewCertificates()
  178. if err != nil {
  179. utils.SendErrorResponse(w, err.Error())
  180. return
  181. }
  182. message := "Domains renewed"
  183. if len(renewedDomains) == 0 {
  184. message = ("All certificates are up-to-date!")
  185. } else {
  186. message = ("The following domains have been renewed: " + strings.Join(renewedDomains, ","))
  187. }
  188. js, _ := json.Marshal(message)
  189. utils.SendJSONResponse(w, string(js))
  190. }
  191. // HandleAutoRenewEnable get and set the auto renew enable state
  192. func (a *AutoRenewer) HandleAutoRenewEnable(w http.ResponseWriter, r *http.Request) {
  193. if r.Method == http.MethodGet {
  194. js, _ := json.Marshal(a.RenewerConfig.Enabled)
  195. utils.SendJSONResponse(w, string(js))
  196. } else if r.Method == http.MethodPost {
  197. val, err := utils.PostBool(r, "enable")
  198. if err != nil {
  199. utils.SendErrorResponse(w, "invalid or empty enable state")
  200. }
  201. if val {
  202. //Check if the email is not empty
  203. if a.RenewerConfig.Email == "" {
  204. utils.SendErrorResponse(w, "Email is not set")
  205. return
  206. }
  207. a.RenewerConfig.Enabled = true
  208. a.saveRenewConfigToFile()
  209. a.Logf("ACME auto renew enabled", nil)
  210. a.StartAutoRenewTicker()
  211. } else {
  212. a.RenewerConfig.Enabled = false
  213. a.saveRenewConfigToFile()
  214. a.Logf("ACME auto renew disabled", nil)
  215. a.StopAutoRenewTicker()
  216. }
  217. } else {
  218. http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
  219. }
  220. }
  221. func (a *AutoRenewer) HandleACMEEmail(w http.ResponseWriter, r *http.Request) {
  222. if r.Method == http.MethodGet {
  223. //Return the current email to user
  224. js, _ := json.Marshal(a.RenewerConfig.Email)
  225. utils.SendJSONResponse(w, string(js))
  226. } else if r.Method == http.MethodPost {
  227. email, err := utils.PostPara(r, "set")
  228. if err != nil {
  229. utils.SendErrorResponse(w, "invalid or empty email given")
  230. return
  231. }
  232. //Check if the email is valid
  233. _, err = mail.ParseAddress(email)
  234. if err != nil {
  235. utils.SendErrorResponse(w, err.Error())
  236. return
  237. }
  238. //Set the new config
  239. a.RenewerConfig.Email = email
  240. a.saveRenewConfigToFile()
  241. utils.SendOK(w)
  242. } else {
  243. http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
  244. }
  245. }
  246. // Check and renew certificates. This check all the certificates in the
  247. // certificate folder and return a list of certs that is renewed in this call
  248. // Return string array with length 0 when no cert is expired
  249. func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
  250. certFolder := a.CertFolder
  251. files, err := os.ReadDir(certFolder)
  252. if err != nil {
  253. a.Logf("Read certificate store failed", err)
  254. return []string{}, err
  255. }
  256. expiredCertList := []*ExpiredCerts{}
  257. if a.RenewerConfig.RenewAll {
  258. //Scan and renew all
  259. for _, file := range files {
  260. if filepath.Ext(file.Name()) == ".crt" || filepath.Ext(file.Name()) == ".pem" {
  261. //This is a public key file
  262. certBytes, err := os.ReadFile(filepath.Join(certFolder, file.Name()))
  263. if err != nil {
  264. continue
  265. }
  266. if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
  267. //This cert is expired
  268. DNSName, err := ExtractDomains(certBytes)
  269. if err != nil {
  270. //Maybe self signed. Ignore this
  271. a.Logf("Encounted error when trying to resolve DNS name for cert "+file.Name(), err)
  272. continue
  273. }
  274. expiredCertList = append(expiredCertList, &ExpiredCerts{
  275. Filepath: filepath.Join(certFolder, file.Name()),
  276. Domains: DNSName,
  277. })
  278. }
  279. }
  280. }
  281. } else {
  282. //Only renew those in the list
  283. for _, file := range files {
  284. fileName := file.Name()
  285. certName := fileName[:len(fileName)-len(filepath.Ext(fileName))]
  286. if contains(a.RenewerConfig.FilesToRenew, certName) {
  287. //This is the one to auto renew
  288. certBytes, err := os.ReadFile(filepath.Join(certFolder, file.Name()))
  289. if err != nil {
  290. continue
  291. }
  292. if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
  293. //This cert is expired
  294. DNSName, err := ExtractDomains(certBytes)
  295. if err != nil {
  296. //Maybe self signed. Ignore this
  297. a.Logf("Encounted error when trying to resolve DNS name for cert "+file.Name(), err)
  298. continue
  299. }
  300. expiredCertList = append(expiredCertList, &ExpiredCerts{
  301. Filepath: filepath.Join(certFolder, file.Name()),
  302. Domains: DNSName,
  303. })
  304. }
  305. }
  306. }
  307. }
  308. return a.renewExpiredDomains(expiredCertList)
  309. }
  310. // Close the auto renewer
  311. func (a *AutoRenewer) Close() {
  312. if a.TickerstopChan != nil {
  313. a.TickerstopChan <- true
  314. }
  315. }
  316. // Renew the certificate by filename extract all DNS name from the
  317. // certificate and renew them one by one by calling to the acmeHandler
  318. func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, error) {
  319. renewedCertFiles := []string{}
  320. for _, expiredCert := range certs {
  321. a.Logf("Renewing "+expiredCert.Filepath+" (Might take a few minutes)", nil)
  322. fileName := filepath.Base(expiredCert.Filepath)
  323. certName := fileName[:len(fileName)-len(filepath.Ext(fileName))]
  324. // Load certificate info for ACME detail
  325. certInfoFilename := fmt.Sprintf("%s/%s.json", filepath.Dir(expiredCert.Filepath), certName)
  326. certInfo, err := LoadCertInfoJSON(certInfoFilename)
  327. if err != nil {
  328. a.Logf("Renew "+certName+"certificate error, can't get the ACME detail for certificate, trying org section as ca", err)
  329. if CAName, extractErr := ExtractIssuerNameFromPEM(expiredCert.Filepath); extractErr != nil {
  330. a.Logf("Extract issuer name for cert error, using default ca", err)
  331. certInfo = &CertificateInfoJSON{}
  332. } else {
  333. certInfo = &CertificateInfoJSON{AcmeName: CAName}
  334. }
  335. }
  336. //For upgrading config from older version of Zoraxy which don't have timeout
  337. if certInfo.PropTimeout == 0 {
  338. //Set default timeout
  339. certInfo.PropTimeout = 300
  340. }
  341. // Extract DNS servers from the certificate info if available
  342. var dnsServers string
  343. if len(certInfo.DNSServers) > 0 {
  344. dnsServers = strings.Join(certInfo.DNSServers, ",")
  345. }
  346. _, err = a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, certInfo.AcmeName, certInfo.AcmeUrl, certInfo.SkipTLS, certInfo.UseDNS, certInfo.PropTimeout, dnsServers)
  347. if err != nil {
  348. a.Logf("Renew "+fileName+"("+strings.Join(expiredCert.Domains, ",")+") failed", err)
  349. } else {
  350. a.Logf("Successfully renewed "+filepath.Base(expiredCert.Filepath), nil)
  351. renewedCertFiles = append(renewedCertFiles, filepath.Base(expiredCert.Filepath))
  352. }
  353. }
  354. return renewedCertFiles, nil
  355. }
  356. // Write the current renewer config to file
  357. func (a *AutoRenewer) saveRenewConfigToFile() error {
  358. js, _ := json.MarshalIndent(a.RenewerConfig, "", " ")
  359. return os.WriteFile(a.ConfigFilePath, js, 0775)
  360. }
  361. // Handle update auto renew EAD configuration
  362. func (a *AutoRenewer) HanldeSetEAB(w http.ResponseWriter, r *http.Request) {
  363. kid, err := utils.GetPara(r, "kid")
  364. if err != nil {
  365. utils.SendErrorResponse(w, "kid not set")
  366. return
  367. }
  368. hmacEncoded, err := utils.GetPara(r, "hmacEncoded")
  369. if err != nil {
  370. utils.SendErrorResponse(w, "hmacEncoded not set")
  371. return
  372. }
  373. acmeDirectoryURL, err := utils.GetPara(r, "acmeDirectoryURL")
  374. if err != nil {
  375. utils.SendErrorResponse(w, "acmeDirectoryURL not set")
  376. return
  377. }
  378. if !a.AcmeHandler.Database.TableExists("acme") {
  379. a.AcmeHandler.Database.NewTable("acme")
  380. }
  381. a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_kid", kid)
  382. a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_hmacEncoded", hmacEncoded)
  383. utils.SendOK(w)
  384. }
  385. // Handle update auto renew DNS configuration
  386. func (a *AutoRenewer) HandleSetDNS(w http.ResponseWriter, r *http.Request) {
  387. dnsProvider, err := utils.PostPara(r, "dnsProvider")
  388. if err != nil {
  389. utils.SendErrorResponse(w, "dnsProvider not set")
  390. return
  391. }
  392. dnsCredentials, err := utils.PostPara(r, "dnsCredentials")
  393. if err != nil {
  394. utils.SendErrorResponse(w, "dnsCredentials not set")
  395. return
  396. }
  397. filename, err := utils.PostPara(r, "filename")
  398. if err != nil {
  399. utils.SendErrorResponse(w, "filename not set")
  400. return
  401. }
  402. dnsServers, err := utils.PostPara(r, "dnsServers")
  403. if err != nil {
  404. dnsServers = ""
  405. }
  406. if !a.AcmeHandler.Database.TableExists("acme") {
  407. a.AcmeHandler.Database.NewTable("acme")
  408. }
  409. a.AcmeHandler.Database.Write("acme", filename+"_dns_provider", dnsProvider)
  410. a.AcmeHandler.Database.Write("acme", filename+"_dns_credentials", dnsCredentials)
  411. a.AcmeHandler.Database.Write("acme", filename+"_dns_servers", dnsServers)
  412. utils.SendOK(w)
  413. }