@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 911 lines 33.2 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
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
15 package caddyhttp
16
17 import (
18 "fmt"
19 "net/http"
20 "slices"
21 "strconv"
22 "strings"
23
24 "github.com/caddyserver/certmagic"
25 "go.uber.org/zap"
26
27 "github.com/caddyserver/caddy/v2"
28 "github.com/caddyserver/caddy/v2/internal"
29 "github.com/caddyserver/caddy/v2/modules/caddytls"
30 )
31
32 // AutoHTTPSConfig is used to disable automatic HTTPS
33 // or certain aspects of it for a specific server.
34 // HTTPS is enabled automatically and by default when
35 // qualifying hostnames are available from the config.
36 type AutoHTTPSConfig struct {
37 // If true, automatic HTTPS will be entirely disabled,
38 // including certificate management and redirects.
39 Disabled bool `json:"disable,omitempty"`
40
41 // If true, only automatic HTTP->HTTPS redirects will
42 // be disabled, but other auto-HTTPS features will
43 // remain enabled.
44 DisableRedir bool `json:"disable_redirects,omitempty"`
45
46 // If true, automatic certificate management will be
47 // disabled, but other auto-HTTPS features will
48 // remain enabled.
49 DisableCerts bool `json:"disable_certificates,omitempty"`
50
51 // Hosts/domain names listed here will not be included
52 // in automatic HTTPS (they will not have certificates
53 // loaded nor redirects applied).
54 Skip []string `json:"skip,omitempty"`
55
56 // Hosts/domain names listed here will still be enabled
57 // for automatic HTTPS (unless in the Skip list), except
58 // that certificates will not be provisioned and managed
59 // for these names.
60 SkipCerts []string `json:"skip_certificates,omitempty"`
61
62 // By default, automatic HTTPS will obtain and renew
63 // certificates for qualifying hostnames. However, if
64 // a certificate with a matching SAN is already loaded
65 // into the cache, certificate management will not be
66 // enabled. To force automated certificate management
67 // regardless of loaded certificates, set this to true.
68 IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"`
69 }
70
71 // automaticHTTPSPhase1 provisions all route matchers, determines
72 // which domain names found in the routes qualify for automatic
73 // HTTPS, and sets up HTTP->HTTPS redirects. This phase must occur
74 // at the beginning of provisioning, because it may add routes and
75 // even servers to the app, which still need to be set up with the
76 // rest of them during provisioning.
77 func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) error {
78 logger := app.logger.Named("auto_https")
79
80 // this map acts as a set to store the domain names
81 // for which we will manage certificates automatically
82 uniqueDomainsForCerts := make(map[string]struct{})
83
84 // this maps domain names for automatic HTTP->HTTPS
85 // redirects to their destination server addresses
86 // (there might be more than 1 if bind is used; see
87 // https://github.com/caddyserver/caddy/issues/3443)
88 redirDomains := make(map[string][]caddy.NetworkAddress)
89
90 // the log configuration for an HTTPS enabled server
91 var logCfg *ServerLogConfig
92
93 // Sort server names to ensure deterministic iteration.
94 // This prevents race conditions where the order of server processing
95 // could affect which server gets assigned the HTTP->HTTPS redirect listener.
96 srvNames := make([]string, 0, len(app.Servers))
97 for name := range app.Servers {
98 srvNames = append(srvNames, name)
99 }
100 slices.Sort(srvNames)
101 for _, srvName := range srvNames {
102 srv := app.Servers[srvName]
103 // as a prerequisite, provision route matchers; this is
104 // required for all routes on all servers, and must be
105 // done before we attempt to do phase 1 of auto HTTPS,
106 // since we have to access the decoded host matchers the
107 // handlers will be provisioned later
108 if srv.Routes != nil {
109 err := srv.Routes.ProvisionMatchers(ctx)
110 if err != nil {
111 return fmt.Errorf("server %s: setting up route matchers: %v", srvName, err)
112 }
113 }
114
115 // prepare for automatic HTTPS
116 if srv.AutoHTTPS == nil {
117 srv.AutoHTTPS = new(AutoHTTPSConfig)
118 }
119 if srv.AutoHTTPS.Disabled {
120 logger.Info("automatic HTTPS is completely disabled for server", zap.String("server_name", srvName))
121 continue
122 }
123
124 // skip if all listeners use the HTTP port
125 if !srv.listenersUseAnyPortOtherThan(app.httpPort()) {
126 logger.Warn("server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server",
127 zap.String("server_name", srvName),
128 zap.Int("http_port", app.httpPort()),
129 )
130 srv.AutoHTTPS.Disabled = true
131 continue
132 }
133
134 // if all listeners are on the HTTPS port, make sure
135 // there is at least one TLS connection policy; it
136 // should be obvious that they want to use TLS without
137 // needing to specify one empty policy to enable it
138 if srv.TLSConnPolicies == nil &&
139 !srv.listenersUseAnyPortOtherThan(app.httpsPort()) {
140 logger.Info("server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS",
141 zap.String("server_name", srvName),
142 zap.Int("https_port", app.httpsPort()),
143 )
144 srv.TLSConnPolicies = caddytls.ConnectionPolicies{new(caddytls.ConnectionPolicy)}
145 }
146
147 // find all qualifying domain names (deduplicated) in this server
148 // (this is where we need the provisioned, decoded request matchers)
149 serverDomainSet := make(map[string]struct{})
150 for routeIdx, route := range srv.Routes {
151 for matcherSetIdx, matcherSet := range route.MatcherSets {
152 for matcherIdx, m := range matcherSet {
153 if hm, ok := m.(*MatchHost); ok {
154 for hostMatcherIdx, d := range *hm {
155 var err error
156 d, err = repl.ReplaceOrErr(d, true, false)
157 if err != nil {
158 return fmt.Errorf("%s: route %d, matcher set %d, matcher %d, host matcher %d: %v",
159 srvName, routeIdx, matcherSetIdx, matcherIdx, hostMatcherIdx, err)
160 }
161 if !slices.Contains(srv.AutoHTTPS.Skip, d) {
162 serverDomainSet[d] = struct{}{}
163 }
164 }
165 }
166 }
167 }
168 }
169
170 // build the list of domains that could be used with ECH (if enabled)
171 // so the TLS app can know to publish ECH configs for them
172 echDomains := make([]string, 0, len(serverDomainSet))
173 for d := range serverDomainSet {
174 echDomains = append(echDomains, d)
175 }
176 app.tlsApp.RegisterServerNames(echDomains, httpsRRALPNs(srv))
177
178 // nothing more to do here if there are no domains that qualify for
179 // automatic HTTPS and there are no explicit TLS connection policies:
180 // if there is at least one domain but no TLS conn policy (F&&T), we'll
181 // add one below; if there are no domains but at least one TLS conn
182 // policy (meaning TLS is enabled) (T&&F), it could be a catch-all with
183 // on-demand TLS -- and in that case we would still need HTTP->HTTPS
184 // redirects, which we set up below; hence these two conditions
185 if len(serverDomainSet) == 0 && len(srv.TLSConnPolicies) == 0 {
186 continue
187 }
188
189 // clone the logger so we can apply it to the HTTP server
190 // (not sure if necessary to clone it; but probably safer)
191 // (we choose one log cfg arbitrarily; not sure which is best)
192 if srv.Logs != nil {
193 logCfg = srv.Logs.clone()
194 }
195
196 // for all the hostnames we found, filter them so we have
197 // a deduplicated list of names for which to obtain certs
198 // (only if cert management not disabled for this server)
199 if srv.AutoHTTPS.DisableCerts {
200 logger.Warn("skipping automated certificate management for server because it is disabled", zap.String("server_name", srvName))
201 } else {
202 for d := range serverDomainSet {
203 if certmagic.SubjectQualifiesForCert(d) &&
204 !slices.Contains(srv.AutoHTTPS.SkipCerts, d) {
205 // if a certificate for this name is already loaded,
206 // don't obtain another one for it, unless we are
207 // supposed to ignore loaded certificates
208 if !srv.AutoHTTPS.IgnoreLoadedCerts && app.tlsApp.HasCertificateForSubject(d) {
209 logger.Info("skipping automatic certificate management because one or more matching certificates are already loaded",
210 zap.String("domain", d),
211 zap.String("server_name", srvName),
212 )
213 continue
214 }
215
216 // most clients don't accept wildcards like *.tld... we
217 // can handle that, but as a courtesy, warn the user
218 if strings.Contains(d, "*") &&
219 strings.Count(strings.Trim(d, "."), ".") == 1 {
220 logger.Warn("most clients do not trust second-level wildcard certificates (*.tld)",
221 zap.String("domain", d))
222 }
223
224 uniqueDomainsForCerts[d] = struct{}{}
225 }
226 }
227 }
228
229 // tell the server to use TLS if it is not already doing so
230 if srv.TLSConnPolicies == nil {
231 srv.TLSConnPolicies = caddytls.ConnectionPolicies{new(caddytls.ConnectionPolicy)}
232 }
233
234 // nothing left to do if auto redirects are disabled
235 if srv.AutoHTTPS.DisableRedir {
236 logger.Info("automatic HTTP->HTTPS redirects are disabled", zap.String("server_name", srvName))
237 continue
238 }
239
240 logger.Info("enabling automatic HTTP->HTTPS redirects", zap.String("server_name", srvName))
241
242 // create HTTP->HTTPS redirects
243 for _, listenAddr := range srv.Listen {
244 // figure out the address we will redirect to...
245 addr, err := caddy.ParseNetworkAddress(listenAddr)
246 if err != nil {
247 msg := "%s: invalid listener address: %v"
248 if strings.Count(listenAddr, ":") > 1 {
249 msg = msg + ", there are too many colons, so the port is ambiguous. Did you mean to wrap the IPv6 address with [] brackets?"
250 }
251 return fmt.Errorf(msg, srvName, listenAddr)
252 }
253
254 // this address might not have a hostname, i.e. might be a
255 // catch-all address for a particular port; we need to keep
256 // track if it is, so we can set up redirects for it anyway
257 // (e.g. the user might have enabled on-demand TLS); we use
258 // an empty string to indicate a catch-all, which we have to
259 // treat special later
260 if len(serverDomainSet) == 0 {
261 app.recordAutoHTTPSRedirectAddress(redirDomains, "", addr)
262 continue
263 }
264
265 // ...and associate it with each domain in this server
266 for d := range serverDomainSet {
267 app.recordAutoHTTPSRedirectAddress(redirDomains, d, addr)
268 }
269 }
270 }
271
272 // if all servers have auto_https disabled and no domains need certs,
273 // skip the rest of the TLS automation setup to avoid creating
274 // unnecessary PKI infrastructure and automation policies
275 allServersDisabled := true
276 for _, srv := range app.Servers {
277 if srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled {
278 allServersDisabled = false
279 break
280 }
281 }
282
283 if allServersDisabled && len(uniqueDomainsForCerts) == 0 {
284 logger.Debug("all servers have automatic HTTPS disabled and no domains need certificates, skipping TLS automation setup")
285 return nil
286 }
287
288 // we now have a list of all the unique names for which we need certs
289 var internal, tailscale []string
290 uniqueDomainsLoop:
291 for d := range uniqueDomainsForCerts {
292 // some names we've found might already have automation policies
293 // explicitly specified for them; we should exclude those from
294 // our hidden/implicit policy, since applying a name to more than
295 // one automation policy would be confusing and an error
296 if app.tlsApp.Automation != nil {
297 for _, ap := range app.tlsApp.Automation.Policies {
298 for _, apHost := range ap.Subjects() {
299 if apHost == d {
300 // if the automation policy has all internal subjects but no issuers,
301 // it will default to CertMagic's issuers which are public CAs; use
302 // our internal issuer instead
303 if len(ap.Issuers) == 0 && ap.AllInternalSubjects() {
304 iss := new(caddytls.InternalIssuer)
305 if err := iss.Provision(ctx); err != nil {
306 return err
307 }
308 ap.Issuers = append(ap.Issuers, iss)
309 }
310 continue uniqueDomainsLoop
311 }
312 }
313 }
314 }
315
316 // if no automation policy exists for the name yet, we will associate it with an implicit one;
317 // we handle tailscale domains specially, and we also separate out identifiers that need the
318 // internal issuer (self-signed certs); certmagic does not consider public IP addresses to be
319 // disqualified for public certs, because there are public CAs that will issue certs for IPs.
320 // However, with auto-HTTPS, many times there is no issuer explicitly defined, and the default
321 // issuers do not (currently, as of 2024) issue IP certificates; so assign all IP subjects to
322 // the internal issuer when there are no explicit automation policies
323 shouldUseInternal := func(ident string) bool {
324 usingDefaultIssuersAndIsIP := certmagic.SubjectIsIP(ident) &&
325 (app.tlsApp == nil || app.tlsApp.Automation == nil || len(app.tlsApp.Automation.Policies) == 0)
326 return !certmagic.SubjectQualifiesForPublicCert(d) || usingDefaultIssuersAndIsIP
327 }
328 if isTailscaleDomain(d) {
329 tailscale = append(tailscale, d)
330 delete(uniqueDomainsForCerts, d) // not managed by us; handled separately
331 } else if shouldUseInternal(d) {
332 internal = append(internal, d)
333 }
334 }
335
336 // ensure there is an automation policy to handle these certs
337 err := app.createAutomationPolicies(ctx, internal, tailscale)
338 if err != nil {
339 return err
340 }
341
342 // we need to reduce the mapping, i.e. group domains by address
343 // since new routes are appended to servers by their address
344 domainsByAddr := make(map[string][]string)
345 for domain, addrs := range redirDomains {
346 for _, addr := range addrs {
347 addrStr := addr.String()
348 domainsByAddr[addrStr] = append(domainsByAddr[addrStr], domain)
349 }
350 }
351
352 // these keep track of the redirect server address(es)
353 // and the routes for those servers which actually
354 // respond with the redirects
355 redirServerAddrs := make(map[string]struct{})
356 redirServers := make(map[string][]Route)
357 var redirRoutes RouteList
358
359 for addrStr, domains := range domainsByAddr {
360 // build the matcher set for this redirect route; (note that we happen
361 // to bypass Provision and Validate steps for these matcher modules)
362 matcherSet := MatcherSet{MatchProtocol("http")}
363 // match on known domain names, unless it's our special case of a
364 // catch-all which is an empty string (common among catch-all sites
365 // that enable on-demand TLS for yet-unknown domain names)
366 if len(domains) != 1 || domains[0] != "" {
367 matcherSet = append(matcherSet, MatchHost(domains))
368 }
369
370 addr, err := caddy.ParseNetworkAddress(addrStr)
371 if err != nil {
372 return err
373 }
374 redirRoute := app.makeRedirRoute(addr.StartPort, matcherSet)
375
376 // use the network/host information from the address,
377 // but change the port to the HTTP port then rebuild
378 redirAddr := addr
379 redirAddr.StartPort = uint(app.httpPort())
380 redirAddr.EndPort = redirAddr.StartPort
381 redirAddrStr := redirAddr.String()
382
383 redirServers[redirAddrStr] = append(redirServers[redirAddrStr], redirRoute)
384 }
385
386 // on-demand TLS means that hostnames may be used which are not
387 // explicitly defined in the config, and we still need to redirect
388 // those; so we can append a single catch-all route (notice there
389 // is no Host matcher) after the other redirect routes which will
390 // allow us to handle unexpected/new hostnames... however, it's
391 // not entirely clear what the redirect destination should be,
392 // so I'm going to just hard-code the app's HTTPS port and call
393 // it good for now...
394 // TODO: This implies that all plaintext requests will be blindly
395 // redirected to their HTTPS equivalent, even if this server
396 // doesn't handle that hostname at all; I don't think this is a
397 // bad thing, and it also obscures the actual hostnames that this
398 // server is configured to match on, which may be desirable, but
399 // it's not something that should be relied on. We can change this
400 // if we want to.
401 appendCatchAll := func(routes []Route) []Route {
402 return append(routes, app.makeRedirRoute(uint(app.httpsPort()), MatcherSet{MatchProtocol("http")}))
403 }
404
405 // Sort redirect addresses to ensure deterministic process
406 redirServerAddrsSorted := make([]string, 0, len(redirServers))
407 for addr := range redirServers {
408 redirServerAddrsSorted = append(redirServerAddrsSorted, addr)
409 }
410 slices.Sort(redirServerAddrsSorted)
411
412 redirServersLoop:
413 for _, redirServerAddr := range redirServerAddrsSorted {
414 routes := redirServers[redirServerAddr]
415 // for each redirect listener, see if there's already a
416 // server configured to listen on that exact address; if so,
417 // insert the redirect route to the end of its route list
418 // after any other routes with host matchers; otherwise,
419 // we'll create a new server for all the listener addresses
420 // that are unused and serve the remaining redirects from it
421
422 // Sort redirect routes by host specificity to ensure exact matches
423 // take precedence over wildcards, preventing ambiguous routing.
424 slices.SortFunc(routes, func(a, b Route) int {
425 hostA := getFirstHostFromRoute(a)
426 hostB := getFirstHostFromRoute(b)
427
428 // Catch-all routes (empty host) have the lowest priority
429 if hostA == "" && hostB != "" {
430 return 1
431 }
432 if hostB == "" && hostA != "" {
433 return -1
434 }
435
436 hasWildcardA := strings.Contains(hostA, "*")
437 hasWildcardB := strings.Contains(hostB, "*")
438
439 // Exact domains take precedence over wildcards
440 if !hasWildcardA && hasWildcardB {
441 return -1
442 }
443 if hasWildcardA && !hasWildcardB {
444 return 1
445 }
446
447 // If both are exact or both are wildcards, the longer one is more specific
448 if len(hostA) != len(hostB) {
449 return len(hostB) - len(hostA)
450 }
451
452 // Tie-breaker: alphabetical order to ensure determinism
453 return strings.Compare(hostA, hostB)
454 })
455
456 // Use the sorted srvNames to consistently find the target server
457 for _, srvName := range srvNames {
458 srv := app.Servers[srvName]
459 // only look at servers which listen on an address which
460 // we want to add redirects to
461 if !srv.hasListenerAddress(redirServerAddr) {
462 continue
463 }
464
465 // find the index of the route after the last route with a host
466 // matcher, then insert the redirects there, but before any
467 // user-defined catch-all routes
468 // see https://github.com/caddyserver/caddy/issues/3212
469 insertIndex := srv.findLastRouteWithHostMatcher()
470
471 // add the redirects at the insert index, except for when
472 // we have a catch-all for HTTPS, in which case the user's
473 // defined catch-all should take precedence. See #4829
474 if len(uniqueDomainsForCerts) != 0 {
475 srv.Routes = append(srv.Routes[:insertIndex], append(routes, srv.Routes[insertIndex:]...)...)
476 }
477
478 // append our catch-all route in case the user didn't define their own
479 srv.Routes = appendCatchAll(srv.Routes)
480
481 continue redirServersLoop
482 }
483
484 // no server with this listener address exists;
485 // save this address and route for custom server
486 redirServerAddrs[redirServerAddr] = struct{}{}
487 redirRoutes = append(redirRoutes, routes...)
488 }
489
490 // if there are routes remaining which do not belong
491 // in any existing server, make our own to serve the
492 // rest of the redirects
493 if len(redirServerAddrs) > 0 {
494 redirServerAddrsList := make([]string, 0, len(redirServerAddrs))
495 for a := range redirServerAddrs {
496 redirServerAddrsList = append(redirServerAddrsList, a)
497 }
498 app.Servers["remaining_auto_https_redirects"] = &Server{
499 Listen: redirServerAddrsList,
500 Routes: appendCatchAll(redirRoutes),
501 Logs: logCfg,
502 }
503 }
504
505 // persist the domains/IPs we're managing certs for through provisioning/startup
506 app.allCertDomains = uniqueDomainsForCerts
507
508 logger.Debug("adjusted config",
509 zap.Reflect("tls", app.tlsApp),
510 zap.Reflect("http", app))
511
512 return nil
513 }
514
515 // recordAutoHTTPSRedirectAddress stores redirect destinations for one domain
516 // using a single winning port while keeping all bind addresses on that port.
517 //
518 // This is needed to avoid two opposite regressions in auto-HTTPS redirects:
519 // preserve all listener addresses when a site binds multiple addresses on the
520 // same HTTPS port, but do not mix in alternate HTTPS ports when the canonical
521 // app HTTPS port is also available.
522 func (app *App) recordAutoHTTPSRedirectAddress(redirDomains map[string][]caddy.NetworkAddress, domain string, addr caddy.NetworkAddress) {
523 existing := redirDomains[domain]
524 if len(existing) == 0 {
525 redirDomains[domain] = []caddy.NetworkAddress{addr}
526 return
527 }
528
529 existingPort := existing[0].StartPort
530 if addr.StartPort != existingPort {
531 if addr.StartPort == uint(app.httpsPort()) && existingPort != uint(app.httpsPort()) {
532 redirDomains[domain] = []caddy.NetworkAddress{addr}
533 }
534 return
535 }
536
537 if slices.Contains(existing, addr) {
538 return
539 }
540
541 redirDomains[domain] = append(existing, addr)
542 }
543
544 func (app *App) makeRedirRoute(redirToPort uint, matcherSet MatcherSet) Route {
545 redirTo := "https://{http.request.host}"
546
547 // since this is an external redirect, we should only append an explicit
548 // port if we know it is not the officially standardized HTTPS port, and,
549 // notably, also not the port that Caddy thinks is the HTTPS port (the
550 // configurable HTTPSPort parameter) - we can't change the standard HTTPS
551 // port externally, so that config parameter is for internal use only;
552 // we also do not append the port if it happens to be the HTTP port as
553 // well, obviously (for example, user defines the HTTP port explicitly
554 // in the list of listen addresses for a server)
555 if redirToPort != uint(app.httpPort()) &&
556 redirToPort != uint(app.httpsPort()) &&
557 redirToPort != DefaultHTTPPort &&
558 redirToPort != DefaultHTTPSPort {
559 redirTo += ":" + strconv.Itoa(int(redirToPort))
560 }
561
562 redirTo += "{http.request.uri}"
563 return Route{
564 MatcherSets: []MatcherSet{matcherSet},
565 Handlers: []MiddlewareHandler{
566 StaticResponse{
567 StatusCode: WeakString(strconv.Itoa(http.StatusPermanentRedirect)),
568 Headers: http.Header{
569 "Location": []string{redirTo},
570 },
571 Close: true,
572 },
573 },
574 }
575 }
576
577 func httpsRRALPNs(srv *Server) []string {
578 alpn := make(map[string]struct{}, 3)
579 if srv.protocol("h3") {
580 alpn["h3"] = struct{}{}
581 }
582 if srv.protocol("h2") {
583 alpn["h2"] = struct{}{}
584 }
585 if srv.protocol("h1") {
586 alpn["http/1.1"] = struct{}{}
587 }
588 return caddytls.OrderedHTTPSRRALPN(alpn)
589 }
590
591 // createAutomationPolicies ensures that automated certificates for this
592 // app are managed properly. This adds up to two automation policies:
593 // one for the public names, and one for the internal names. If a catch-all
594 // automation policy exists, it will be shallow-copied and used as the
595 // base for the new ones (this is important for preserving behavior the
596 // user intends to be "defaults").
597 func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames, tailscaleNames []string) error {
598 // before we begin, loop through the existing automation policies
599 // and, for any ACMEIssuers we find, make sure they're filled in
600 // with default values that might be specified in our HTTP app; also
601 // look for a base (or "catch-all" / default) automation policy,
602 // which we're going to essentially require, to make sure it has
603 // those defaults, too
604 var basePolicy *caddytls.AutomationPolicy
605 var foundBasePolicy bool
606 if app.tlsApp.Automation == nil {
607 // we will expect this to not be nil from now on
608 app.tlsApp.Automation = new(caddytls.AutomationConfig)
609 }
610 for _, ap := range app.tlsApp.Automation.Policies {
611 // on-demand policies can have the tailscale manager added implicitly
612 // if there's no explicit manager configured -- for convenience
613 if ap.OnDemand && len(ap.Managers) == 0 {
614 var ts caddytls.Tailscale
615 if err := ts.Provision(ctx); err != nil {
616 return err
617 }
618 ap.Managers = []certmagic.Manager{ts}
619
620 // must reprovision the automation policy so that the underlying
621 // CertMagic config knows about the updated Managers
622 if err := ap.Provision(app.tlsApp); err != nil {
623 return fmt.Errorf("re-provisioning automation policy: %v", err)
624 }
625 }
626
627 // set up default issuer -- honestly, this is only
628 // really necessary because the HTTP app is opinionated
629 // and has settings which could be inferred as new
630 // defaults for the ACMEIssuer in the TLS app (such as
631 // what the HTTP and HTTPS ports are)
632 if ap.Issuers == nil {
633 var err error
634 ap.Issuers, err = caddytls.DefaultIssuersProvisioned(ctx)
635 if err != nil {
636 return err
637 }
638 }
639 for _, iss := range ap.Issuers {
640 if acmeIssuer, ok := iss.(acmeCapable); ok {
641 err := app.fillInACMEIssuer(acmeIssuer.GetACMEIssuer())
642 if err != nil {
643 return err
644 }
645 }
646 }
647
648 // while we're here, is this the catch-all/base policy?
649 if !foundBasePolicy && len(ap.SubjectsRaw) == 0 {
650 basePolicy = ap
651 foundBasePolicy = true
652 }
653 }
654
655 // Ensure automation policies' CertMagic configs are rebuilt when
656 // ACME issuer templates may have been modified above (for example,
657 // alternate ports filled in by the HTTP app). If a policy is already
658 // provisioned, perform a lightweight rebuild of the CertMagic config
659 // so issuers receive SetConfig with the updated templates; otherwise
660 // run a normal Provision to initialize the policy.
661 for i, ap := range app.tlsApp.Automation.Policies {
662 // If the policy is already provisioned, rebuild only the CertMagic
663 // config so issuers get SetConfig with updated templates. Otherwise
664 // provision the policy normally (which may load modules).
665 if ap.IsProvisioned() {
666 if err := ap.RebuildCertMagic(app.tlsApp); err != nil {
667 return fmt.Errorf("rebuilding certmagic config for automation policy %d: %v", i, err)
668 }
669 } else {
670 if err := ap.Provision(app.tlsApp); err != nil {
671 return fmt.Errorf("provisioning automation policy %d after auto-HTTPS defaults: %v", i, err)
672 }
673 }
674 }
675
676 if basePolicy == nil {
677 // no base policy found; we will make one
678 basePolicy = new(caddytls.AutomationPolicy)
679 }
680
681 // if the basePolicy has an existing ACMEIssuer (particularly to
682 // include any type that embeds/wraps an ACMEIssuer), let's use it
683 // (I guess we just use the first one?), otherwise we'll make one
684 var baseACMEIssuer *caddytls.ACMEIssuer
685 for _, iss := range basePolicy.Issuers {
686 if acmeWrapper, ok := iss.(acmeCapable); ok {
687 baseACMEIssuer = acmeWrapper.GetACMEIssuer()
688 break
689 }
690 }
691 if baseACMEIssuer == nil {
692 // note that this happens if basePolicy.Issuers is empty
693 // OR if it is not empty but does not have not an ACMEIssuer
694 baseACMEIssuer = new(caddytls.ACMEIssuer)
695 }
696
697 // if there was a base policy to begin with, we already
698 // filled in its issuer's defaults; if there wasn't, we
699 // still need to do that
700 if !foundBasePolicy {
701 err := app.fillInACMEIssuer(baseACMEIssuer)
702 if err != nil {
703 return err
704 }
705 }
706
707 // never overwrite any other issuer that might already be configured
708 if basePolicy.Issuers == nil {
709 var err error
710 basePolicy.Issuers, err = caddytls.DefaultIssuersProvisioned(ctx)
711 if err != nil {
712 return err
713 }
714 for _, iss := range basePolicy.Issuers {
715 if acmeIssuer, ok := iss.(acmeCapable); ok {
716 err := app.fillInACMEIssuer(acmeIssuer.GetACMEIssuer())
717 if err != nil {
718 return err
719 }
720 }
721 }
722 }
723
724 if !foundBasePolicy {
725 // there was no base policy to begin with, so add
726 // our base/catch-all policy - this will serve the
727 // public-looking names as well as any other names
728 // that don't match any other policy
729 err := app.tlsApp.AddAutomationPolicy(basePolicy)
730 if err != nil {
731 return err
732 }
733 } else {
734 // a base policy already existed; we might have
735 // changed it, so re-provision it
736 err := basePolicy.Provision(app.tlsApp)
737 if err != nil {
738 return err
739 }
740 }
741
742 // public names will be taken care of by the base (catch-all)
743 // policy, which we've ensured exists if not already specified;
744 // internal names, however, need to be handled by an internal
745 // issuer, which we need to make a new policy for, scoped to
746 // just those names (yes, this logic is a bit asymmetric, but
747 // it works, because our assumed/natural default issuer is an
748 // ACME issuer)
749 if len(internalNames) > 0 {
750 internalIssuer := new(caddytls.InternalIssuer)
751
752 // shallow-copy the base policy; we want to inherit
753 // from it, not replace it... this takes two lines to
754 // overrule compiler optimizations
755 policyCopy := *basePolicy
756 newPolicy := &policyCopy
757
758 // very important to provision the issuer, since we
759 // are bypassing the JSON-unmarshaling step
760 if err := internalIssuer.Provision(ctx); err != nil {
761 return err
762 }
763
764 // this policy should apply only to the given names
765 // and should use our issuer -- yes, this overrides
766 // any issuer that may have been set in the base
767 // policy, but we do this because these names do not
768 // already have a policy associated with them, which
769 // is easy to do; consider the case of a Caddyfile
770 // that has only "localhost" as a name, but sets the
771 // default/global ACME CA to the Let's Encrypt staging
772 // endpoint... they probably don't intend to change the
773 // fundamental set of names that setting applies to,
774 // rather they just want to change the CA for the set
775 // of names that would normally use the production API;
776 // anyway, that gets into the weeds a bit...
777 newPolicy.SubjectsRaw = internalNames
778 newPolicy.Issuers = []certmagic.Issuer{internalIssuer}
779 err := app.tlsApp.AddAutomationPolicy(newPolicy)
780 if err != nil {
781 return err
782 }
783 }
784
785 // tailscale names go in their own automation policies because
786 // they require on-demand TLS to be enabled, which we obviously
787 // can't enable for everything
788 if len(tailscaleNames) > 0 {
789 policyCopy := *basePolicy
790 newPolicy := &policyCopy
791
792 var ts caddytls.Tailscale
793 if err := ts.Provision(ctx); err != nil {
794 return err
795 }
796
797 newPolicy.SubjectsRaw = tailscaleNames
798 newPolicy.Issuers = nil
799 newPolicy.Managers = append(newPolicy.Managers, ts)
800 err := app.tlsApp.AddAutomationPolicy(newPolicy)
801 if err != nil {
802 return err
803 }
804 }
805
806 // we just changed a lot of stuff, so double-check that it's all good
807 err := app.tlsApp.Validate()
808 if err != nil {
809 return err
810 }
811
812 return nil
813 }
814
815 // fillInACMEIssuer fills in default values into acmeIssuer that
816 // are defined in app; these values at time of writing are just
817 // app.HTTPPort and app.HTTPSPort, which are used by ACMEIssuer.
818 // Sure, we could just use the global/CertMagic defaults, but if
819 // a user has configured those ports in the HTTP app, it makes
820 // sense to use them in the TLS app too, even if they forgot (or
821 // were too lazy, like me) to set it in each automation policy
822 // that uses it -- this just makes things a little less tedious
823 // for the user, so they don't have to repeat those ports in
824 // potentially many places. This function never steps on existing
825 // config values. If any changes are made, acmeIssuer is
826 // reprovisioned. acmeIssuer must not be nil.
827 func (app *App) fillInACMEIssuer(acmeIssuer *caddytls.ACMEIssuer) error {
828 if app.HTTPPort > 0 || app.HTTPSPort > 0 {
829 if acmeIssuer.Challenges == nil {
830 acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
831 }
832 }
833 if app.HTTPPort > 0 {
834 if acmeIssuer.Challenges.HTTP == nil {
835 acmeIssuer.Challenges.HTTP = new(caddytls.HTTPChallengeConfig)
836 }
837 // don't overwrite existing explicit config
838 if acmeIssuer.Challenges.HTTP.AlternatePort == 0 {
839 acmeIssuer.Challenges.HTTP.AlternatePort = app.HTTPPort
840 }
841 }
842 if app.HTTPSPort > 0 {
843 if acmeIssuer.Challenges.TLSALPN == nil {
844 acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig)
845 }
846 // don't overwrite existing explicit config
847 if acmeIssuer.Challenges.TLSALPN.AlternatePort == 0 {
848 acmeIssuer.Challenges.TLSALPN.AlternatePort = app.HTTPSPort
849 }
850 }
851 // we must provision all ACME issuers, even if nothing
852 // was changed, because we don't know if they are new
853 // and haven't been provisioned yet; if an ACME issuer
854 // never gets provisioned, its Agree field stays false,
855 // which leads to, um, problems later on
856 return acmeIssuer.Provision(app.ctx)
857 }
858
859 // automaticHTTPSPhase2 begins certificate management for
860 // all names in the qualifying domain set for each server.
861 // This phase must occur after provisioning and at the end
862 // of app start, after all the servers have been started.
863 // Doing this last ensures that there won't be any race
864 // for listeners on the HTTP or HTTPS ports when management
865 // is async (if CertMagic's solvers bind to those ports
866 // first, then our servers would fail to bind to them,
867 // which would be bad, since CertMagic's bindings are
868 // temporary and don't serve the user's sites!).
869 func (app *App) automaticHTTPSPhase2() error {
870 if len(app.allCertDomains) == 0 {
871 return nil
872 }
873 app.logger.Info("enabling automatic TLS certificate management",
874 zap.Strings("domains", internal.MaxSizeSubjectsListForLog(app.allCertDomains, 1000)),
875 )
876 err := app.tlsApp.Manage(app.allCertDomains)
877 if err != nil {
878 return fmt.Errorf("managing certificates for %d domains: %s", len(app.allCertDomains), err)
879 }
880 app.allCertDomains = nil // no longer needed; allow GC to deallocate
881 return nil
882 }
883
884 func isTailscaleDomain(name string) bool {
885 return strings.HasSuffix(strings.ToLower(name), ".ts.net")
886 }
887
888 type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer }
889
890 // getFirstHostFromRoute traverses a route's matchers to find the Host rule.
891 // Since we are dealing with internally generated redirect routes, the host
892 // is typically the first string within the MatchHost.
893 func getFirstHostFromRoute(r Route) string {
894 for _, matcherSet := range r.MatcherSets {
895 for _, m := range matcherSet {
896 // Check if the matcher is of type MatchHost (value or pointer)
897 switch hm := m.(type) {
898 case MatchHost:
899 if len(hm) > 0 {
900 return hm[0]
901 }
902 case *MatchHost:
903 if len(*hm) > 0 {
904 return (*hm)[0]
905 }
906 }
907 }
908 }
909 // Return an empty string if it's a catch-all route (no specific host)
910 return ""
911 }