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.

solvers.go 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. // Copyright 2015 Matthew Holt
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package certmagic
  15. import (
  16. "context"
  17. "crypto/tls"
  18. "encoding/json"
  19. "fmt"
  20. "log"
  21. "net"
  22. "net/http"
  23. "path"
  24. "runtime"
  25. "strings"
  26. "sync"
  27. "sync/atomic"
  28. "time"
  29. "github.com/libdns/libdns"
  30. "github.com/mholt/acmez"
  31. "github.com/mholt/acmez/acme"
  32. )
  33. // httpSolver solves the HTTP challenge. It must be
  34. // associated with a config and an address to use
  35. // for solving the challenge. If multiple httpSolvers
  36. // are initialized concurrently, the first one to
  37. // begin will start the server, and the last one to
  38. // finish will stop the server. This solver must be
  39. // wrapped by a distributedSolver to work properly,
  40. // because the only way the HTTP challenge handler
  41. // can access the keyAuth material is by loading it
  42. // from storage, which is done by distributedSolver.
  43. type httpSolver struct {
  44. closed int32 // accessed atomically
  45. acmeManager *ACMEManager
  46. address string
  47. }
  48. // Present starts an HTTP server if none is already listening on s.address.
  49. func (s *httpSolver) Present(ctx context.Context, _ acme.Challenge) error {
  50. solversMu.Lock()
  51. defer solversMu.Unlock()
  52. si := getSolverInfo(s.address)
  53. si.count++
  54. if si.listener != nil {
  55. return nil // already be served by us
  56. }
  57. // notice the unusual error handling here; we
  58. // only continue to start a challenge server if
  59. // we got a listener; in all other cases return
  60. ln, err := robustTryListen(s.address)
  61. if ln == nil {
  62. return err
  63. }
  64. // successfully bound socket, so save listener and start key auth HTTP server
  65. si.listener = ln
  66. go s.serve(si)
  67. return nil
  68. }
  69. // serve is an HTTP server that serves only HTTP challenge responses.
  70. func (s *httpSolver) serve(si *solverInfo) {
  71. defer func() {
  72. if err := recover(); err != nil {
  73. buf := make([]byte, stackTraceBufferSize)
  74. buf = buf[:runtime.Stack(buf, false)]
  75. log.Printf("panic: http solver server: %v\n%s", err, buf)
  76. }
  77. }()
  78. defer close(si.done)
  79. httpServer := &http.Server{Handler: s.acmeManager.HTTPChallengeHandler(http.NewServeMux())}
  80. httpServer.SetKeepAlivesEnabled(false)
  81. err := httpServer.Serve(si.listener)
  82. if err != nil && atomic.LoadInt32(&s.closed) != 1 {
  83. log.Printf("[ERROR] key auth HTTP server: %v", err)
  84. }
  85. }
  86. // CleanUp cleans up the HTTP server if it is the last one to finish.
  87. func (s *httpSolver) CleanUp(ctx context.Context, _ acme.Challenge) error {
  88. solversMu.Lock()
  89. defer solversMu.Unlock()
  90. si := getSolverInfo(s.address)
  91. si.count--
  92. if si.count == 0 {
  93. // last one out turns off the lights
  94. atomic.StoreInt32(&s.closed, 1)
  95. if si.listener != nil {
  96. si.listener.Close()
  97. <-si.done
  98. }
  99. delete(solvers, s.address)
  100. }
  101. return nil
  102. }
  103. // tlsALPNSolver is a type that can solve TLS-ALPN challenges.
  104. // It must have an associated config and address on which to
  105. // serve the challenge.
  106. type tlsALPNSolver struct {
  107. config *Config
  108. address string
  109. }
  110. // Present adds the certificate to the certificate cache and, if
  111. // needed, starts a TLS server for answering TLS-ALPN challenges.
  112. func (s *tlsALPNSolver) Present(ctx context.Context, chal acme.Challenge) error {
  113. // we pre-generate the certificate for efficiency with multi-perspective
  114. // validation, so it only has to be done once (at least, by this instance;
  115. // distributed solving does not have that luxury, oh well) - update the
  116. // challenge data in memory to be the generated certificate
  117. cert, err := acmez.TLSALPN01ChallengeCert(chal)
  118. if err != nil {
  119. return err
  120. }
  121. activeChallengesMu.Lock()
  122. chalData := activeChallenges[chal.Identifier.Value]
  123. chalData.data = cert
  124. activeChallenges[chal.Identifier.Value] = chalData
  125. activeChallengesMu.Unlock()
  126. // the rest of this function increments the
  127. // challenge count for the solver at this
  128. // listener address, and if necessary, starts
  129. // a simple TLS server
  130. solversMu.Lock()
  131. defer solversMu.Unlock()
  132. si := getSolverInfo(s.address)
  133. si.count++
  134. if si.listener != nil {
  135. return nil // already be served by us
  136. }
  137. // notice the unusual error handling here; we
  138. // only continue to start a challenge server if
  139. // we got a listener; in all other cases return
  140. ln, err := robustTryListen(s.address)
  141. if ln == nil {
  142. return err
  143. }
  144. // we were able to bind the socket, so make it into a TLS
  145. // listener, store it with the solverInfo, and start the
  146. // challenge server
  147. si.listener = tls.NewListener(ln, s.config.TLSConfig())
  148. go func() {
  149. defer func() {
  150. if err := recover(); err != nil {
  151. buf := make([]byte, stackTraceBufferSize)
  152. buf = buf[:runtime.Stack(buf, false)]
  153. log.Printf("panic: tls-alpn solver server: %v\n%s", err, buf)
  154. }
  155. }()
  156. defer close(si.done)
  157. for {
  158. conn, err := si.listener.Accept()
  159. if err != nil {
  160. if atomic.LoadInt32(&si.closed) == 1 {
  161. return
  162. }
  163. log.Printf("[ERROR] TLS-ALPN challenge server: accept: %v", err)
  164. continue
  165. }
  166. go s.handleConn(conn)
  167. }
  168. }()
  169. return nil
  170. }
  171. // handleConn completes the TLS handshake and then closes conn.
  172. func (*tlsALPNSolver) handleConn(conn net.Conn) {
  173. defer func() {
  174. if err := recover(); err != nil {
  175. buf := make([]byte, stackTraceBufferSize)
  176. buf = buf[:runtime.Stack(buf, false)]
  177. log.Printf("panic: tls-alpn solver handler: %v\n%s", err, buf)
  178. }
  179. }()
  180. defer conn.Close()
  181. tlsConn, ok := conn.(*tls.Conn)
  182. if !ok {
  183. log.Printf("[ERROR] TLS-ALPN challenge server: expected tls.Conn but got %T: %#v", conn, conn)
  184. return
  185. }
  186. err := tlsConn.Handshake()
  187. if err != nil {
  188. log.Printf("[ERROR] TLS-ALPN challenge server: handshake: %v", err)
  189. return
  190. }
  191. }
  192. // CleanUp removes the challenge certificate from the cache, and if
  193. // it is the last one to finish, stops the TLS server.
  194. func (s *tlsALPNSolver) CleanUp(ctx context.Context, chal acme.Challenge) error {
  195. s.config.certCache.mu.Lock()
  196. delete(s.config.certCache.cache, tlsALPNCertKeyName(chal.Identifier.Value))
  197. s.config.certCache.mu.Unlock()
  198. solversMu.Lock()
  199. defer solversMu.Unlock()
  200. si := getSolverInfo(s.address)
  201. si.count--
  202. if si.count == 0 {
  203. // last one out turns off the lights
  204. atomic.StoreInt32(&si.closed, 1)
  205. if si.listener != nil {
  206. si.listener.Close()
  207. <-si.done
  208. }
  209. delete(solvers, s.address)
  210. }
  211. return nil
  212. }
  213. // tlsALPNCertKeyName returns the key to use when caching a cert
  214. // for use with the TLS-ALPN ACME challenge. It is simply to help
  215. // avoid conflicts (although at time of writing, there shouldn't
  216. // be, since the cert cache is keyed by hash of certificate chain).
  217. func tlsALPNCertKeyName(sniName string) string {
  218. return sniName + ":acme-tls-alpn"
  219. }
  220. // DNS01Solver is a type that makes libdns providers usable
  221. // as ACME dns-01 challenge solvers.
  222. // See https://github.com/libdns/libdns
  223. type DNS01Solver struct {
  224. // The implementation that interacts with the DNS
  225. // provider to set or delete records. (REQUIRED)
  226. DNSProvider ACMEDNSProvider
  227. // The TTL for the temporary challenge records.
  228. TTL time.Duration
  229. // Maximum time to wait for temporary record to appear.
  230. PropagationTimeout time.Duration
  231. // Preferred DNS resolver(s) to use when doing DNS lookups.
  232. Resolvers []string
  233. txtRecords map[string]dnsPresentMemory // keyed by domain name
  234. txtRecordsMu sync.Mutex
  235. }
  236. // Present creates the DNS TXT record for the given ACME challenge.
  237. func (s *DNS01Solver) Present(ctx context.Context, challenge acme.Challenge) error {
  238. dnsName := challenge.DNS01TXTRecordName()
  239. keyAuth := challenge.DNS01KeyAuthorization()
  240. // multiple identifiers can have the same ACME challenge
  241. // domain (e.g. example.com and *.example.com) so we need
  242. // to ensure that we don't solve those concurrently and
  243. // step on each challenges' metaphorical toes; see
  244. // https://github.com/caddyserver/caddy/issues/3474
  245. activeDNSChallenges.Lock(dnsName)
  246. zone, err := findZoneByFQDN(dnsName, recursiveNameservers(s.Resolvers))
  247. if err != nil {
  248. return fmt.Errorf("could not determine zone for domain %q: %v", dnsName, err)
  249. }
  250. rec := libdns.Record{
  251. Type: "TXT",
  252. Name: libdns.RelativeName(dnsName+".", zone),
  253. Value: keyAuth,
  254. TTL: s.TTL,
  255. }
  256. results, err := s.DNSProvider.AppendRecords(ctx, zone, []libdns.Record{rec})
  257. if err != nil {
  258. return fmt.Errorf("adding temporary record for zone %s: %w", zone, err)
  259. }
  260. if len(results) != 1 {
  261. return fmt.Errorf("expected one record, got %d: %v", len(results), results)
  262. }
  263. // remember the record and zone we got so we can clean up more efficiently
  264. s.txtRecordsMu.Lock()
  265. if s.txtRecords == nil {
  266. s.txtRecords = make(map[string]dnsPresentMemory)
  267. }
  268. s.txtRecords[dnsName] = dnsPresentMemory{dnsZone: zone, rec: results[0]}
  269. s.txtRecordsMu.Unlock()
  270. return nil
  271. }
  272. // Wait blocks until the TXT record created in Present() appears in
  273. // authoritative lookups, i.e. until it has propagated, or until
  274. // timeout, whichever is first.
  275. func (s *DNS01Solver) Wait(ctx context.Context, challenge acme.Challenge) error {
  276. dnsName := challenge.DNS01TXTRecordName()
  277. keyAuth := challenge.DNS01KeyAuthorization()
  278. timeout := s.PropagationTimeout
  279. if timeout == 0 {
  280. timeout = 2 * time.Minute
  281. }
  282. const interval = 2 * time.Second
  283. resolvers := recursiveNameservers(s.Resolvers)
  284. var err error
  285. start := time.Now()
  286. for time.Since(start) < timeout {
  287. select {
  288. case <-time.After(interval):
  289. case <-ctx.Done():
  290. return ctx.Err()
  291. }
  292. var ready bool
  293. ready, err = checkDNSPropagation(dnsName, keyAuth, resolvers)
  294. if err != nil {
  295. return fmt.Errorf("checking DNS propagation of %s: %w", dnsName, err)
  296. }
  297. if ready {
  298. return nil
  299. }
  300. }
  301. return fmt.Errorf("timed out waiting for record to fully propagate; verify DNS provider configuration is correct - last error: %v", err)
  302. }
  303. // CleanUp deletes the DNS TXT record created in Present().
  304. func (s *DNS01Solver) CleanUp(ctx context.Context, challenge acme.Challenge) error {
  305. dnsName := challenge.DNS01TXTRecordName()
  306. defer func() {
  307. // always forget about it so we don't leak memory
  308. s.txtRecordsMu.Lock()
  309. delete(s.txtRecords, dnsName)
  310. s.txtRecordsMu.Unlock()
  311. // always do this last - but always do it!
  312. activeDNSChallenges.Unlock(dnsName)
  313. }()
  314. // recall the record we created and zone we looked up
  315. s.txtRecordsMu.Lock()
  316. memory, ok := s.txtRecords[dnsName]
  317. if !ok {
  318. s.txtRecordsMu.Unlock()
  319. return fmt.Errorf("no memory of presenting a DNS record for %s (probably OK if presenting failed)", challenge.Identifier.Value)
  320. }
  321. s.txtRecordsMu.Unlock()
  322. // clean up the record
  323. _, err := s.DNSProvider.DeleteRecords(ctx, memory.dnsZone, []libdns.Record{memory.rec})
  324. if err != nil {
  325. return fmt.Errorf("deleting temporary record for zone %s: %w", memory.dnsZone, err)
  326. }
  327. return nil
  328. }
  329. type dnsPresentMemory struct {
  330. dnsZone string
  331. rec libdns.Record
  332. }
  333. // ACMEDNSProvider defines the set of operations required for
  334. // ACME challenges. A DNS provider must be able to append and
  335. // delete records in order to solve ACME challenges. Find one
  336. // you can use at https://github.com/libdns. If your provider
  337. // isn't implemented yet, feel free to contribute!
  338. type ACMEDNSProvider interface {
  339. libdns.RecordAppender
  340. libdns.RecordDeleter
  341. }
  342. // activeDNSChallenges synchronizes DNS challenges for
  343. // names to ensure that challenges for the same ACME
  344. // DNS name do not overlap; for example, the TXT record
  345. // to make for both example.com and *.example.com are
  346. // the same; thus we cannot solve them concurrently.
  347. var activeDNSChallenges = newMapMutex()
  348. // mapMutex implements named mutexes.
  349. type mapMutex struct {
  350. cond *sync.Cond
  351. set map[interface{}]struct{}
  352. }
  353. func newMapMutex() *mapMutex {
  354. return &mapMutex{
  355. cond: sync.NewCond(new(sync.Mutex)),
  356. set: make(map[interface{}]struct{}),
  357. }
  358. }
  359. func (mmu *mapMutex) Lock(key interface{}) {
  360. mmu.cond.L.Lock()
  361. defer mmu.cond.L.Unlock()
  362. for mmu.locked(key) {
  363. mmu.cond.Wait()
  364. }
  365. mmu.set[key] = struct{}{}
  366. }
  367. func (mmu *mapMutex) Unlock(key interface{}) {
  368. mmu.cond.L.Lock()
  369. defer mmu.cond.L.Unlock()
  370. delete(mmu.set, key)
  371. mmu.cond.Broadcast()
  372. }
  373. func (mmu *mapMutex) locked(key interface{}) (ok bool) {
  374. _, ok = mmu.set[key]
  375. return
  376. }
  377. // distributedSolver allows the ACME HTTP-01 and TLS-ALPN challenges
  378. // to be solved by an instance other than the one which initiated it.
  379. // This is useful behind load balancers or in other cluster/fleet
  380. // configurations. The only requirement is that the instance which
  381. // initiates the challenge shares the same storage and locker with
  382. // the others in the cluster. The storage backing the certificate
  383. // cache in distributedSolver.config is crucial.
  384. //
  385. // Obviously, the instance which completes the challenge must be
  386. // serving on the HTTPChallengePort for the HTTP-01 challenge or the
  387. // TLSALPNChallengePort for the TLS-ALPN-01 challenge (or have all
  388. // the packets port-forwarded) to receive and handle the request. The
  389. // server which receives the challenge must handle it by checking to
  390. // see if the challenge token exists in storage, and if so, decode it
  391. // and use it to serve up the correct response. HTTPChallengeHandler
  392. // in this package as well as the GetCertificate method implemented
  393. // by a Config support and even require this behavior.
  394. //
  395. // In short: the only two requirements for cluster operation are
  396. // sharing sync and storage, and using the facilities provided by
  397. // this package for solving the challenges.
  398. type distributedSolver struct {
  399. // The storage backing the distributed solver. It must be
  400. // the same storage configuration as what is solving the
  401. // challenge in order to be effective.
  402. storage Storage
  403. // The storage key prefix, associated with the issuer
  404. // that is solving the challenge.
  405. storageKeyIssuerPrefix string
  406. // Since the distributedSolver is only a
  407. // wrapper over an actual solver, place
  408. // the actual solver here.
  409. solver acmez.Solver
  410. }
  411. // Present invokes the underlying solver's Present method
  412. // and also stores domain, token, and keyAuth to the storage
  413. // backing the certificate cache of dhs.acmeManager.
  414. func (dhs distributedSolver) Present(ctx context.Context, chal acme.Challenge) error {
  415. infoBytes, err := json.Marshal(chal)
  416. if err != nil {
  417. return err
  418. }
  419. err = dhs.storage.Store(dhs.challengeTokensKey(chal.Identifier.Value), infoBytes)
  420. if err != nil {
  421. return err
  422. }
  423. err = dhs.solver.Present(ctx, chal)
  424. if err != nil {
  425. return fmt.Errorf("presenting with embedded solver: %v", err)
  426. }
  427. return nil
  428. }
  429. // Wait wraps the underlying solver's Wait() method, if any. Implements acmez.Waiter.
  430. func (dhs distributedSolver) Wait(ctx context.Context, challenge acme.Challenge) error {
  431. if waiter, ok := dhs.solver.(acmez.Waiter); ok {
  432. return waiter.Wait(ctx, challenge)
  433. }
  434. return nil
  435. }
  436. // CleanUp invokes the underlying solver's CleanUp method
  437. // and also cleans up any assets saved to storage.
  438. func (dhs distributedSolver) CleanUp(ctx context.Context, chal acme.Challenge) error {
  439. err := dhs.storage.Delete(dhs.challengeTokensKey(chal.Identifier.Value))
  440. if err != nil {
  441. return err
  442. }
  443. err = dhs.solver.CleanUp(ctx, chal)
  444. if err != nil {
  445. return fmt.Errorf("cleaning up embedded provider: %v", err)
  446. }
  447. return nil
  448. }
  449. // challengeTokensPrefix returns the key prefix for challenge info.
  450. func (dhs distributedSolver) challengeTokensPrefix() string {
  451. return path.Join(dhs.storageKeyIssuerPrefix, "challenge_tokens")
  452. }
  453. // challengeTokensKey returns the key to use to store and access
  454. // challenge info for domain.
  455. func (dhs distributedSolver) challengeTokensKey(domain string) string {
  456. return path.Join(dhs.challengeTokensPrefix(), StorageKeys.Safe(domain)+".json")
  457. }
  458. // solverInfo associates a listener with the
  459. // number of challenges currently using it.
  460. type solverInfo struct {
  461. closed int32 // accessed atomically
  462. count int
  463. listener net.Listener
  464. done chan struct{} // used to signal when our own solver server is done
  465. }
  466. // getSolverInfo gets a valid solverInfo struct for address.
  467. func getSolverInfo(address string) *solverInfo {
  468. si, ok := solvers[address]
  469. if !ok {
  470. si = &solverInfo{done: make(chan struct{})}
  471. solvers[address] = si
  472. }
  473. return si
  474. }
  475. // robustTryListen calls net.Listen for a TCP socket at addr.
  476. // This function may return both a nil listener and a nil error!
  477. // If it was able to bind the socket, it returns the listener
  478. // and no error. If it wasn't able to bind the socket because
  479. // the socket is already in use, then it returns a nil listener
  480. // and nil error. If it had any other error, it returns the
  481. // error. The intended error handling logic for this function
  482. // is to proceed if the returned listener is not nil; otherwise
  483. // return err (which may also be nil). In other words, this
  484. // function ignores errors if the socket is already in use,
  485. // which is useful for our challenge servers, where we assume
  486. // that whatever is already listening can solve the challenges.
  487. func robustTryListen(addr string) (net.Listener, error) {
  488. var listenErr error
  489. for i := 0; i < 2; i++ {
  490. // doesn't hurt to sleep briefly before the second
  491. // attempt in case the OS has timing issues
  492. if i > 0 {
  493. time.Sleep(100 * time.Millisecond)
  494. }
  495. // if we can bind the socket right away, great!
  496. var ln net.Listener
  497. ln, listenErr = net.Listen("tcp", addr)
  498. if listenErr == nil {
  499. return ln, nil
  500. }
  501. // if it failed just because the socket is already in use, we
  502. // have no choice but to assume that whatever is using the socket
  503. // can answer the challenge already, so we ignore the error
  504. connectErr := dialTCPSocket(addr)
  505. if connectErr == nil {
  506. return nil, nil
  507. }
  508. // hmm, we couldn't connect to the socket, so something else must
  509. // be wrong, right? wrong!! we've had reports across multiple OSes
  510. // now that sometimes connections fail even though the OS told us
  511. // that the address was already in use; either the listener is
  512. // fluctuating between open and closed very, very quickly, or the
  513. // OS is inconsistent and contradicting itself; I have been unable
  514. // to reproduce this, so I'm now resorting to hard-coding substring
  515. // matching in error messages as a really hacky and unreliable
  516. // safeguard against this, until we can idenify exactly what was
  517. // happening; see the following threads for more info:
  518. // https://caddy.community/t/caddy-retry-error/7317
  519. // https://caddy.community/t/v2-upgrade-to-caddy2-failing-with-errors/7423
  520. if strings.Contains(listenErr.Error(), "address already in use") ||
  521. strings.Contains(listenErr.Error(), "one usage of each socket address") {
  522. log.Printf("[WARNING] OS reports a contradiction: %v - but we cannot connect to it, with this error: %v; continuing anyway 🤞 (I don't know what causes this... if you do, please help?)", listenErr, connectErr)
  523. return nil, nil
  524. }
  525. }
  526. return nil, fmt.Errorf("could not start listener for challenge server at %s: %v", addr, listenErr)
  527. }
  528. // dialTCPSocket connects to a TCP address just for the sake of
  529. // seeing if it is open. It returns a nil error if a TCP connection
  530. // can successfully be made to addr within a short timeout.
  531. func dialTCPSocket(addr string) error {
  532. conn, err := net.DialTimeout("tcp", addr, 250*time.Millisecond)
  533. if err == nil {
  534. conn.Close()
  535. }
  536. return err
  537. }
  538. // GetACMEChallenge returns an active ACME challenge for the given identifier,
  539. // or false if no active challenge for that identifier is known.
  540. func GetACMEChallenge(identifier string) (Challenge, bool) {
  541. activeChallengesMu.Lock()
  542. chalData, ok := activeChallenges[identifier]
  543. activeChallengesMu.Unlock()
  544. return chalData, ok
  545. }
  546. // The active challenge solvers, keyed by listener address,
  547. // and protected by a mutex. Note that the creation of
  548. // solver listeners and the incrementing of their counts
  549. // are atomic operations guarded by this mutex.
  550. var (
  551. solvers = make(map[string]*solverInfo)
  552. solversMu sync.Mutex
  553. )
  554. // activeChallenges holds information about all known, currently-active
  555. // ACME challenges, keyed by identifier. CertMagic guarantees that
  556. // challenges for the same identifier do not overlap, by its locking
  557. // mechanisms; thus if a challenge comes in for a certain identifier,
  558. // we can be confident that if this process initiated the challenge,
  559. // the correct information to solve it is in this map. (It may have
  560. // alternatively been initiated by another instance in a cluster, in
  561. // which case the distributed solver will take care of that.)
  562. var (
  563. activeChallenges = make(map[string]Challenge)
  564. activeChallengesMu sync.Mutex
  565. )
  566. // Challenge is an ACME challenge, but optionally paired with
  567. // data that can make it easier or more efficient to solve.
  568. type Challenge struct {
  569. acme.Challenge
  570. data interface{}
  571. }
  572. // solverWrapper should be used to wrap all challenge solvers so that
  573. // we can add the challenge info to memory; this makes challenges globally
  574. // solvable by a single HTTP or TLS server even if multiple servers with
  575. // different configurations/scopes need to get certificates.
  576. type solverWrapper struct{ acmez.Solver }
  577. func (sw solverWrapper) Present(ctx context.Context, chal acme.Challenge) error {
  578. activeChallengesMu.Lock()
  579. activeChallenges[chal.Identifier.Value] = Challenge{Challenge: chal}
  580. activeChallengesMu.Unlock()
  581. return sw.Solver.Present(ctx, chal)
  582. }
  583. func (sw solverWrapper) Wait(ctx context.Context, chal acme.Challenge) error {
  584. if waiter, ok := sw.Solver.(acmez.Waiter); ok {
  585. return waiter.Wait(ctx, chal)
  586. }
  587. return nil
  588. }
  589. func (sw solverWrapper) CleanUp(ctx context.Context, chal acme.Challenge) error {
  590. activeChallengesMu.Lock()
  591. delete(activeChallenges, chal.Identifier.Value)
  592. activeChallengesMu.Unlock()
  593. return sw.Solver.CleanUp(ctx, chal)
  594. }
  595. // Interface guards
  596. var (
  597. _ acmez.Solver = (*solverWrapper)(nil)
  598. _ acmez.Waiter = (*solverWrapper)(nil)
  599. _ acmez.Waiter = (*distributedSolver)(nil)
  600. )