autorenew.go 13 KB

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