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.

acmeclient.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  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. "crypto/x509"
  19. "fmt"
  20. weakrand "math/rand"
  21. "net"
  22. "net/http"
  23. "net/url"
  24. "strconv"
  25. "strings"
  26. "sync"
  27. "time"
  28. "github.com/mholt/acmez"
  29. "github.com/mholt/acmez/acme"
  30. "go.uber.org/zap"
  31. )
  32. func init() {
  33. weakrand.Seed(time.Now().UnixNano())
  34. }
  35. // acmeClient holds state necessary to perform ACME operations
  36. // for certificate management with an ACME account. Call
  37. // ACMEManager.newACMEClientWithAccount() to get a valid one.
  38. type acmeClient struct {
  39. mgr *ACMEManager
  40. acmeClient *acmez.Client
  41. account acme.Account
  42. }
  43. // newACMEClientWithAccount creates an ACME client ready to use with an account, including
  44. // loading one from storage or registering a new account with the CA if necessary. If
  45. // useTestCA is true, am.TestCA will be used if set; otherwise, the primary CA will be used.
  46. func (am *ACMEManager) newACMEClientWithAccount(ctx context.Context, useTestCA, interactive bool) (*acmeClient, error) {
  47. // first, get underlying ACME client
  48. client, err := am.newACMEClient(useTestCA)
  49. if err != nil {
  50. return nil, err
  51. }
  52. // look up or create the ACME account
  53. var account acme.Account
  54. if am.AccountKeyPEM != "" {
  55. account, err = am.GetAccount(ctx, []byte(am.AccountKeyPEM))
  56. } else {
  57. account, err = am.getAccount(client.Directory, am.Email)
  58. }
  59. if err != nil {
  60. return nil, fmt.Errorf("getting ACME account: %v", err)
  61. }
  62. // register account if it is new
  63. if account.Status == "" {
  64. if am.NewAccountFunc != nil {
  65. account, err = am.NewAccountFunc(ctx, am, account)
  66. if err != nil {
  67. return nil, fmt.Errorf("account pre-registration callback: %v", err)
  68. }
  69. }
  70. // agree to terms
  71. if interactive {
  72. if !am.Agreed {
  73. var termsURL string
  74. dir, err := client.GetDirectory(ctx)
  75. if err != nil {
  76. return nil, fmt.Errorf("getting directory: %w", err)
  77. }
  78. if dir.Meta != nil {
  79. termsURL = dir.Meta.TermsOfService
  80. }
  81. if termsURL != "" {
  82. am.Agreed = am.askUserAgreement(termsURL)
  83. if !am.Agreed {
  84. return nil, fmt.Errorf("user must agree to CA terms")
  85. }
  86. }
  87. }
  88. } else {
  89. // can't prompt a user who isn't there; they should
  90. // have reviewed the terms beforehand
  91. am.Agreed = true
  92. }
  93. account.TermsOfServiceAgreed = am.Agreed
  94. // associate account with external binding, if configured
  95. if am.ExternalAccount != nil {
  96. err := account.SetExternalAccountBinding(ctx, client.Client, *am.ExternalAccount)
  97. if err != nil {
  98. return nil, err
  99. }
  100. }
  101. // create account
  102. account, err = client.NewAccount(ctx, account)
  103. if err != nil {
  104. return nil, fmt.Errorf("registering account %v with server: %w", account.Contact, err)
  105. }
  106. // persist the account to storage
  107. err = am.saveAccount(client.Directory, account)
  108. if err != nil {
  109. return nil, fmt.Errorf("could not save account %v: %v", account.Contact, err)
  110. }
  111. }
  112. c := &acmeClient{
  113. mgr: am,
  114. acmeClient: client,
  115. account: account,
  116. }
  117. return c, nil
  118. }
  119. // newACMEClient creates a new underlying ACME client using the settings in am,
  120. // independent of any particular ACME account. If useTestCA is true, am.TestCA
  121. // will be used if it is set; otherwise, the primary CA will be used.
  122. func (am *ACMEManager) newACMEClient(useTestCA bool) (*acmez.Client, error) {
  123. // ensure defaults are filled in
  124. var caURL string
  125. if useTestCA {
  126. caURL = am.TestCA
  127. }
  128. if caURL == "" {
  129. caURL = am.CA
  130. }
  131. if caURL == "" {
  132. caURL = DefaultACME.CA
  133. }
  134. certObtainTimeout := am.CertObtainTimeout
  135. if certObtainTimeout == 0 {
  136. certObtainTimeout = DefaultACME.CertObtainTimeout
  137. }
  138. // ensure endpoint is secure (assume HTTPS if scheme is missing)
  139. if !strings.Contains(caURL, "://") {
  140. caURL = "https://" + caURL
  141. }
  142. u, err := url.Parse(caURL)
  143. if err != nil {
  144. return nil, err
  145. }
  146. if u.Scheme != "https" && !isLoopback(u.Host) && !isInternal(u.Host) {
  147. return nil, fmt.Errorf("%s: insecure CA URL (HTTPS required)", caURL)
  148. }
  149. // set up the dialers and resolver for the ACME client's HTTP client
  150. dialer := &net.Dialer{
  151. Timeout: 30 * time.Second,
  152. KeepAlive: 2 * time.Minute,
  153. }
  154. if am.Resolver != "" {
  155. dialer.Resolver = &net.Resolver{
  156. PreferGo: true,
  157. Dial: func(ctx context.Context, network, _ string) (net.Conn, error) {
  158. return (&net.Dialer{
  159. Timeout: 15 * time.Second,
  160. }).DialContext(ctx, network, am.Resolver)
  161. },
  162. }
  163. }
  164. // TODO: we could potentially reuse the HTTP transport and client
  165. hc := am.httpClient // TODO: is this racey?
  166. if am.httpClient == nil {
  167. transport := &http.Transport{
  168. Proxy: http.ProxyFromEnvironment,
  169. DialContext: dialer.DialContext,
  170. TLSHandshakeTimeout: 15 * time.Second,
  171. ResponseHeaderTimeout: 15 * time.Second,
  172. ExpectContinueTimeout: 2 * time.Second,
  173. ForceAttemptHTTP2: true,
  174. }
  175. if am.TrustedRoots != nil {
  176. transport.TLSClientConfig = &tls.Config{
  177. RootCAs: am.TrustedRoots,
  178. }
  179. }
  180. hc = &http.Client{
  181. Transport: transport,
  182. Timeout: HTTPTimeout,
  183. }
  184. am.httpClient = hc
  185. }
  186. client := &acmez.Client{
  187. Client: &acme.Client{
  188. Directory: caURL,
  189. PollTimeout: certObtainTimeout,
  190. UserAgent: buildUAString(),
  191. HTTPClient: hc,
  192. },
  193. ChallengeSolvers: make(map[string]acmez.Solver),
  194. }
  195. if am.Logger != nil {
  196. l := am.Logger.Named("acme_client")
  197. client.Client.Logger, client.Logger = l, l
  198. }
  199. // configure challenges (most of the time, DNS challenge is
  200. // exclusive of other ones because it is usually only used
  201. // in situations where the default challenges would fail)
  202. if am.DNS01Solver == nil {
  203. // enable HTTP-01 challenge
  204. if !am.DisableHTTPChallenge {
  205. useHTTPPort := HTTPChallengePort
  206. if HTTPPort > 0 && HTTPPort != HTTPChallengePort {
  207. useHTTPPort = HTTPPort
  208. }
  209. if am.AltHTTPPort > 0 {
  210. useHTTPPort = am.AltHTTPPort
  211. }
  212. client.ChallengeSolvers[acme.ChallengeTypeHTTP01] = distributedSolver{
  213. storage: am.config.Storage,
  214. storageKeyIssuerPrefix: am.storageKeyCAPrefix(client.Directory),
  215. solver: &httpSolver{
  216. acmeManager: am,
  217. address: net.JoinHostPort(am.ListenHost, strconv.Itoa(useHTTPPort)),
  218. },
  219. }
  220. }
  221. // enable TLS-ALPN-01 challenge
  222. if !am.DisableTLSALPNChallenge {
  223. useTLSALPNPort := TLSALPNChallengePort
  224. if HTTPSPort > 0 && HTTPSPort != TLSALPNChallengePort {
  225. useTLSALPNPort = HTTPSPort
  226. }
  227. if am.AltTLSALPNPort > 0 {
  228. useTLSALPNPort = am.AltTLSALPNPort
  229. }
  230. client.ChallengeSolvers[acme.ChallengeTypeTLSALPN01] = distributedSolver{
  231. storage: am.config.Storage,
  232. storageKeyIssuerPrefix: am.storageKeyCAPrefix(client.Directory),
  233. solver: &tlsALPNSolver{
  234. config: am.config,
  235. address: net.JoinHostPort(am.ListenHost, strconv.Itoa(useTLSALPNPort)),
  236. },
  237. }
  238. }
  239. } else {
  240. // use DNS challenge exclusively
  241. client.ChallengeSolvers[acme.ChallengeTypeDNS01] = am.DNS01Solver
  242. }
  243. // wrap solvers in our wrapper so that we can keep track of challenge
  244. // info: this is useful for solving challenges globally as a process;
  245. // for example, usually there is only one process that can solve the
  246. // HTTP and TLS-ALPN challenges, and only one server in that process
  247. // that can bind the necessary port(s), so if a server listening on
  248. // a different port needed a certificate, it would have to know about
  249. // the other server listening on that port, and somehow convey its
  250. // challenge info or share its config, but this isn't always feasible;
  251. // what the wrapper does is it accesses a global challenge memory so
  252. // that unrelated servers in this process can all solve each others'
  253. // challenges without having to know about each other - Caddy's admin
  254. // endpoint uses this functionality since it and the HTTP/TLS modules
  255. // do not know about each other
  256. // (doing this here in a separate loop ensures that even if we expose
  257. // solver config to users later, we will even wrap their own solvers)
  258. for name, solver := range client.ChallengeSolvers {
  259. client.ChallengeSolvers[name] = solverWrapper{solver}
  260. }
  261. return client, nil
  262. }
  263. func (c *acmeClient) throttle(ctx context.Context, names []string) error {
  264. // throttling is scoped to CA + account email
  265. rateLimiterKey := c.acmeClient.Directory + "," + c.mgr.Email
  266. rateLimitersMu.Lock()
  267. rl, ok := rateLimiters[rateLimiterKey]
  268. if !ok {
  269. rl = NewRateLimiter(RateLimitEvents, RateLimitEventsWindow)
  270. rateLimiters[rateLimiterKey] = rl
  271. // TODO: stop rate limiter when it is garbage-collected...
  272. }
  273. rateLimitersMu.Unlock()
  274. if c.mgr.Logger != nil {
  275. c.mgr.Logger.Info("waiting on internal rate limiter",
  276. zap.Strings("identifiers", names),
  277. zap.String("ca", c.acmeClient.Directory),
  278. zap.String("account", c.mgr.Email),
  279. )
  280. }
  281. err := rl.Wait(ctx)
  282. if err != nil {
  283. return err
  284. }
  285. if c.mgr.Logger != nil {
  286. c.mgr.Logger.Info("done waiting on internal rate limiter",
  287. zap.Strings("identifiers", names),
  288. zap.String("ca", c.acmeClient.Directory),
  289. zap.String("account", c.mgr.Email),
  290. )
  291. }
  292. return nil
  293. }
  294. func (c *acmeClient) usingTestCA() bool {
  295. return c.mgr.TestCA != "" && c.acmeClient.Directory == c.mgr.TestCA
  296. }
  297. func (c *acmeClient) revoke(ctx context.Context, cert *x509.Certificate, reason int) error {
  298. return c.acmeClient.RevokeCertificate(ctx, c.account,
  299. cert, c.account.PrivateKey, reason)
  300. }
  301. func buildUAString() string {
  302. ua := "CertMagic"
  303. if UserAgent != "" {
  304. ua = UserAgent + " " + ua
  305. }
  306. return ua
  307. }
  308. // These internal rate limits are designed to prevent accidentally
  309. // firehosing a CA's ACME endpoints. They are not intended to
  310. // replace or replicate the CA's actual rate limits.
  311. //
  312. // Let's Encrypt's rate limits can be found here:
  313. // https://letsencrypt.org/docs/rate-limits/
  314. //
  315. // Currently (as of December 2019), Let's Encrypt's most relevant
  316. // rate limit for large deployments is 300 new orders per account
  317. // per 3 hours (on average, or best case, that's about 1 every 36
  318. // seconds, or 2 every 72 seconds, etc.); but it's not reasonable
  319. // to try to assume that our internal state is the same as the CA's
  320. // (due to process restarts, config changes, failed validations,
  321. // etc.) and ultimately, only the CA's actual rate limiter is the
  322. // authority. Thus, our own rate limiters do not attempt to enforce
  323. // external rate limits. Doing so causes problems when the domains
  324. // are not in our control (i.e. serving customer sites) and/or lots
  325. // of domains fail validation: they clog our internal rate limiter
  326. // and nearly starve out (or at least slow down) the other domains
  327. // that need certificates. Failed transactions are already retried
  328. // with exponential backoff, so adding in rate limiting can slow
  329. // things down even more.
  330. //
  331. // Instead, the point of our internal rate limiter is to avoid
  332. // hammering the CA's endpoint when there are thousands or even
  333. // millions of certificates under management. Our goal is to
  334. // allow small bursts in a relatively short timeframe so as to
  335. // not block any one domain for too long, without unleashing
  336. // thousands of requests to the CA at once.
  337. var (
  338. rateLimiters = make(map[string]*RingBufferRateLimiter)
  339. rateLimitersMu sync.RWMutex
  340. // RateLimitEvents is how many new events can be allowed
  341. // in RateLimitEventsWindow.
  342. RateLimitEvents = 20
  343. // RateLimitEventsWindow is the size of the sliding
  344. // window that throttles events.
  345. RateLimitEventsWindow = 1 * time.Minute
  346. )
  347. // Some default values passed down to the underlying ACME client.
  348. var (
  349. UserAgent string
  350. HTTPTimeout = 30 * time.Second
  351. )