@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 1,322 lines 47.9 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 httpcaddyfile
16
17 import (
18 "bytes"
19 "encoding/json"
20 "fmt"
21 "reflect"
22 "slices"
23 "sort"
24 "strconv"
25 "strings"
26
27 "github.com/caddyserver/certmagic"
28 "github.com/mholt/acmez/v3/acme"
29
30 "github.com/caddyserver/caddy/v2"
31 "github.com/caddyserver/caddy/v2/caddyconfig"
32 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
33 "github.com/caddyserver/caddy/v2/modules/caddytls"
34 )
35
36 func (st ServerType) buildTLSApp(
37 pairings []sbAddrAssociation,
38 options map[string]any,
39 warnings []caddyconfig.Warning,
40 ) (*caddytls.TLS, []caddyconfig.Warning, error) {
41 tlsApp := &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}
42 var certLoaders []caddytls.CertificateLoader
43
44 httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
45 if hp, ok := options["http_port"].(int); ok {
46 httpPort = strconv.Itoa(hp)
47 }
48 autoHTTPS := []string{}
49 if ah, ok := options["auto_https"].([]string); ok {
50 autoHTTPS = ah
51 }
52
53 // find all hosts that share a server block with a hostless
54 // key, so that they don't get forgotten/omitted by auto-HTTPS
55 // (since they won't appear in route matchers)
56 httpsHostsSharedWithHostlessKey := make(map[string]struct{})
57 if !slices.Contains(autoHTTPS, "off") {
58 for _, pair := range pairings {
59 for _, sb := range pair.serverBlocks {
60 for _, addr := range sb.parsedKeys {
61 if addr.Host != "" {
62 continue
63 }
64
65 // this server block has a hostless key, now
66 // go through and add all the hosts to the set
67 for _, otherAddr := range sb.parsedKeys {
68 if otherAddr.Original == addr.Original {
69 continue
70 }
71 if otherAddr.Host != "" && otherAddr.Scheme != "http" && otherAddr.Port != httpPort {
72 httpsHostsSharedWithHostlessKey[otherAddr.Host] = struct{}{}
73 }
74 }
75 break
76 }
77 }
78 }
79 }
80
81 // a catch-all automation policy is used as a "default" for all subjects that
82 // don't have custom configuration explicitly associated with them; this
83 // is only to add if the global settings or defaults are non-empty
84 catchAllAP, err := newBaseAutomationPolicy(options, warnings, false)
85 if err != nil {
86 return nil, warnings, err
87 }
88 if catchAllAP != nil {
89 if tlsApp.Automation == nil {
90 tlsApp.Automation = new(caddytls.AutomationConfig)
91 }
92 tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
93 }
94
95 forcedAutomatedNames := make(map[string]struct{}) // explicitly configured to be automated, even if covered by a wildcard
96
97 for _, p := range pairings {
98 // avoid setting up TLS automation policies for a server that is HTTP-only
99 var addresses []string
100 for _, addressWithProtocols := range p.addressesWithProtocols {
101 addresses = append(addresses, addressWithProtocols.address)
102 }
103 if !listenersUseAnyPortOtherThan(addresses, httpPort) {
104 continue
105 }
106
107 for _, sblock := range p.serverBlocks {
108 // check the scheme of all the site addresses,
109 // skip building AP if they all had http://
110 if sblock.isAllHTTP() {
111 continue
112 }
113
114 // get values that populate an automation policy for this block
115 ap, err := newBaseAutomationPolicy(options, warnings, true)
116 if err != nil {
117 return nil, warnings, err
118 }
119
120 sblockHosts := sblock.hostsFromKeys(false)
121 if len(sblockHosts) == 0 && catchAllAP != nil {
122 ap = catchAllAP
123 }
124
125 // on-demand tls
126 if _, ok := sblock.pile["tls.on_demand"]; ok {
127 ap.OnDemand = true
128 }
129
130 // collect hosts that are forced to have certs automated for their specific name
131 if _, ok := sblock.pile["tls.force_automate"]; ok {
132 for _, host := range sblockHosts {
133 forcedAutomatedNames[host] = struct{}{}
134 }
135 }
136
137 // reuse private keys tls
138 if _, ok := sblock.pile["tls.reuse_private_keys"]; ok {
139 ap.ReusePrivateKeys = true
140 }
141
142 if keyTypeVals, ok := sblock.pile["tls.key_type"]; ok {
143 ap.KeyType = keyTypeVals[0].Value.(string)
144 }
145
146 if renewalWindowRatioVals, ok := sblock.pile["tls.renewal_window_ratio"]; ok {
147 ap.RenewalWindowRatio = renewalWindowRatioVals[0].Value.(float64)
148 } else if globalRenewalWindowRatio, ok := options["renewal_window_ratio"]; ok {
149 ap.RenewalWindowRatio = globalRenewalWindowRatio.(float64)
150 }
151
152 // certificate issuers
153 if issuerVals, ok := sblock.pile["tls.cert_issuer"]; ok {
154 var issuers []certmagic.Issuer
155 for _, issuerVal := range issuerVals {
156 issuers = append(issuers, issuerVal.Value.(certmagic.Issuer))
157 }
158 if ap == catchAllAP && !reflect.DeepEqual(ap.Issuers, issuers) {
159 // this more correctly implements an error check that was removed
160 // below; try it with this config:
161 //
162 // :443 {
163 // bind 127.0.0.1
164 // }
165 //
166 // :443 {
167 // bind ::1
168 // tls {
169 // issuer acme
170 // }
171 // }
172 return nil, warnings, fmt.Errorf("automation policy from site block is also default/catch-all policy because of key without hostname, and the two are in conflict: %#v != %#v", ap.Issuers, issuers)
173 }
174 ap.Issuers = issuers
175 }
176
177 // certificate managers
178 if certManagerVals, ok := sblock.pile["tls.cert_manager"]; ok {
179 for _, certManager := range certManagerVals {
180 certGetterName := certManager.Value.(caddy.Module).CaddyModule().ID.Name()
181 ap.ManagersRaw = append(ap.ManagersRaw, caddyconfig.JSONModuleObject(certManager.Value, "via", certGetterName, &warnings))
182 }
183 }
184 // custom bind host
185 for _, cfgVal := range sblock.pile["bind"] {
186 for _, iss := range ap.Issuers {
187 // if an issuer was already configured and it is NOT an ACME issuer,
188 // skip, since we intend to adjust only ACME issuers; ensure we
189 // include any issuer that embeds/wraps an underlying ACME issuer
190 var acmeIssuer *caddytls.ACMEIssuer
191 if acmeWrapper, ok := iss.(acmeCapable); ok {
192 acmeIssuer = acmeWrapper.GetACMEIssuer()
193 }
194 if acmeIssuer == nil {
195 continue
196 }
197
198 // proceed to configure the ACME issuer's bind host, without
199 // overwriting any existing settings
200 if acmeIssuer.Challenges == nil {
201 acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
202 }
203 if acmeIssuer.Challenges.BindHost == "" {
204 // only binding to one host is supported
205 var bindHost string
206 if asserted, ok := cfgVal.Value.(addressesWithProtocols); ok && len(asserted.addresses) > 0 {
207 bindHost = asserted.addresses[0]
208 }
209 acmeIssuer.Challenges.BindHost = bindHost
210 }
211 }
212 }
213
214 // we used to ensure this block is allowed to create an automation policy;
215 // doing so was forbidden if it has a key with no host (i.e. ":443")
216 // and if there is a different server block that also has a key with no
217 // host -- since a key with no host matches any host, we need its
218 // associated automation policy to have an empty Subjects list, i.e. no
219 // host filter, which is indistinguishable between the two server blocks
220 // because automation is not done in the context of a particular server...
221 // this is an example of a poor mapping from Caddyfile to JSON but that's
222 // the least-leaky abstraction I could figure out -- however, this check
223 // was preventing certain listeners, like those provided by plugins, from
224 // being used as desired (see the Tailscale listener plugin), so I removed
225 // the check: and I think since I originally wrote the check I added a new
226 // check above which *properly* detects this ambiguity without breaking the
227 // listener plugin; see the check above with a commented example config
228 if len(sblockHosts) == 0 && catchAllAP == nil {
229 // this server block has a key with no hosts, but there is not yet
230 // a catch-all automation policy (probably because no global options
231 // were set), so this one becomes it
232 catchAllAP = ap
233 }
234
235 hostsNotHTTP := sblock.hostsFromKeysNotHTTP(httpPort)
236 sort.Strings(hostsNotHTTP) // solely for deterministic test results
237
238 // associate our new automation policy with this server block's hosts
239 ap.SubjectsRaw = hostsNotHTTP
240
241 // if a combination of public and internal names were given
242 // for this same server block and no issuer was specified, we
243 // need to separate them out in the automation policies so
244 // that the internal names can use the internal issuer and
245 // the other names can use the default/public/ACME issuer
246 var ap2 *caddytls.AutomationPolicy
247 if len(ap.Issuers) == 0 {
248 var internal, external []string
249 for _, s := range ap.SubjectsRaw {
250 // do not create Issuers for Tailscale domains; they will be given a Manager instead
251 if isTailscaleDomain(s) {
252 continue
253 }
254 if !certmagic.SubjectQualifiesForCert(s) {
255 return nil, warnings, fmt.Errorf("subject does not qualify for certificate: '%s'", s)
256 }
257 // we don't use certmagic.SubjectQualifiesForPublicCert() because of one nuance:
258 // names like *.*.tld that may not qualify for a public certificate are actually
259 // fine when used with OnDemand, since OnDemand (currently) does not obtain
260 // wildcards (if it ever does, there will be a separate config option to enable
261 // it that we would need to check here) since the hostname is known at handshake;
262 // and it is unexpected to switch to internal issuer when the user wants to get
263 // regular certificates on-demand for a class of certs like *.*.tld.
264 if subjectQualifiesForPublicCert(ap, s) {
265 external = append(external, s)
266 } else {
267 internal = append(internal, s)
268 }
269 }
270 if len(external) > 0 && len(internal) > 0 {
271 ap.SubjectsRaw = external
272 apCopy := *ap
273 ap2 = &apCopy
274 ap2.SubjectsRaw = internal
275 ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)}
276 }
277 }
278
279 if tlsApp.Automation == nil {
280 tlsApp.Automation = new(caddytls.AutomationConfig)
281 }
282 tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, ap)
283 if ap2 != nil {
284 tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, ap2)
285 }
286
287 // certificate loaders
288 if clVals, ok := sblock.pile["tls.cert_loader"]; ok {
289 for _, clVal := range clVals {
290 certLoaders = append(certLoaders, clVal.Value.(caddytls.CertificateLoader))
291 }
292 }
293 }
294 }
295
296 // group certificate loaders by module name, then add to config
297 if len(certLoaders) > 0 {
298 loadersByName := make(map[string]caddytls.CertificateLoader)
299 for _, cl := range certLoaders {
300 name := caddy.GetModuleName(cl)
301 // ugh... technically, we may have multiple FileLoader and FolderLoader
302 // modules (because the tls directive returns one per occurrence), but
303 // the config structure expects only one instance of each kind of loader
304 // module, so we have to combine them... instead of enumerating each
305 // possible cert loader module in a type switch, we can use reflection,
306 // which works on any cert loaders that are slice types
307 if reflect.TypeOf(cl).Kind() == reflect.Slice {
308 combined := reflect.ValueOf(loadersByName[name])
309 if !combined.IsValid() {
310 combined = reflect.New(reflect.TypeOf(cl)).Elem()
311 }
312 clVal := reflect.ValueOf(cl)
313 for i := range clVal.Len() {
314 combined = reflect.Append(combined, clVal.Index(i))
315 }
316 loadersByName[name] = combined.Interface().(caddytls.CertificateLoader)
317 }
318 }
319 for certLoaderName, loaders := range loadersByName {
320 tlsApp.CertificatesRaw[certLoaderName] = caddyconfig.JSON(loaders, &warnings)
321 }
322 }
323
324 // set any of the on-demand options, for if/when on-demand TLS is enabled
325 if onDemand, ok := options["on_demand_tls"].(*caddytls.OnDemandConfig); ok {
326 if tlsApp.Automation == nil {
327 tlsApp.Automation = new(caddytls.AutomationConfig)
328 }
329 tlsApp.Automation.OnDemand = onDemand
330 }
331
332 // set up "global" (to the TLS app) DNS provider config
333 if globalDNS, ok := options["dns"]; ok && globalDNS != nil {
334 tlsApp.DNSRaw = caddyconfig.JSONModuleObject(globalDNS, "name", globalDNS.(caddy.Module).CaddyModule().ID.Name(), nil)
335 }
336
337 // set up "global" (to the TLS app) DNS resolvers config
338 if globalResolvers, ok := options["tls_resolvers"]; ok && globalResolvers != nil {
339 tlsApp.Resolvers = globalResolvers.([]string)
340 }
341
342 // set up ECH from Caddyfile options
343 if ech, ok := options["ech"].(*caddytls.ECH); ok {
344 tlsApp.EncryptedClientHello = ech
345
346 // outer server names will need certificates, so make sure they're included
347 // in an automation policy for them that applies any global options. A name
348 // that a site block's policy already covers keeps that policy, because a
349 // second policy for it would be rejected as ambiguous or picked ahead of
350 // the site's.
351 ap, err := newBaseAutomationPolicy(options, warnings, true)
352 if err != nil {
353 return nil, warnings, err
354 }
355 for _, cfg := range ech.Configs {
356 if cfg.PublicName == "" || automationPolicyCoversName(tlsApp.Automation, cfg.PublicName) {
357 continue
358 }
359 ap.SubjectsRaw = append(ap.SubjectsRaw, cfg.PublicName)
360 }
361 // without subjects, the policy would apply to every name as a catch-all
362 if len(ap.SubjectsRaw) > 0 {
363 if tlsApp.Automation == nil {
364 tlsApp.Automation = new(caddytls.AutomationConfig)
365 }
366 tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, ap)
367 }
368 }
369
370 // if the storage clean interval is a boolean, then it's "off" to disable cleaning
371 if sc, ok := options["storage_check"].(string); ok && sc == "off" {
372 tlsApp.DisableStorageCheck = true
373 }
374
375 // if the storage clean interval is a boolean, then it's "off" to disable cleaning
376 if sci, ok := options["storage_clean_interval"].(bool); ok && !sci {
377 tlsApp.DisableStorageClean = true
378 }
379
380 // set the storage clean interval if configured
381 if storageCleanInterval, ok := options["storage_clean_interval"].(caddy.Duration); ok {
382 if tlsApp.Automation == nil {
383 tlsApp.Automation = new(caddytls.AutomationConfig)
384 }
385 tlsApp.Automation.StorageCleanInterval = storageCleanInterval
386 }
387
388 // set the expired certificates renew interval if configured
389 if renewCheckInterval, ok := options["renew_interval"].(caddy.Duration); ok {
390 if tlsApp.Automation == nil {
391 tlsApp.Automation = new(caddytls.AutomationConfig)
392 }
393 tlsApp.Automation.RenewCheckInterval = renewCheckInterval
394 }
395
396 // set the OCSP check interval if configured
397 if ocspCheckInterval, ok := options["ocsp_interval"].(caddy.Duration); ok {
398 if tlsApp.Automation == nil {
399 tlsApp.Automation = new(caddytls.AutomationConfig)
400 }
401 tlsApp.Automation.OCSPCheckInterval = ocspCheckInterval
402 }
403
404 // set whether OCSP stapling should be disabled for manually-managed certificates
405 if ocspConfig, ok := options["ocsp_stapling"].(certmagic.OCSPConfig); ok {
406 tlsApp.DisableOCSPStapling = ocspConfig.DisableStapling
407 }
408
409 // if any hostnames appear on the same server block as a key with
410 // no host, they will not be used with route matchers because the
411 // hostless key matches all hosts, therefore, it wouldn't be
412 // considered for auto-HTTPS, so we need to make sure those hosts
413 // are manually considered for managed certificates; we also need
414 // to make sure that any of these names which are internal-only
415 // get internal certificates by default rather than ACME
416 var al caddytls.AutomateLoader
417 internalAP := &caddytls.AutomationPolicy{
418 IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
419 }
420 if !slices.Contains(autoHTTPS, "off") && !slices.Contains(autoHTTPS, "disable_certs") {
421 for h := range httpsHostsSharedWithHostlessKey {
422 al = append(al, h)
423 if !certmagic.SubjectQualifiesForPublicCert(h) {
424 internalAP.SubjectsRaw = append(internalAP.SubjectsRaw, h)
425 }
426 }
427 }
428 for name := range forcedAutomatedNames {
429 if slices.Contains(al, name) {
430 continue
431 }
432 al = append(al, name)
433 }
434 // names from the tls_automate_names global option are managed without a
435 // site block of their own, so that asking for a certificate does not also
436 // mean serving the name; like force_automate, an explicitly listed name is
437 // managed even where auto-HTTPS would not have chosen it. Names that cannot
438 // get a public certificate are given the internal issuer, the same
439 // treatment they would get from a site block.
440 if automateNames, ok := options["tls_automate_names"].([]string); ok {
441 var publicNames []string
442 for _, name := range automateNames {
443 if slices.Contains(al, name) {
444 continue
445 }
446 al = append(al, name)
447 // a name that a site block already wrote a policy for keeps that
448 // policy: it is more specific than anything the global options can
449 // say, and a second policy naming the same subject is ambiguous --
450 // adapting would fail outright. The name still belongs in the
451 // automate loader, since a site block served only over HTTP does
452 // not get its certificate managed by auto-HTTPS.
453 if automationPolicyExistsForSubject(tlsApp.Automation, name) {
454 continue
455 }
456 if certmagic.SubjectQualifiesForPublicCert(name) {
457 publicNames = append(publicNames, name)
458 } else {
459 internalAP.SubjectsRaw = append(internalAP.SubjectsRaw, name)
460 }
461 }
462 // the names still need an automation policy of their own, or they would
463 // miss the issuer configured by global options -- the catch-all policy
464 // that would otherwise carry it is dropped once every other policy
465 // names its subjects. Consolidation folds this back into an identical
466 // policy, so a name listed here ends up in the same place it would have
467 // had it been given a site block.
468 if len(publicNames) > 0 {
469 // only worth a policy if it would carry something: either global
470 // automation options, or ACME defaults filled in further below.
471 // Without either, the names are managed with the defaults anyway,
472 // and an empty policy would just be noise in the output.
473 ap, err := newBaseAutomationPolicy(options, warnings, hasGlobalACMEDefaults(options))
474 if err != nil {
475 return nil, warnings, err
476 }
477 if ap != nil {
478 ap.SubjectsRaw = publicNames
479 if tlsApp.Automation == nil {
480 tlsApp.Automation = new(caddytls.AutomationConfig)
481 }
482 tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, ap)
483 }
484 }
485 }
486 slices.Sort(al) // to stabilize the adapt output
487 if len(al) > 0 {
488 tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
489 }
490 if len(internalAP.SubjectsRaw) > 0 {
491 if tlsApp.Automation == nil {
492 tlsApp.Automation = new(caddytls.AutomationConfig)
493 }
494 tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, internalAP)
495 }
496
497 // if there are any global options set for issuers (ACME ones in particular), make sure they
498 // take effect in every automation policy that does not have any issuers
499 if tlsApp.Automation != nil {
500 globalEmail := options["email"]
501 globalACMECA := options["acme_ca"]
502 if hasGlobalACMEDefaults(options) {
503 for i := range tlsApp.Automation.Policies {
504 ap := tlsApp.Automation.Policies[i]
505 if len(ap.Issuers) == 0 && automationPolicyHasAllPublicNames(ap) {
506 // for public names, create default issuers which will later be filled in with configured global defaults
507 // (internal names will implicitly use the internal issuer at auto-https time)
508 emailStr, _ := globalEmail.(string)
509 ap.Issuers = caddytls.DefaultIssuers(emailStr)
510
511 // if a specific endpoint is configured, can't use multiple default issuers
512 if globalACMECA != nil {
513 ap.Issuers = []certmagic.Issuer{new(caddytls.ACMEIssuer)}
514 }
515 }
516 }
517 }
518 }
519
520 // finalize and verify policies; do cleanup
521 if tlsApp.Automation != nil {
522 for i, ap := range tlsApp.Automation.Policies {
523 // ensure all issuers have global defaults filled in
524 for j, issuer := range ap.Issuers {
525 err := fillInGlobalACMEDefaults(issuer, options)
526 if err != nil {
527 return nil, warnings, fmt.Errorf("filling in global issuer defaults for AP %d, issuer %d: %v", i, j, err)
528 }
529 }
530
531 // encode all issuer values we created, so they will be rendered in the output
532 if len(ap.Issuers) > 0 && ap.IssuersRaw == nil {
533 for _, iss := range ap.Issuers {
534 issuerName := iss.(caddy.Module).CaddyModule().ID.Name()
535 ap.IssuersRaw = append(ap.IssuersRaw, caddyconfig.JSONModuleObject(iss, "module", issuerName, &warnings))
536 }
537 }
538 }
539
540 // consolidate automation policies that are the exact same
541 tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies)
542
543 // ensure automation policies don't overlap subjects (this should be
544 // an error at provision-time as well, but catch it in the adapt phase
545 // for convenience)
546 automationHostSet := make(map[string]struct{})
547 for _, ap := range tlsApp.Automation.Policies {
548 for _, s := range ap.SubjectsRaw {
549 if _, ok := automationHostSet[s]; ok {
550 return nil, warnings, fmt.Errorf("hostname appears in more than one automation policy, making certificate management ambiguous: %s", s)
551 }
552 automationHostSet[s] = struct{}{}
553 }
554 }
555
556 // if nothing remains, remove any excess values to clean up the resulting config
557 if len(tlsApp.Automation.Policies) == 0 {
558 tlsApp.Automation.Policies = nil
559 }
560 if reflect.DeepEqual(tlsApp.Automation, new(caddytls.AutomationConfig)) {
561 tlsApp.Automation = nil
562 }
563 }
564
565 return tlsApp, warnings, nil
566 }
567
568 type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer }
569
570 func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) error {
571 acmeWrapper, ok := issuer.(acmeCapable)
572 if !ok {
573 return nil
574 }
575 acmeIssuer := acmeWrapper.GetACMEIssuer()
576 if acmeIssuer == nil {
577 return nil
578 }
579
580 globalEmail := options["email"]
581 globalACMECA := options["acme_ca"]
582 globalACMECARoot := options["acme_ca_root"]
583 globalACMEDNS, globalACMEDNSok := options["acme_dns"] // can be set to nil (to use globally-defined "dns" value instead), but it is still set
584 globalACMEEAB := options["acme_eab"]
585 globalPreferredChains := options["preferred_chains"]
586 globalCertLifetime := options["cert_lifetime"]
587 globalHTTPPort, globalHTTPSPort := options["http_port"], options["https_port"]
588 globalDefaultBind := options["default_bind"]
589
590 if globalEmail != nil && acmeIssuer.Email == "" {
591 acmeIssuer.Email = globalEmail.(string)
592 }
593 if globalACMECA != nil && acmeIssuer.CA == "" {
594 acmeIssuer.CA = globalACMECA.(string)
595 }
596 if globalACMECARoot != nil && !slices.Contains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) {
597 acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string))
598 }
599 if globalACMEDNSok && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil || acmeIssuer.Challenges.DNS.ProviderRaw == nil) {
600 globalDNS := options["dns"]
601 if globalDNS == nil && globalACMEDNS == nil {
602 return fmt.Errorf("acme_dns specified without DNS provider config, but no provider specified with 'dns' global option")
603 }
604 if acmeIssuer.Challenges == nil {
605 acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
606 }
607 if acmeIssuer.Challenges.DNS == nil {
608 acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
609 }
610 if globalACMEDNS != nil && acmeIssuer.Challenges.DNS.ProviderRaw == nil {
611 // Set a global DNS provider if `acme_dns` is set
612 acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(globalACMEDNS, "name", globalACMEDNS.(caddy.Module).CaddyModule().ID.Name(), nil)
613 }
614 }
615 if globalACMEEAB != nil && acmeIssuer.ExternalAccount == nil {
616 acmeIssuer.ExternalAccount = globalACMEEAB.(*acme.EAB)
617 }
618 if globalPreferredChains != nil && acmeIssuer.PreferredChains == nil {
619 acmeIssuer.PreferredChains = globalPreferredChains.(*caddytls.ChainPreference)
620 }
621 // only configure alt HTTP and TLS-ALPN ports if the DNS challenge is not enabled (wouldn't hurt, but isn't necessary since the DNS challenge is exclusive of others)
622 if globalHTTPPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.HTTP == nil || acmeIssuer.Challenges.HTTP.AlternatePort == 0) {
623 if acmeIssuer.Challenges == nil {
624 acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
625 }
626 if acmeIssuer.Challenges.HTTP == nil {
627 acmeIssuer.Challenges.HTTP = new(caddytls.HTTPChallengeConfig)
628 }
629 acmeIssuer.Challenges.HTTP.AlternatePort = globalHTTPPort.(int)
630 }
631 if globalHTTPSPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.TLSALPN == nil || acmeIssuer.Challenges.TLSALPN.AlternatePort == 0) {
632 if acmeIssuer.Challenges == nil {
633 acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
634 }
635 if acmeIssuer.Challenges.TLSALPN == nil {
636 acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig)
637 }
638 acmeIssuer.Challenges.TLSALPN.AlternatePort = globalHTTPSPort.(int)
639 }
640 // If BindHost is still unset, fall back to the first default_bind address if set
641 // This avoids binding the automation policy to the wildcard socket, which is unexpected behavior when a more selective socket is specified via default_bind
642 // In BSD it is valid to bind to the wildcard socket even though a more selective socket is already open (still unexpected behavior by the caller though)
643 // In Linux the same call will error with EADDRINUSE whenever the listener for the automation policy is opened
644 if acmeIssuer.Challenges == nil || (acmeIssuer.Challenges.DNS == nil && acmeIssuer.Challenges.BindHost == "") {
645 if defBinds, ok := globalDefaultBind.([]ConfigValue); ok && len(defBinds) > 0 {
646 if abp, ok := defBinds[0].Value.(addressesWithProtocols); ok && len(abp.addresses) > 0 {
647 if acmeIssuer.Challenges == nil {
648 acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
649 }
650 acmeIssuer.Challenges.BindHost = abp.addresses[0]
651 }
652 }
653 }
654 if globalCertLifetime != nil && acmeIssuer.CertificateLifetime == 0 {
655 acmeIssuer.CertificateLifetime = globalCertLifetime.(caddy.Duration)
656 }
657 // apply global resolvers if DNS challenge is configured and resolvers are not already set
658 globalResolvers := options["tls_resolvers"]
659 if globalResolvers != nil && acmeIssuer.Challenges != nil && acmeIssuer.Challenges.DNS != nil {
660 // Check if DNS challenge is actually configured
661 hasDNSChallenge := globalACMEDNSok || acmeIssuer.Challenges.DNS.ProviderRaw != nil
662 if hasDNSChallenge && len(acmeIssuer.Challenges.DNS.Resolvers) == 0 {
663 acmeIssuer.Challenges.DNS.Resolvers = globalResolvers.([]string)
664 }
665 }
666 return nil
667 }
668
669 // implicitACMEIssuers returns the issuers to use for ACME-related tls
670 // shortcuts such as ca, ca_root, and dns. If any global cert_issuer options
671 // configure ACME issuers, those become the templates for the local shortcut
672 // configuration; otherwise, default ACME issuers are used.
673 func implicitACMEIssuers(h Helper, acmeIssuer *caddytls.ACMEIssuer) []certmagic.Issuer {
674 globalIssuers, _ := h.Option("cert_issuer").([]certmagic.Issuer)
675
676 var implicitIssuers []certmagic.Issuer
677 for _, issuer := range globalIssuers {
678 acmeWrapper, ok := issuer.(acmeCapable)
679 if !ok {
680 continue
681 }
682 baseIssuer := acmeWrapper.GetACMEIssuer()
683 if baseIssuer == nil {
684 continue
685 }
686 implicitIssuers = append(implicitIssuers, mergeACMEIssuers(baseIssuer, acmeIssuer))
687 }
688 if len(implicitIssuers) > 0 {
689 return implicitIssuers
690 }
691
692 // If an ACME CA endpoint was set locally, the user expects to use only that
693 // CA rather than the usual default fallback issuers.
694 defaultIssuers := caddytls.DefaultIssuers(acmeIssuer.Email)
695 if acmeIssuer.CA != "" {
696 defaultIssuers = []certmagic.Issuer{new(caddytls.ACMEIssuer)}
697 }
698
699 implicitIssuers = make([]certmagic.Issuer, 0, len(defaultIssuers))
700 for _, issuer := range defaultIssuers {
701 acmeWrapper, ok := issuer.(acmeCapable)
702 if !ok {
703 implicitIssuers = append(implicitIssuers, issuer)
704 continue
705 }
706 baseIssuer := acmeWrapper.GetACMEIssuer()
707 if baseIssuer == nil {
708 implicitIssuers = append(implicitIssuers, issuer)
709 continue
710 }
711 implicitIssuers = append(implicitIssuers, mergeACMEIssuers(baseIssuer, acmeIssuer))
712 }
713 return implicitIssuers
714 }
715
716 func mergeACMEIssuers(base, overrides *caddytls.ACMEIssuer) *caddytls.ACMEIssuer {
717 if base == nil {
718 return cloneACMEIssuer(overrides)
719 }
720
721 merged := cloneACMEIssuer(base)
722 if overrides == nil {
723 return merged
724 }
725
726 if overrides.CA != "" {
727 merged.CA = overrides.CA
728 }
729 if overrides.TestCA != "" {
730 merged.TestCA = overrides.TestCA
731 }
732 if overrides.Email != "" {
733 merged.Email = overrides.Email
734 }
735 if overrides.Profile != "" {
736 merged.Profile = overrides.Profile
737 }
738 if overrides.AccountKey != "" {
739 merged.AccountKey = overrides.AccountKey
740 }
741 if overrides.ExternalAccount != nil {
742 merged.ExternalAccount = cloneACMEEAB(overrides.ExternalAccount)
743 }
744 if overrides.ACMETimeout != 0 {
745 merged.ACMETimeout = overrides.ACMETimeout
746 }
747 if len(overrides.TrustedRootsPEMFiles) > 0 {
748 merged.TrustedRootsPEMFiles = appendUniqueStrings(merged.TrustedRootsPEMFiles, overrides.TrustedRootsPEMFiles...)
749 }
750 if overrides.PreferredChains != nil {
751 merged.PreferredChains = cloneChainPreference(overrides.PreferredChains)
752 }
753 if overrides.CertificateLifetime != 0 {
754 merged.CertificateLifetime = overrides.CertificateLifetime
755 }
756 if len(overrides.NetworkProxyRaw) > 0 {
757 merged.NetworkProxyRaw = slices.Clone(overrides.NetworkProxyRaw)
758 }
759 merged.Challenges = mergeChallengesConfig(merged.Challenges, overrides.Challenges)
760
761 return merged
762 }
763
764 func mergeChallengesConfig(base, overrides *caddytls.ChallengesConfig) *caddytls.ChallengesConfig {
765 if base == nil {
766 return cloneChallengesConfig(overrides)
767 }
768 merged := cloneChallengesConfig(base)
769 if overrides == nil {
770 return merged
771 }
772
773 merged.HTTP = mergeHTTPChallengeConfig(merged.HTTP, overrides.HTTP)
774 merged.TLSALPN = mergeTLSALPNChallengeConfig(merged.TLSALPN, overrides.TLSALPN)
775 merged.DNS = mergeDNSChallengeConfig(merged.DNS, overrides.DNS)
776 if overrides.BindHost != "" {
777 merged.BindHost = overrides.BindHost
778 }
779 if overrides.Distributed != nil {
780 value := *overrides.Distributed
781 merged.Distributed = &value
782 }
783
784 return merged
785 }
786
787 func mergeHTTPChallengeConfig(base, overrides *caddytls.HTTPChallengeConfig) *caddytls.HTTPChallengeConfig {
788 if base == nil {
789 return cloneHTTPChallengeConfig(overrides)
790 }
791 merged := cloneHTTPChallengeConfig(base)
792 if overrides == nil {
793 return merged
794 }
795
796 if overrides.Disabled {
797 merged.Disabled = true
798 }
799 if overrides.AlternatePort != 0 {
800 merged.AlternatePort = overrides.AlternatePort
801 }
802
803 return merged
804 }
805
806 func mergeTLSALPNChallengeConfig(base, overrides *caddytls.TLSALPNChallengeConfig) *caddytls.TLSALPNChallengeConfig {
807 if base == nil {
808 return cloneTLSALPNChallengeConfig(overrides)
809 }
810 merged := cloneTLSALPNChallengeConfig(base)
811 if overrides == nil {
812 return merged
813 }
814
815 if overrides.Disabled {
816 merged.Disabled = true
817 }
818 if overrides.AlternatePort != 0 {
819 merged.AlternatePort = overrides.AlternatePort
820 }
821
822 return merged
823 }
824
825 func mergeDNSChallengeConfig(base, overrides *caddytls.DNSChallengeConfig) *caddytls.DNSChallengeConfig {
826 if base == nil {
827 return cloneDNSChallengeConfig(overrides)
828 }
829 merged := cloneDNSChallengeConfig(base)
830 if overrides == nil {
831 return merged
832 }
833
834 if len(overrides.ProviderRaw) > 0 {
835 merged.ProviderRaw = slices.Clone(overrides.ProviderRaw)
836 }
837 if overrides.PropagationDelay != 0 {
838 merged.PropagationDelay = overrides.PropagationDelay
839 }
840 if overrides.PropagationTimeout != 0 {
841 merged.PropagationTimeout = overrides.PropagationTimeout
842 }
843 if overrides.Resolvers != nil {
844 merged.Resolvers = slices.Clone(overrides.Resolvers)
845 }
846 if overrides.OverrideDomain != "" {
847 merged.OverrideDomain = overrides.OverrideDomain
848 }
849 if overrides.TTL != 0 {
850 merged.TTL = overrides.TTL
851 }
852
853 return merged
854 }
855
856 func cloneACMEIssuer(iss *caddytls.ACMEIssuer) *caddytls.ACMEIssuer {
857 if iss == nil {
858 return nil
859 }
860
861 cloned := *iss
862 cloned.Challenges = cloneChallengesConfig(iss.Challenges)
863 cloned.ExternalAccount = cloneACMEEAB(iss.ExternalAccount)
864 cloned.TrustedRootsPEMFiles = slices.Clone(iss.TrustedRootsPEMFiles)
865 cloned.PreferredChains = cloneChainPreference(iss.PreferredChains)
866 cloned.NetworkProxyRaw = slices.Clone(iss.NetworkProxyRaw)
867
868 return &cloned
869 }
870
871 func cloneChallengesConfig(cfg *caddytls.ChallengesConfig) *caddytls.ChallengesConfig {
872 if cfg == nil {
873 return nil
874 }
875
876 cloned := *cfg
877 cloned.HTTP = cloneHTTPChallengeConfig(cfg.HTTP)
878 cloned.TLSALPN = cloneTLSALPNChallengeConfig(cfg.TLSALPN)
879 cloned.DNS = cloneDNSChallengeConfig(cfg.DNS)
880 if cfg.Distributed != nil {
881 value := *cfg.Distributed
882 cloned.Distributed = &value
883 }
884
885 return &cloned
886 }
887
888 func cloneHTTPChallengeConfig(cfg *caddytls.HTTPChallengeConfig) *caddytls.HTTPChallengeConfig {
889 if cfg == nil {
890 return nil
891 }
892
893 cloned := *cfg
894 return &cloned
895 }
896
897 func cloneTLSALPNChallengeConfig(cfg *caddytls.TLSALPNChallengeConfig) *caddytls.TLSALPNChallengeConfig {
898 if cfg == nil {
899 return nil
900 }
901
902 cloned := *cfg
903 return &cloned
904 }
905
906 func cloneDNSChallengeConfig(cfg *caddytls.DNSChallengeConfig) *caddytls.DNSChallengeConfig {
907 if cfg == nil {
908 return nil
909 }
910
911 cloned := *cfg
912 cloned.ProviderRaw = slices.Clone(cfg.ProviderRaw)
913 cloned.Resolvers = slices.Clone(cfg.Resolvers)
914
915 return &cloned
916 }
917
918 func cloneACMEEAB(eab *acme.EAB) *acme.EAB {
919 if eab == nil {
920 return nil
921 }
922
923 cloned := *eab
924 return &cloned
925 }
926
927 func cloneChainPreference(pref *caddytls.ChainPreference) *caddytls.ChainPreference {
928 if pref == nil {
929 return nil
930 }
931
932 cloned := *pref
933 cloned.RootCommonName = slices.Clone(pref.RootCommonName)
934 cloned.AnyCommonName = slices.Clone(pref.AnyCommonName)
935 if pref.Smallest != nil {
936 value := *pref.Smallest
937 cloned.Smallest = &value
938 }
939
940 return &cloned
941 }
942
943 func appendUniqueStrings(existing []string, additions ...string) []string {
944 for _, value := range additions {
945 if !slices.Contains(existing, value) {
946 existing = append(existing, value)
947 }
948 }
949 return existing
950 }
951
952 // automationPolicyExistsForSubject reports whether some automation policy
953 // already names subject. Subjects are compared exactly, which is the same
954 // comparison the adapter uses to reject overlapping policies; a catch-all
955 // policy names no subjects and so never matches.
956 func automationPolicyExistsForSubject(automation *caddytls.AutomationConfig, subject string) bool {
957 if automation == nil {
958 return false
959 }
960 return slices.ContainsFunc(automation.Policies, func(ap *caddytls.AutomationPolicy) bool {
961 return slices.Contains(ap.SubjectsRaw, subject)
962 })
963 }
964
965 // automationPolicyCoversName reports whether some automation policy with
966 // subjects would be selected for the ECH public name at runtime. The TLS app
967 // trims and lowercases public names, then picks a policy for each one with
968 // certmagic.MatchWildcard, so a wildcard or differently cased subject covers
969 // it too. A catch-all policy names no subjects and so never matches.
970 func automationPolicyCoversName(automation *caddytls.AutomationConfig, publicName string) bool {
971 if automation == nil {
972 return false
973 }
974 name := strings.ToLower(strings.TrimSpace(publicName))
975 return slices.ContainsFunc(automation.Policies, func(ap *caddytls.AutomationPolicy) bool {
976 return slices.ContainsFunc(ap.SubjectsRaw, func(subj string) bool {
977 return certmagic.MatchWildcard(name, subj)
978 })
979 })
980 }
981
982 // hasGlobalACMEDefaults reports whether any global option is set that an
983 // automation policy without issuers of its own would later be filled in with.
984 // A policy is worth creating for a subject when this is true, even if the
985 // policy is otherwise empty at the time it is made.
986 func hasGlobalACMEDefaults(options map[string]any) bool {
987 _, hasACMEDNS := options["acme_dns"] // can be set to nil (to use globally-defined "dns" value instead), but it is still set
988 return options["email"] != nil ||
989 options["acme_ca"] != nil ||
990 options["acme_ca_root"] != nil ||
991 hasACMEDNS ||
992 options["acme_eab"] != nil ||
993 options["preferred_chains"] != nil
994 }
995
996 // newBaseAutomationPolicy returns a new TLS automation policy that gets
997 // its values from the global options map. It should be used as the base
998 // for any other automation policies. A nil policy (and no error) will be
999 // returned if there are no default/global options. However, if always is
1000 // true, a non-nil value will always be returned (unless there is an error).
1001 func newBaseAutomationPolicy(
1002 options map[string]any,
1003 _ []caddyconfig.Warning,
1004 always bool,
1005 ) (*caddytls.AutomationPolicy, error) {
1006 issuers, hasIssuers := options["cert_issuer"]
1007 _, hasLocalCerts := options["local_certs"]
1008 keyType, hasKeyType := options["key_type"]
1009 ocspStapling, hasOCSPStapling := options["ocsp_stapling"]
1010 renewalWindowRatio, hasRenewalWindowRatio := options["renewal_window_ratio"]
1011 hasGlobalAutomationOpts := hasIssuers || hasLocalCerts || hasKeyType || hasOCSPStapling || hasRenewalWindowRatio
1012
1013 globalACMECA := options["acme_ca"]
1014 globalACMECARoot := options["acme_ca_root"]
1015 _, globalACMEDNS := options["acme_dns"] // can be set to nil (to use globally-defined "dns" value instead), but it is still set
1016 globalACMEEAB := options["acme_eab"]
1017 globalPreferredChains := options["preferred_chains"]
1018 hasGlobalACMEDefaults := globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS || globalACMEEAB != nil || globalPreferredChains != nil
1019
1020 // if there are no global options related to automation policies
1021 // set, then we can just return right away
1022 if !hasGlobalAutomationOpts && !hasGlobalACMEDefaults {
1023 if always {
1024 return new(caddytls.AutomationPolicy), nil
1025 }
1026 return nil, nil
1027 }
1028
1029 ap := new(caddytls.AutomationPolicy)
1030 if hasKeyType {
1031 ap.KeyType = keyType.(string)
1032 }
1033
1034 if hasIssuers && hasLocalCerts {
1035 return nil, fmt.Errorf("global options are ambiguous: local_certs is confusing when combined with cert_issuer, because local_certs is also a specific kind of issuer")
1036 }
1037
1038 if hasIssuers {
1039 ap.Issuers = issuers.([]certmagic.Issuer)
1040 } else if hasLocalCerts {
1041 ap.Issuers = []certmagic.Issuer{new(caddytls.InternalIssuer)}
1042 }
1043
1044 if hasGlobalACMEDefaults {
1045 for i := range ap.Issuers {
1046 if err := fillInGlobalACMEDefaults(ap.Issuers[i], options); err != nil {
1047 return nil, fmt.Errorf("filling in global issuer defaults for issuer %d: %v", i, err)
1048 }
1049 }
1050 }
1051
1052 if hasOCSPStapling {
1053 ocspConfig := ocspStapling.(certmagic.OCSPConfig)
1054 ap.DisableOCSPStapling = ocspConfig.DisableStapling
1055 ap.OCSPOverrides = ocspConfig.ResponderOverrides
1056 }
1057
1058 if hasRenewalWindowRatio {
1059 ap.RenewalWindowRatio = renewalWindowRatio.(float64)
1060 }
1061
1062 return ap, nil
1063 }
1064
1065 // consolidateAutomationPolicies combines automation policies that are the same,
1066 // for a cleaner overall output.
1067 func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls.AutomationPolicy {
1068 // sort from most specific to least specific; we depend on this ordering
1069 sort.SliceStable(aps, func(i, j int) bool {
1070 if automationPolicyIsSubset(aps[i], aps[j]) {
1071 return true
1072 }
1073 if automationPolicyIsSubset(aps[j], aps[i]) {
1074 return false
1075 }
1076 return len(aps[i].SubjectsRaw) > len(aps[j].SubjectsRaw)
1077 })
1078
1079 emptyAPCount := 0
1080 origLenAPs := len(aps)
1081 // compute the number of empty policies (disregarding subjects) - see #4128
1082 // while we're at it,
1083 emptyAP := new(caddytls.AutomationPolicy)
1084 for i := 0; i < len(aps); i++ {
1085 emptyAP.SubjectsRaw = aps[i].SubjectsRaw
1086 emptyAP.ManagersRaw = nil
1087 if reflect.DeepEqual(aps[i], emptyAP) {
1088 // AP is empty
1089 emptyAPCount++
1090
1091 // see if this AP shadows something later
1092 shadowIdx := automationPolicyShadows(i, aps)
1093 emptyAP.SubjectsRaw = nil
1094 if shadowIdx >= 0 {
1095 emptyAP.SubjectsRaw = aps[shadowIdx].SubjectsRaw
1096 // allow the later policy, which is likely for a wildcard, to have cert
1097 // managers ("get_certificate"), since wildcards now cover specific
1098 // subdomains by default, when configured (see discussion in #7559)
1099 emptyAP.ManagersRaw = aps[shadowIdx].ManagersRaw
1100 }
1101
1102 // if this is the last AP, we can delete it, since auto-https should
1103 // pick it up; if it shadows something later that is also empty, we
1104 // can similarly delete this; but if it shadows something that is NOT
1105 // empty, we must not delete it since the shadowing has a purpose
1106 if i == len(aps)-1 || (shadowIdx >= 0 && reflect.DeepEqual(aps[shadowIdx], emptyAP)) {
1107 aps = slices.Delete(aps, i, i+1)
1108 i--
1109 }
1110 }
1111 }
1112 // If all policies are empty, we can return nil, as there is no need to set any policy
1113 if emptyAPCount == origLenAPs {
1114 return nil
1115 }
1116
1117 // remove or combine duplicate policies
1118 outer:
1119 for i := 0; i < len(aps); i++ {
1120 // compare only with next policies; we sorted by specificity so we must not delete earlier policies
1121 for j := i + 1; j < len(aps); j++ {
1122 // if they're exactly equal in every way, just keep one of them
1123 if reflect.DeepEqual(aps[i], aps[j]) {
1124 aps = slices.Delete(aps, j, j+1)
1125 // must re-evaluate current i against next j; can't skip it!
1126 // even if i decrements to -1, will be incremented to 0 immediately
1127 i--
1128 continue outer
1129 }
1130
1131 // if the policy is the same, we can keep just one, but we have
1132 // to be careful which one we keep; if only one has any hostnames
1133 // defined, then we need to keep the one without any hostnames,
1134 // otherwise the one without any subjects (a catch-all) would be
1135 // eaten up by the one with subjects; and if both have subjects, we
1136 // need to combine their lists
1137 if automationPoliciesHaveSameIssuers(aps[i], aps[j]) &&
1138 reflect.DeepEqual(aps[i].ManagersRaw, aps[j].ManagersRaw) &&
1139 bytes.Equal(aps[i].StorageRaw, aps[j].StorageRaw) &&
1140 aps[i].MustStaple == aps[j].MustStaple &&
1141 aps[i].KeyType == aps[j].KeyType &&
1142 aps[i].OnDemand == aps[j].OnDemand &&
1143 aps[i].ReusePrivateKeys == aps[j].ReusePrivateKeys &&
1144 aps[i].RenewalWindowRatio == aps[j].RenewalWindowRatio {
1145 if len(aps[i].SubjectsRaw) > 0 && len(aps[j].SubjectsRaw) == 0 {
1146 // later policy (at j) has no subjects ("catch-all"), so we can
1147 // remove the identical-but-more-specific policy that comes first
1148 // AS LONG AS it is not shadowed by another policy before it; e.g.
1149 // if policy i is for example.com, policy i+1 is '*.com', and policy
1150 // j is catch-all, we cannot remove policy i because that would
1151 // cause example.com to be served by the less specific policy for
1152 // '*.com', which might be different (yes we've seen this happen)
1153 if automationPolicyShadows(i, aps) >= j {
1154 aps = slices.Delete(aps, i, i+1)
1155 i--
1156 continue outer
1157 }
1158 } else if !automationPolicyMergeShadows(i, j, aps) {
1159 // merge only if it won't change which policy handles a name
1160 // avoid repeated subjects
1161 for _, subj := range aps[j].SubjectsRaw {
1162 if !slices.Contains(aps[i].SubjectsRaw, subj) {
1163 aps[i].SubjectsRaw = append(aps[i].SubjectsRaw, subj)
1164 }
1165 }
1166 aps = slices.Delete(aps, j, j+1)
1167 j--
1168 }
1169 }
1170 }
1171 }
1172
1173 return aps
1174 }
1175
1176 // automationPolicyIsSubset returns true if a's subjects are a subset
1177 // of b's subjects.
1178 func automationPolicyIsSubset(a, b *caddytls.AutomationPolicy) bool {
1179 if len(b.SubjectsRaw) == 0 {
1180 return true
1181 }
1182 if len(a.SubjectsRaw) == 0 {
1183 return false
1184 }
1185 for _, aSubj := range a.SubjectsRaw {
1186 inSuperset := slices.ContainsFunc(b.SubjectsRaw, func(bSubj string) bool {
1187 return certmagic.MatchWildcard(aSubj, bSubj)
1188 })
1189 if !inSuperset {
1190 return false
1191 }
1192 }
1193 return true
1194 }
1195
1196 // automationPolicyShadows returns the index of a policy that aps[i] shadows;
1197 // in other words, for all policies after position i, if that policy covers
1198 // the same subjects but is less specific, that policy's position is returned,
1199 // or -1 if no shadowing is found. For example, if policy i is for
1200 // "foo.example.com" and policy i+2 is for "*.example.com", then i+2 will be
1201 // returned, since that policy is shadowed by i, which is in front.
1202 func automationPolicyShadows(i int, aps []*caddytls.AutomationPolicy) int {
1203 for j := i + 1; j < len(aps); j++ {
1204 if automationPolicyIsSubset(aps[i], aps[j]) {
1205 return j
1206 }
1207 }
1208 return -1
1209 }
1210
1211 // automationPolicyMergeShadows returns true if merging aps[j] into aps[i]
1212 // (where i < j) would change which policy handles a name. This happens
1213 // when a subject of a policy between i and j overlaps with a subject of
1214 // aps[j], in either direction.
1215 //
1216 // For example, say the policies are for "example.com" (i),
1217 // "foo.example.com" (i+1), and "*.example.com" (j). Merging j into i moves
1218 // "*.example.com" before the i+1 policy. Then the wildcard matches
1219 // "foo.example.com" first, and the i+1 policy is never used.
1220 //
1221 // The reverse case matters too. If the wildcard is in i+1 and the name is
1222 // in j, merging moves the name before the wildcard, and its policy changes.
1223 // A merge should not change the result, so it is skipped here as well.
1224 //
1225 // This uses certmagic.MatchWildcard, which the TLS app also uses to pick
1226 // a policy for a name.
1227 func automationPolicyMergeShadows(i, j int, aps []*caddytls.AutomationPolicy) bool {
1228 for k := i + 1; k < j; k++ {
1229 for _, kSubj := range aps[k].SubjectsRaw {
1230 for _, jSubj := range aps[j].SubjectsRaw {
1231 if certmagic.MatchWildcard(kSubj, jSubj) ||
1232 certmagic.MatchWildcard(jSubj, kSubj) {
1233 return true
1234 }
1235 }
1236 }
1237 }
1238 return false
1239 }
1240
1241 // subjectQualifiesForPublicCert is like certmagic.SubjectQualifiesForPublicCert() except
1242 // that this allows domains with multiple wildcard levels like '*.*.example.com' to qualify
1243 // if the automation policy has OnDemand enabled (i.e. this function is more lenient).
1244 //
1245 // IP subjects are considered as non-qualifying for public certs. Technically, there are
1246 // now public ACME CAs as well as non-ACME CAs that issue IP certificates. But this function
1247 // is used solely for implicit automation (defaults), where it gets really complicated to
1248 // keep track of which issuers support IP certificates in which circumstances. Currently,
1249 // issuers that support IP certificates are very few, and all require some sort of config
1250 // from the user anyway (such as an account credential). Since we cannot implicitly and
1251 // automatically get public IP certs without configuration from the user, we treat IPs as
1252 // not qualifying for public certificates. Users should expressly configure an issuer
1253 // that supports IP certs for that purpose.
1254 func subjectQualifiesForPublicCert(ap *caddytls.AutomationPolicy, subj string) bool {
1255 return !certmagic.SubjectIsIP(subj) &&
1256 !certmagic.SubjectIsInternal(subj) &&
1257 (strings.Count(subj, "*.") < 2 || ap.OnDemand)
1258 }
1259
1260 func automationPoliciesHaveSameIssuers(a, b *caddytls.AutomationPolicy) bool {
1261 if reflect.DeepEqual(a.IssuersRaw, b.IssuersRaw) {
1262 return automationPoliciesHaveCompatibleImplicitIssuers(a, b)
1263 }
1264 return automationPolicyUsesDefaultInternalIssuer(a) && automationPolicyUsesDefaultInternalIssuer(b)
1265 }
1266
1267 func automationPolicyUsesDefaultInternalIssuer(ap *caddytls.AutomationPolicy) bool {
1268 if len(ap.IssuersRaw) == 0 && len(ap.Issuers) == 0 {
1269 return automationPolicyImplicitIssuerClass(ap) == "internal"
1270 }
1271 return len(ap.IssuersRaw) == 1 &&
1272 len(ap.Issuers) == 0 &&
1273 string(bytes.TrimSpace(ap.IssuersRaw[0])) == `{"module":"internal"}`
1274 }
1275
1276 // automationPoliciesHaveCompatibleImplicitIssuers returns whether two policies
1277 // without explicit issuers can be consolidated without changing default issuer
1278 // selection for their subjects.
1279 func automationPoliciesHaveCompatibleImplicitIssuers(a, b *caddytls.AutomationPolicy) bool {
1280 if len(a.IssuersRaw) > 0 || len(a.Issuers) > 0 ||
1281 len(b.IssuersRaw) > 0 || len(b.Issuers) > 0 {
1282 return true
1283 }
1284
1285 aClass := automationPolicyImplicitIssuerClass(a)
1286 bClass := automationPolicyImplicitIssuerClass(b)
1287 return aClass == "catch-all" || bClass == "catch-all" || aClass == bClass
1288 }
1289
1290 func automationPolicyImplicitIssuerClass(ap *caddytls.AutomationPolicy) string {
1291 if len(ap.SubjectsRaw) == 0 {
1292 return "catch-all"
1293 }
1294
1295 hasPublic := slices.ContainsFunc(ap.SubjectsRaw, func(subj string) bool {
1296 return subjectQualifiesForPublicCert(ap, subj)
1297 })
1298 hasInternal := slices.ContainsFunc(ap.SubjectsRaw, func(subj string) bool {
1299 return !subjectQualifiesForPublicCert(ap, subj)
1300 })
1301
1302 switch {
1303 case hasPublic && hasInternal:
1304 return "mixed"
1305 case hasPublic:
1306 return "public"
1307 default:
1308 return "internal"
1309 }
1310 }
1311
1312 // automationPolicyHasAllPublicNames returns true if all the names on the policy
1313 // do NOT qualify for public certs OR are tailscale domains.
1314 func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
1315 return !slices.ContainsFunc(ap.SubjectsRaw, func(i string) bool {
1316 return !subjectQualifiesForPublicCert(ap, i) || isTailscaleDomain(i)
1317 })
1318 }
1319
1320 func isTailscaleDomain(name string) bool {
1321 return strings.HasSuffix(strings.ToLower(name), ".ts.net")
1322 }