1
0

autorenew.go 13 KB

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