Docker template generator
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lego.go 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. package main
  2. import (
  3. "crypto"
  4. "crypto/ecdsa"
  5. "crypto/elliptic"
  6. "crypto/rand"
  7. "crypto/x509"
  8. "encoding/json"
  9. "github.com/go-acme/lego/v3/certcrypto"
  10. "github.com/go-acme/lego/v3/certificate"
  11. "github.com/go-acme/lego/v3/lego"
  12. "github.com/go-acme/lego/v3/log"
  13. "github.com/go-acme/lego/v3/providers/dns"
  14. "github.com/go-acme/lego/v3/registration"
  15. "go.uber.org/zap"
  16. "io/ioutil"
  17. "sort"
  18. "time"
  19. )
  20. type AcmeUser struct {
  21. Email string `json:"email"`
  22. Registration *registration.Resource `json:"registration,omitempty"`
  23. LiveKey *ecdsa.PrivateKey `json:"-"`
  24. Key []byte `json:"key"`
  25. }
  26. func (u *AcmeUser) GetEmail() string {
  27. return u.Email
  28. }
  29. func (u AcmeUser) GetRegistration() *registration.Resource {
  30. return u.Registration
  31. }
  32. func (u *AcmeUser) GetPrivateKey() crypto.PrivateKey {
  33. return u.LiveKey
  34. }
  35. type SavedCertificate struct {
  36. Domains []string `json:"domains"`
  37. CertURL string `json:"certUrl"`
  38. CertStableURL string `json:"certStableUrl"`
  39. NotAfter time.Time `json:"notAfter"`
  40. PrivateKey []byte `json:"privateKey"`
  41. Certificate []byte `json:"certificate"`
  42. IssuerCertificate []byte `json:"issuer"`
  43. CSR []byte `json:"csr"`
  44. }
  45. type CertificateManagerData struct {
  46. User *AcmeUser `json:"user"`
  47. Certs []*SavedCertificate `json:"certs"`
  48. }
  49. type CertificateManager struct {
  50. logger *zap.SugaredLogger
  51. acmeProvider string
  52. keyType certcrypto.KeyType
  53. path string
  54. dnsProvider string
  55. data *CertificateManagerData
  56. client *lego.Client
  57. }
  58. func NewCertificateManager(logger *zap.SugaredLogger, acmeProvider string, keyType certcrypto.KeyType, dnsProvider string, path string) *CertificateManager {
  59. return &CertificateManager{
  60. logger: logger,
  61. acmeProvider: acmeProvider,
  62. keyType: keyType,
  63. dnsProvider: dnsProvider,
  64. path: path,
  65. }
  66. }
  67. func (c *CertificateManager) Init(email string) error {
  68. legoLogger, err := zap.NewStdLogAt(c.logger.Desugar(), zap.DebugLevel)
  69. if err == nil {
  70. log.Logger = legoLogger
  71. err = c.load()
  72. }
  73. if err == nil {
  74. err = c.createUser(email)
  75. }
  76. if err == nil {
  77. err = c.createClient()
  78. }
  79. if err == nil {
  80. err = c.register()
  81. }
  82. return err
  83. }
  84. func (c *CertificateManager) load() error {
  85. data := &CertificateManagerData{}
  86. buf, _ := ioutil.ReadFile(c.path)
  87. if buf != nil {
  88. err := json.Unmarshal(buf, data)
  89. if err != nil {
  90. return err
  91. }
  92. if data.User != nil {
  93. liveKey, err := x509.ParseECPrivateKey(data.User.Key)
  94. if err != nil {
  95. return err
  96. }
  97. data.User.LiveKey = liveKey
  98. }
  99. }
  100. c.data = data
  101. return nil
  102. }
  103. func (c *CertificateManager) save() error {
  104. c.logger.Info("Saving certificate config to ", c.path)
  105. data, err := json.Marshal(c.data)
  106. if err != nil {
  107. return err
  108. }
  109. return ioutil.WriteFile(c.path, data, 0600)
  110. }
  111. func (c *CertificateManager) createUser(email string) error {
  112. if c.data.User == nil {
  113. c.logger.Infof("Creating a new private key for ACME use")
  114. privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  115. if err != nil {
  116. return err
  117. }
  118. marshaled, err := x509.MarshalECPrivateKey(privateKey)
  119. if err != nil {
  120. return err
  121. }
  122. c.data.User = &AcmeUser{
  123. LiveKey: privateKey,
  124. Key: marshaled,
  125. Email: email,
  126. }
  127. return c.save()
  128. }
  129. return nil
  130. }
  131. func (c *CertificateManager) createClient() error {
  132. config := lego.NewConfig(c.data.User)
  133. config.CADirURL = c.acmeProvider
  134. config.Certificate.KeyType = c.keyType
  135. client, err := lego.NewClient(config)
  136. if err != nil {
  137. return err
  138. }
  139. provider, err := dns.NewDNSChallengeProviderByName(c.dnsProvider)
  140. if err != nil {
  141. return err
  142. }
  143. err = client.Challenge.SetDNS01Provider(provider)
  144. if err != nil {
  145. return err
  146. }
  147. c.client = client
  148. return nil
  149. }
  150. func (c *CertificateManager) register() error {
  151. if c.data.User.Registration == nil {
  152. c.logger.Infof("Registering new user with ACME provider")
  153. reg, err := c.client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
  154. if err != nil {
  155. return err
  156. }
  157. c.data.User.Registration = reg
  158. return c.save()
  159. }
  160. return nil
  161. }
  162. func (c *CertificateManager) GetCertificate(domains []string) (error, *SavedCertificate) {
  163. existing := c.loadCert(domains)
  164. if existing != nil {
  165. if existing.NotAfter.Before(time.Now().Add(time.Hour * 24 * 31)) {
  166. c.logger.Debugf("Found existing certificate for %s, but it expires soon; renewing", domains)
  167. } else {
  168. c.logger.Debugf("Returning existing certificate for request %s", domains)
  169. return nil, existing
  170. }
  171. }
  172. request := certificate.ObtainRequest{
  173. Domains: domains,
  174. Bundle: true,
  175. }
  176. cert, err := c.client.Certificate.Obtain(request)
  177. if err != nil {
  178. return err, nil
  179. }
  180. return c.saveCert(domains, cert)
  181. }
  182. func (c *CertificateManager) loadCert(domains []string) *SavedCertificate {
  183. for _, cert := range c.data.Certs {
  184. if domainsMatch(cert.Domains, domains) {
  185. return cert
  186. }
  187. }
  188. return nil
  189. }
  190. func domainsMatch(domains1, domains2 []string) bool {
  191. if len(domains1) != len(domains2) {
  192. return false
  193. }
  194. sort.Strings(domains1)
  195. sort.Strings(domains2)
  196. for i := range domains1 {
  197. if domains1[i] != domains2[i] {
  198. return false
  199. }
  200. }
  201. return true
  202. }
  203. func (c *CertificateManager) removeCerts(domains []string) {
  204. var newCerts []*SavedCertificate
  205. for _, cert := range c.data.Certs {
  206. if !domainsMatch(cert.Domains, domains) {
  207. newCerts = append(newCerts, cert)
  208. }
  209. }
  210. diff := len(c.data.Certs) - len(newCerts)
  211. if diff > 0 {
  212. c.logger.Debugf("Removed %d certificates matching %s", diff, domains)
  213. c.data.Certs = newCerts
  214. }
  215. }
  216. func (c *CertificateManager) saveCert(domains []string, cert *certificate.Resource) (error, *SavedCertificate) {
  217. c.removeCerts(domains)
  218. savedCert := &SavedCertificate{
  219. Domains: domains,
  220. Certificate: cert.Certificate,
  221. NotAfter: c.getExpiry(cert),
  222. PrivateKey: cert.PrivateKey,
  223. CertStableURL: cert.CertStableURL,
  224. CertURL: cert.CertURL,
  225. CSR: cert.CSR,
  226. IssuerCertificate: cert.IssuerCertificate,
  227. }
  228. c.data.Certs = append(c.data.Certs, savedCert)
  229. return c.save(), savedCert
  230. }
  231. func (c *CertificateManager) getExpiry(cert *certificate.Resource) time.Time {
  232. pem, err := certcrypto.ParsePEMCertificate(cert.Certificate)
  233. if err != nil {
  234. c.logger.Fatal(err)
  235. }
  236. return pem.NotAfter
  237. }