Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
httpcaddyfile: Many tls-related improvements including on-demand support
Holy heck this was complicated
Matthew Holt committed
Mar 17, 2020 at 21:00 UTC
fc7340e11aa9ca6326909aedfd36bb2c5b53d2a8
10 files changed
+599
-241
caddyconfig/httpcaddyfile/addresses.go
+1
-3
@@ -274,8 +274,6 @@ func ParseAddress(str string) (Address, error) {
274
return a, nil
275
}
276
277
-// TODO: which of the methods on Address are even used?
278
-
277
// String returns a human-readable form of a. It will
278
// be a cleaned-up and filled-out URL string.
279
func (a Address) String() string {
@@ -312,7 +310,7 @@ func (a Address) Normalize() Address {
310
path := a.Path
311
312
// ensure host is normalized if it's an IP address
315
- host := a.Host
313
+ host := strings.TrimSpace(a.Host)
314
if ip := net.ParseIP(host); ip != nil {
315
host = ip.String()
316
}
caddyconfig/httpcaddyfile/builtins.go
+31
-41
@@ -103,14 +103,16 @@ func parseRoot(h Helper) ([]ConfigValue, error) {
103
// load <paths...>
104
// ca <acme_ca_endpoint>
105
// dns <provider_name>
106
+// on_demand
107
// }
108
//
109
func parseTLS(h Helper) ([]ConfigValue, error) {
109
- var cp *caddytls.ConnectionPolicy
110
+ cp := new(caddytls.ConnectionPolicy)
111
var fileLoader caddytls.FileLoader
112
var folderLoader caddytls.FolderLoader
113
var acmeIssuer *caddytls.ACMEIssuer
114
var internalIssuer *caddytls.InternalIssuer
115
+ var onDemand bool
116
117
for h.Next() {
118
// file certificate loader
@@ -173,10 +175,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
175
tlsCertTags[certFilename] = tag
176
}
177
certSelector := caddytls.CustomCertSelectionPolicy{Tag: tag}
176
- if cp == nil {
177
- cp = new(caddytls.ConnectionPolicy)
178
- }
179
-
178
cp.CertSelection = caddyconfig.JSONModuleObject(certSelector, "policy", "custom", h.warnings)
179
default:
180
return nil, h.ArgErr()
@@ -187,7 +185,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
185
hasBlock = true
186
187
switch h.Val() {
190
- // connection policy
188
case "protocols":
189
args := h.RemainingArgs()
190
if len(args) == 0 {
@@ -197,55 +194,41 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
194
if _, ok := caddytls.SupportedProtocols[args[0]]; !ok {
195
return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[0])
196
}
200
- if cp == nil {
201
- cp = new(caddytls.ConnectionPolicy)
202
- }
197
cp.ProtocolMin = args[0]
198
}
199
if len(args) > 1 {
200
if _, ok := caddytls.SupportedProtocols[args[1]]; !ok {
201
return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[1])
202
}
209
- if cp == nil {
210
- cp = new(caddytls.ConnectionPolicy)
211
- }
203
cp.ProtocolMax = args[1]
204
}
205
+
206
case "ciphers":
207
for h.NextArg() {
208
if _, ok := caddytls.SupportedCipherSuites[h.Val()]; !ok {
209
return nil, h.Errf("Wrong cipher suite name or cipher suite not supported: '%s'", h.Val())
210
}
219
- if cp == nil {
220
- cp = new(caddytls.ConnectionPolicy)
221
- }
211
cp.CipherSuites = append(cp.CipherSuites, h.Val())
212
}
213
+
214
case "curves":
215
for h.NextArg() {
216
if _, ok := caddytls.SupportedCurves[h.Val()]; !ok {
217
return nil, h.Errf("Wrong curve name or curve not supported: '%s'", h.Val())
218
}
229
- if cp == nil {
230
- cp = new(caddytls.ConnectionPolicy)
231
- }
219
cp.Curves = append(cp.Curves, h.Val())
220
}
221
+
222
case "alpn":
223
args := h.RemainingArgs()
224
if len(args) == 0 {
225
return nil, h.ArgErr()
226
}
239
- if cp == nil {
240
- cp = new(caddytls.ConnectionPolicy)
241
- }
227
cp.ALPN = args
228
244
- // certificate folder loader
229
case "load":
230
folderLoader = append(folderLoader, h.RemainingArgs()...)
231
248
- // automation policy
232
case "ca":
233
arg := h.RemainingArgs()
234
if len(arg) != 1 {
@@ -256,7 +239,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
239
}
240
acmeIssuer.CA = arg[0]
241
259
- // DNS provider for ACME DNS challenge
242
case "dns":
243
if !h.Next() {
244
return nil, h.ArgErr()
@@ -284,6 +266,12 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
266
}
267
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, arg[0])
268
269
+ case "on_demand":
270
+ if h.NextArg() {
271
+ return nil, h.ArgErr()
272
+ }
273
+ onDemand = true
274
+
275
default:
276
return nil, h.Errf("unknown subdirective: %s", h.Val())
277
}
@@ -304,31 +292,15 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
292
Class: "tls.certificate_loader",
293
Value: fileLoader,
294
})
307
- // ensure server uses HTTPS by setting non-nil conn policy
308
- if cp == nil {
309
- cp = new(caddytls.ConnectionPolicy)
310
- }
295
}
296
if len(folderLoader) > 0 {
297
configVals = append(configVals, ConfigValue{
298
Class: "tls.certificate_loader",
299
Value: folderLoader,
300
})
317
- // ensure server uses HTTPS by setting non-nil conn policy
318
- if cp == nil {
319
- cp = new(caddytls.ConnectionPolicy)
320
- }
321
- }
322
-
323
- // connection policy
324
- if cp != nil {
325
- configVals = append(configVals, ConfigValue{
326
- Class: "tls.connection_policy",
327
- Value: cp,
328
- })
301
}
302
331
- // automation policy
303
+ // issuer
304
if acmeIssuer != nil && internalIssuer != nil {
305
// the logic to support this would be complex
306
return nil, h.Err("cannot use both ACME and internal issuers in same server block")
@@ -356,6 +328,24 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
328
})
329
}
330
331
+ // on-demand TLS
332
+ if onDemand {
333
+ configVals = append(configVals, ConfigValue{
334
+ Class: "tls.on_demand",
335
+ Value: true,
336
+ })
337
+ }
338
+
339
+ // connection policy -- always add one, to ensure that TLS
340
+ // is enabled, because this directive was used (this is
341
+ // needed, for instance, when a site block has a key of
342
+ // just ":5000" - i.e. no hostname, and only on-demand TLS
343
+ // is enabled)
344
+ configVals = append(configVals, ConfigValue{
345
+ Class: "tls.connection_policy",
346
+ Value: cp,
347
+ })
348
+
349
return configVals, nil
350
}
351
caddyconfig/httpcaddyfile/httptype.go
+62
-169
@@ -26,7 +26,6 @@ import (
26
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
27
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
28
"github.com/caddyserver/caddy/v2/modules/caddytls"
29
- "github.com/caddyserver/certmagic"
29
)
30
31
func init() {
@@ -177,105 +176,10 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
176
Servers: servers,
177
}
178
180
- // now for the TLS app! (TODO: refactor into own func)
181
- tlsApp := caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}
182
- var certLoaders []caddytls.CertificateLoader
183
- for _, p := range pairings {
184
- for i, sblock := range p.serverBlocks {
185
- // tls automation policies
186
- if issuerVals, ok := sblock.pile["tls.cert_issuer"]; ok {
187
- for _, issuerVal := range issuerVals {
188
- issuer := issuerVal.Value.(certmagic.Issuer)
189
- sblockHosts, err := st.hostsFromServerBlockKeys(sblock.block)
190
- if err != nil {
191
- return nil, warnings, err
192
- }
193
- if len(sblockHosts) > 0 {
194
- if tlsApp.Automation == nil {
195
- tlsApp.Automation = new(caddytls.AutomationConfig)
196
- }
197
- tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, &caddytls.AutomationPolicy{
198
- Subjects: sblockHosts,
199
- IssuerRaw: caddyconfig.JSONModuleObject(issuer, "module", issuer.(caddy.Module).CaddyModule().ID.Name(), &warnings),
200
- })
201
- } else {
202
- warnings = append(warnings, caddyconfig.Warning{
203
- Message: fmt.Sprintf("Server block %d %v has no names that qualify for automatic HTTPS, so no TLS automation policy will be added.", i, sblock.block.Keys),
204
- })
205
- }
206
- }
207
- }
208
- // tls certificate loaders
209
- if clVals, ok := sblock.pile["tls.certificate_loader"]; ok {
210
- for _, clVal := range clVals {
211
- certLoaders = append(certLoaders, clVal.Value.(caddytls.CertificateLoader))
212
- }
213
- }
214
- }
215
- }
216
- // group certificate loaders by module name, then add to config
217
- if len(certLoaders) > 0 {
218
- loadersByName := make(map[string]caddytls.CertificateLoader)
219
- for _, cl := range certLoaders {
220
- name := caddy.GetModuleName(cl)
221
- // ugh... technically, we may have multiple FileLoader and FolderLoader
222
- // modules (because the tls directive returns one per occurrence), but
223
- // the config structure expects only one instance of each kind of loader
224
- // module, so we have to combine them... instead of enumerating each
225
- // possible cert loader module in a type switch, we can use reflection,
226
- // which works on any cert loaders that are slice types
227
- if reflect.TypeOf(cl).Kind() == reflect.Slice {
228
- combined := reflect.ValueOf(loadersByName[name])
229
- if !combined.IsValid() {
230
- combined = reflect.New(reflect.TypeOf(cl)).Elem()
231
- }
232
- clVal := reflect.ValueOf(cl)
233
- for i := 0; i < clVal.Len(); i++ {
234
- combined = reflect.Append(reflect.Value(combined), clVal.Index(i))
235
- }
236
- loadersByName[name] = combined.Interface().(caddytls.CertificateLoader)
237
- }
238
- }
239
- for certLoaderName, loaders := range loadersByName {
240
- tlsApp.CertificatesRaw[certLoaderName] = caddyconfig.JSON(loaders, &warnings)
241
- }
242
- }
243
- // if global ACME CA, DNS, or email were set, append a catch-all automation
244
- // policy that ensures they will be used if no tls directive was used
245
- acmeCA, hasACMECA := options["acme_ca"]
246
- acmeDNS, hasACMEDNS := options["acme_dns"]
247
- email, hasEmail := options["email"]
248
- if hasACMECA || hasACMEDNS || hasEmail {
249
- if tlsApp.Automation == nil {
250
- tlsApp.Automation = new(caddytls.AutomationConfig)
251
- }
252
- if !hasACMECA {
253
- acmeCA = ""
254
- }
255
- if !hasEmail {
256
- email = ""
257
- }
258
- mgr := caddytls.ACMEIssuer{
259
- CA: acmeCA.(string),
260
- Email: email.(string),
261
- }
262
- if hasACMEDNS {
263
- provName := acmeDNS.(string)
264
- dnsProvModule, err := caddy.GetModule("tls.dns." + provName)
265
- if err != nil {
266
- return nil, warnings, fmt.Errorf("getting DNS provider module named '%s': %v", provName, err)
267
- }
268
- mgr.Challenges = &caddytls.ChallengesConfig{
269
- DNSRaw: caddyconfig.JSONModuleObject(dnsProvModule.New(), "provider", provName, &warnings),
270
- }
271
- }
272
- tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, &caddytls.AutomationPolicy{
273
- IssuerRaw: caddyconfig.JSONModuleObject(mgr, "module", "acme", &warnings),
274
- })
275
- }
276
- if tlsApp.Automation != nil {
277
- // consolidate automation policies that are the exact same
278
- tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies)
179
+ // then make the TLS app
180
+ tlsApp, warnings, err := st.buildTLSApp(pairings, options, warnings)
181
+ if err != nil {
182
+ return nil, warnings, err
183
}
184
185
// if experimental HTTP/3 is enabled, enable it on each server
@@ -316,10 +220,10 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
220
221
// annnd the top-level config, then we're done!
222
cfg := &caddy.Config{AppsRaw: make(caddy.ModuleMap)}
319
- if !reflect.DeepEqual(httpApp, caddyhttp.App{}) {
223
+ if len(httpApp.Servers) > 0 {
224
cfg.AppsRaw["http"] = caddyconfig.JSON(httpApp, &warnings)
225
}
322
- if !reflect.DeepEqual(tlsApp, caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}) {
226
+ if !reflect.DeepEqual(tlsApp, &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}) {
227
cfg.AppsRaw["tls"] = caddyconfig.JSON(tlsApp, &warnings)
228
}
229
if storageCvtr, ok := options["storage"].(caddy.StorageConverter); ok {
@@ -377,7 +281,6 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
281
var val interface{}
282
var err error
283
disp := caddyfile.NewDispenser(segment)
380
- // TODO: make this switch into a map
284
switch dir {
285
case "http_port":
286
val, err = parseOptHTTPPort(disp)
@@ -399,6 +302,10 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
302
val, err = parseOptAdmin(disp)
303
case "debug":
304
options["debug"] = true
305
+ case "on_demand_tls":
306
+ val, err = parseOptOnDemand(disp)
307
+ case "local_certs":
308
+ val = true
309
default:
310
return nil, fmt.Errorf("unrecognized parameter name: %s", dir)
311
}
@@ -411,8 +318,10 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
318
return serverBlocks[1:], nil
319
}
320
414
-// hostsFromServerBlockKeys returns a list of all the
415
-// hostnames found in the keys of the server block sb.
321
+// hostsFromServerBlockKeys returns a list of all the non-empty hostnames
322
+// found in the keys of the server block sb. If sb has a key that omits
323
+// the hostname (i.e. is a catch-all/empty host), then the returned list
324
+// is empty, because the server block effectively matches ALL hosts.
325
// The list may not be in a consistent order.
326
func (st *ServerType) hostsFromServerBlockKeys(sb caddyfile.ServerBlock) ([]string, error) {
327
// first get each unique hostname
@@ -424,7 +333,9 @@ func (st *ServerType) hostsFromServerBlockKeys(sb caddyfile.ServerBlock) ([]stri
333
}
334
addr = addr.Normalize()
335
if addr.Host == "" {
427
- continue
336
+ // server block contains a key like ":443", i.e. the host portion
337
+ // is empty / catch-all, which means to match all hosts
338
+ return []string{}, nil
339
}
340
hostMap[addr.Host] = struct{}{}
341
}
@@ -497,25 +408,18 @@ func (st *ServerType) serversFromPairings(
408
return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.block.Keys, err)
409
}
410
500
- // tls: connection policies and toggle auto HTTPS
501
- if _, ok := sblock.pile["tls.off"]; ok {
502
- // TODO: right now, no directives yield any tls.off value...
503
- // tls off: disable TLS (and automatic HTTPS) for server block's names
504
- if srv.AutoHTTPS == nil {
505
- srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
506
- }
507
- srv.AutoHTTPS.Disabled = true
508
- } else if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
509
- // tls connection policies
411
+ hosts, err := st.hostsFromServerBlockKeys(sblock.block)
412
+ if err != nil {
413
+ return nil, err
414
+ }
415
416
+ // tls: connection policies
417
+ if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
418
+ // tls connection policies
419
for _, cpVal := range cpVals {
420
cp := cpVal.Value.(*caddytls.ConnectionPolicy)
421
422
// make sure the policy covers all hostnames from the block
515
- hosts, err := st.hostsFromServerBlockKeys(sblock.block)
516
- if err != nil {
517
- return nil, err
518
- }
423
for _, h := range hosts {
424
if h == defaultSNI {
425
hosts = append(hosts, "")
@@ -524,7 +428,6 @@ func (st *ServerType) serversFromPairings(
428
}
429
}
430
527
- // TODO: are matchers needed if every hostname of the resulting config is matched?
431
if len(hosts) > 0 {
432
cp.MatchersRaw = caddy.ModuleMap{
433
"sni": caddyconfig.JSON(hosts, warnings), // make sure to match all hosts, not just auto-HTTPS-qualified ones
@@ -536,7 +439,6 @@ func (st *ServerType) serversFromPairings(
439
440
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
441
}
539
- // TODO: consolidate equal conn policies?
442
}
443
444
// exclude any hosts that were defined explicitly with
@@ -547,7 +449,7 @@ func (st *ServerType) serversFromPairings(
449
return nil, err
450
}
451
addr = addr.Normalize()
550
- if addr.Scheme == "http" {
452
+ if addr.Scheme == "http" && addr.Host != "" {
453
if srv.AutoHTTPS == nil {
454
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
455
}
@@ -607,10 +509,15 @@ func (st *ServerType) serversFromPairings(
509
// catch-all/default policy if there isn't one already (it's
510
// important that it goes at the end) - see issue #3004:
511
// https://github.com/caddyserver/caddy/issues/3004
512
+ // TODO: maybe a smarter way to handle this might be to just make the
513
+ // auto-HTTPS logic at provision-time detect if there is any connection
514
+ // policy missing for any HTTPS-enabled hosts, if so, add it... maybe?
515
if !hasCatchAllTLSConnPolicy && (len(srv.TLSConnPolicies) > 0 || defaultSNI != "") {
516
srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{DefaultSNI: defaultSNI})
517
}
518
519
+ // tidy things up a bit
520
+ srv.TLSConnPolicies = consolidateConnPolicies(srv.TLSConnPolicies)
521
srv.Routes = consolidateRoutes(srv.Routes)
522
523
servers[fmt.Sprintf("srv%d", i)] = srv
@@ -619,6 +526,26 @@ func (st *ServerType) serversFromPairings(
526
return servers, nil
527
}
528
529
+// consolidateConnPolicies combines TLS connection policies that are the same,
530
+// for a cleaner overall output.
531
+func consolidateConnPolicies(cps caddytls.ConnectionPolicies) caddytls.ConnectionPolicies {
532
+ for i := 0; i < len(cps); i++ {
533
+ for j := 0; j < len(cps); j++ {
534
+ if j == i {
535
+ continue
536
+ }
537
+
538
+ // if they're exactly equal in every way, just keep one of them
539
+ if reflect.DeepEqual(cps[i], cps[j]) {
540
+ cps = append(cps[:j], cps[j+1:]...)
541
+ i--
542
+ break
543
+ }
544
+ }
545
+ }
546
+ return cps
547
+}
548
+
549
// appendSubrouteToRouteList appends the routes in subroute
550
// to the routeList, optionally qualified by matchers.
551
func appendSubrouteToRouteList(routeList caddyhttp.RouteList,
@@ -750,52 +677,6 @@ func consolidateRoutes(routes caddyhttp.RouteList) caddyhttp.RouteList {
677
return routes
678
}
679
753
-// consolidateAutomationPolicies combines automation policies that are the same,
754
-// for a cleaner overall output.
755
-func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls.AutomationPolicy {
756
- for i := 0; i < len(aps); i++ {
757
- for j := 0; j < len(aps); j++ {
758
- if j == i {
759
- continue
760
- }
761
-
762
- // if they're exactly equal in every way, just keep one of them
763
- if reflect.DeepEqual(aps[i], aps[j]) {
764
- aps = append(aps[:j], aps[j+1:]...)
765
- i--
766
- break
767
- }
768
-
769
- // if the policy is the same, we can keep just one, but we have
770
- // to be careful which one we keep; if only one has any hostnames
771
- // defined, then we need to keep the one without any hostnames,
772
- // otherwise the one without any subjects (a catch-all) would be
773
- // eaten up by the one with subjects; and if both have subjects, we
774
- // need to combine their lists
775
- if reflect.DeepEqual(aps[i].IssuerRaw, aps[j].IssuerRaw) &&
776
- aps[i].ManageSync == aps[j].ManageSync {
777
- if len(aps[i].Subjects) == 0 && len(aps[j].Subjects) > 0 {
778
- aps = append(aps[:j], aps[j+1:]...)
779
- } else if len(aps[i].Subjects) > 0 && len(aps[j].Subjects) == 0 {
780
- aps = append(aps[:i], aps[i+1:]...)
781
- } else {
782
- aps[i].Subjects = append(aps[i].Subjects, aps[j].Subjects...)
783
- aps = append(aps[:j], aps[j+1:]...)
784
- }
785
- i--
786
- break
787
- }
788
- }
789
- }
790
-
791
- // ensure any catch-all policies go last
792
- sort.SliceStable(aps, func(i, j int) bool {
793
- return len(aps[i].Subjects) > len(aps[j].Subjects)
794
- })
795
-
796
- return aps
797
-}
798
-
680
func matcherSetFromMatcherToken(
681
tkn caddyfile.Token,
682
matcherDefs map[string]caddy.ModuleMap,
@@ -831,6 +712,7 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
712
// keep routes with common host and path matchers together
713
var matcherPairs []*hostPathPair
714
715
+ var catchAllHosts bool
716
for _, key := range sblock.Keys {
717
addr, err := ParseAddress(key)
718
if err != nil {
@@ -856,6 +738,17 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
738
matcherPairs = append(matcherPairs, chosenMatcherPair)
739
}
740
741
+ // if one of the keys has no host (i.e. is a catch-all for
742
+ // any hostname), then we need to null out the host matcher
743
+ // entirely so that it matches all hosts
744
+ if addr.Host == "" && !catchAllHosts {
745
+ chosenMatcherPair.hostm = nil
746
+ catchAllHosts = true
747
+ }
748
+ if catchAllHosts {
749
+ continue
750
+ }
751
+
752
// add this server block's keys to the matcher
753
// pair if it doesn't already exist
754
if addr.Host != "" {
caddyconfig/httpcaddyfile/options.go
+67
-6
@@ -15,11 +15,12 @@
15
package httpcaddyfile
16
17
import (
18
- "fmt"
18
"strconv"
19
+ "time"
20
21
"github.com/caddyserver/caddy/v2"
22
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
23
+ "github.com/caddyserver/caddy/v2/modules/caddytls"
24
)
25
26
func parseOptHTTPPort(d *caddyfile.Dispenser) (int, error) {
@@ -68,7 +69,7 @@ func parseOptOrder(d *caddyfile.Dispenser) ([]string, error) {
69
}
70
dirName := d.Val()
71
if _, ok := registeredDirectives[dirName]; !ok {
71
- return nil, fmt.Errorf("%s is not a registered directive", dirName)
72
+ return nil, d.Errf("%s is not a registered directive", dirName)
73
}
74
75
// get positional token
@@ -104,7 +105,7 @@ func parseOptOrder(d *caddyfile.Dispenser) ([]string, error) {
105
case "before":
106
case "after":
107
default:
107
- return nil, fmt.Errorf("unknown positional '%s'", pos)
108
+ return nil, d.Errf("unknown positional '%s'", pos)
109
}
110
111
// get name of other directive
@@ -145,11 +146,11 @@ func parseOptStorage(d *caddyfile.Dispenser) (caddy.StorageConverter, error) {
146
modName := args[0]
147
mod, err := caddy.GetModule("caddy.storage." + modName)
148
if err != nil {
148
- return nil, fmt.Errorf("getting storage module '%s': %v", modName, err)
149
+ return nil, d.Errf("getting storage module '%s': %v", modName, err)
150
}
151
unm, ok := mod.New().(caddyfile.Unmarshaler)
152
if !ok {
152
- return nil, fmt.Errorf("storage module '%s' is not a Caddyfile unmarshaler", mod.ID)
153
+ return nil, d.Errf("storage module '%s' is not a Caddyfile unmarshaler", mod.ID)
154
}
155
err = unm.UnmarshalCaddyfile(d.NewFromNextSegment())
156
if err != nil {
@@ -157,7 +158,7 @@ func parseOptStorage(d *caddyfile.Dispenser) (caddy.StorageConverter, error) {
158
}
159
storage, ok := unm.(caddy.StorageConverter)
160
if !ok {
160
- return nil, fmt.Errorf("module %s is not a StorageConverter", mod.ID)
161
+ return nil, d.Errf("module %s is not a StorageConverter", mod.ID)
162
}
163
return storage, nil
164
}
@@ -187,3 +188,63 @@ func parseOptAdmin(d *caddyfile.Dispenser) (string, error) {
188
}
189
return "", nil
190
}
191
+
192
+func parseOptOnDemand(d *caddyfile.Dispenser) (*caddytls.OnDemandConfig, error) {
193
+ var ond *caddytls.OnDemandConfig
194
+ for d.Next() {
195
+ if d.NextArg() {
196
+ return nil, d.ArgErr()
197
+ }
198
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
199
+ switch d.Val() {
200
+ case "ask":
201
+ if !d.NextArg() {
202
+ return nil, d.ArgErr()
203
+ }
204
+ if ond == nil {
205
+ ond = new(caddytls.OnDemandConfig)
206
+ }
207
+ ond.Ask = d.Val()
208
+
209
+ case "interval":
210
+ if !d.NextArg() {
211
+ return nil, d.ArgErr()
212
+ }
213
+ dur, err := time.ParseDuration(d.Val())
214
+ if err != nil {
215
+ return nil, err
216
+ }
217
+ if ond == nil {
218
+ ond = new(caddytls.OnDemandConfig)
219
+ }
220
+ if ond.RateLimit == nil {
221
+ ond.RateLimit = new(caddytls.RateLimit)
222
+ }
223
+ ond.RateLimit.Interval = caddy.Duration(dur)
224
+
225
+ case "burst":
226
+ if !d.NextArg() {
227
+ return nil, d.ArgErr()
228
+ }
229
+ burst, err := strconv.Atoi(d.Val())
230
+ if err != nil {
231
+ return nil, err
232
+ }
233
+ if ond == nil {
234
+ ond = new(caddytls.OnDemandConfig)
235
+ }
236
+ if ond.RateLimit == nil {
237
+ ond.RateLimit = new(caddytls.RateLimit)
238
+ }
239
+ ond.RateLimit.Burst = burst
240
+
241
+ default:
242
+ return nil, d.Errf("unrecognized parameter '%s'", d.Val())
243
+ }
244
+ }
245
+ }
246
+ if ond == nil {
247
+ return nil, d.Err("expected at least one config parameter for on_demand_tls")
248
+ }
249
+ return ond, nil
250
+}
caddyconfig/httpcaddyfile/tlsapp.go
new
+386
@@ -0,0 +1,386 @@
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
+ "fmt"
20
+ "reflect"
21
+ "sort"
22
+
23
+ "github.com/caddyserver/caddy/v2"
24
+ "github.com/caddyserver/caddy/v2/caddyconfig"
25
+ "github.com/caddyserver/caddy/v2/modules/caddytls"
26
+ "github.com/caddyserver/certmagic"
27
+)
28
+
29
+func (st ServerType) buildTLSApp(
30
+ pairings []sbAddrAssociation,
31
+ options map[string]interface{},
32
+ warnings []caddyconfig.Warning,
33
+) (*caddytls.TLS, []caddyconfig.Warning, error) {
34
+
35
+ tlsApp := &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}
36
+ var certLoaders []caddytls.CertificateLoader
37
+
38
+ // count how many server blocks have a key with no host,
39
+ // and find all hosts that share a server block with a
40
+ // hostless key, so that they don't get forgotten/omitted
41
+ // by auto-HTTPS (since they won't appear in route matchers)
42
+ var serverBlocksWithHostlessKey int
43
+ hostsSharedWithHostlessKey := make(map[string]struct{})
44
+ for _, pair := range pairings {
45
+ for _, sb := range pair.serverBlocks {
46
+ for _, key := range sb.block.Keys {
47
+ addr, err := ParseAddress(key)
48
+ if err != nil {
49
+ return nil, warnings, err
50
+ }
51
+ addr = addr.Normalize()
52
+ if addr.Host == "" {
53
+ serverBlocksWithHostlessKey++
54
+ // this server block has a hostless key, now
55
+ // go through and add all the hosts to the set
56
+ for _, otherKey := range sb.block.Keys {
57
+ if otherKey == key {
58
+ continue
59
+ }
60
+ addr, err := ParseAddress(otherKey)
61
+ if err != nil {
62
+ return nil, warnings, err
63
+ }
64
+ addr = addr.Normalize()
65
+ if addr.Host != "" {
66
+ hostsSharedWithHostlessKey[addr.Host] = struct{}{}
67
+ }
68
+ }
69
+ break
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ catchAllAP, err := newBaseAutomationPolicy(options, warnings, false)
76
+ if err != nil {
77
+ return nil, warnings, err
78
+ }
79
+
80
+ for _, p := range pairings {
81
+ for _, sblock := range p.serverBlocks {
82
+ // get values that populate an automation policy for this block
83
+ var ap *caddytls.AutomationPolicy
84
+
85
+ sblockHosts, err := st.hostsFromServerBlockKeys(sblock.block)
86
+ if err != nil {
87
+ return nil, warnings, err
88
+ }
89
+ if len(sblockHosts) == 0 {
90
+ ap = catchAllAP
91
+ }
92
+
93
+ // on-demand tls
94
+ if _, ok := sblock.pile["tls.on_demand"]; ok {
95
+ if ap == nil {
96
+ var err error
97
+ ap, err = newBaseAutomationPolicy(options, warnings, true)
98
+ if err != nil {
99
+ return nil, warnings, err
100
+ }
101
+ }
102
+ ap.OnDemand = true
103
+ }
104
+
105
+ // certificate issuers
106
+ if issuerVals, ok := sblock.pile["tls.cert_issuer"]; ok {
107
+ for _, issuerVal := range issuerVals {
108
+ issuer := issuerVal.Value.(certmagic.Issuer)
109
+ if ap == nil {
110
+ var err error
111
+ ap, err = newBaseAutomationPolicy(options, warnings, true)
112
+ if err != nil {
113
+ return nil, warnings, err
114
+ }
115
+ }
116
+ encoded := caddyconfig.JSONModuleObject(issuer, "module", issuer.(caddy.Module).CaddyModule().ID.Name(), &warnings)
117
+ if ap == catchAllAP && ap.IssuerRaw != nil && !bytes.Equal(ap.IssuerRaw, encoded) {
118
+ return nil, warnings, fmt.Errorf("conflicting issuer configuration: %s != %s", ap.IssuerRaw, encoded)
119
+ }
120
+ ap.IssuerRaw = encoded
121
+ }
122
+ }
123
+
124
+ if ap != nil {
125
+ // first make sure this block is allowed to create an automation policy;
126
+ // doing so is forbidden if it has a key with no host (i.e. ":443")
127
+ // and if there is a different server block that also has a key with no
128
+ // host -- since a key with no host matches any host, we need its
129
+ // associated automation policy to have an empty Subjects list, i.e. no
130
+ // host filter, which is indistinguishable between the two server blocks
131
+ // because automation is not done in the context of a particular server...
132
+ // this is an example of a poor mapping from Caddyfile to JSON but that's
133
+ // the least-leaky abstraction I could figure out
134
+ if len(sblockHosts) == 0 {
135
+ if serverBlocksWithHostlessKey > 1 {
136
+ // this server block and at least one other has a key with no host,
137
+ // making the two indistinguishable; it is misleading to define such
138
+ // a policy within one server block since it actually will apply to
139
+ // others as well
140
+ return nil, warnings, fmt.Errorf("cannot make a TLS automation policy from a server block that has a host-less address when there are other server block addresses lacking a host")
141
+ }
142
+ if catchAllAP == nil {
143
+ // this server block has a key with no hosts, but there is not yet
144
+ // a catch-all automation policy (probably because no global options
145
+ // were set), so this one becomes it
146
+ catchAllAP = ap
147
+ }
148
+ }
149
+
150
+ // associate our new automation policy with this server block's hosts,
151
+ // unless, of course, the server block has a key with no hosts, in which
152
+ // case its automation policy becomes or blends with the default/global
153
+ // automation policy because, of necessity, it applies to all hostnames
154
+ // (i.e. it has no Subjects filter) -- in that case, we'll append it last
155
+ if ap != catchAllAP {
156
+ ap.Subjects = sblockHosts
157
+
158
+ // if a combination of public and internal names were given
159
+ // for this same server block and no issuer was specified, we
160
+ // need to separate them out in the automation policies so
161
+ // that the internal names can use the internal issuer and
162
+ // the other names can use the default/public/ACME issuer
163
+ var ap2 *caddytls.AutomationPolicy
164
+ if ap.Issuer == nil {
165
+ var internal, external []string
166
+ for _, s := range ap.Subjects {
167
+ if certmagic.SubjectQualifiesForPublicCert(s) {
168
+ external = append(external, s)
169
+ } else {
170
+ internal = append(internal, s)
171
+ }
172
+ }
173
+ if len(external) > 0 && len(internal) > 0 {
174
+ ap.Subjects = external
175
+ apCopy := *ap
176
+ ap2 = &apCopy
177
+ ap2.Subjects = internal
178
+ ap2.IssuerRaw = caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)
179
+ }
180
+ }
181
+ if tlsApp.Automation == nil {
182
+ tlsApp.Automation = new(caddytls.AutomationConfig)
183
+ }
184
+ tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, ap)
185
+ if ap2 != nil {
186
+ tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, ap2)
187
+ }
188
+ }
189
+ }
190
+
191
+ // certificate loaders
192
+ if clVals, ok := sblock.pile["tls.certificate_loader"]; ok {
193
+ for _, clVal := range clVals {
194
+ certLoaders = append(certLoaders, clVal.Value.(caddytls.CertificateLoader))
195
+ }
196
+ }
197
+ }
198
+ }
199
+
200
+ // group certificate loaders by module name, then add to config
201
+ if len(certLoaders) > 0 {
202
+ loadersByName := make(map[string]caddytls.CertificateLoader)
203
+ for _, cl := range certLoaders {
204
+ name := caddy.GetModuleName(cl)
205
+ // ugh... technically, we may have multiple FileLoader and FolderLoader
206
+ // modules (because the tls directive returns one per occurrence), but
207
+ // the config structure expects only one instance of each kind of loader
208
+ // module, so we have to combine them... instead of enumerating each
209
+ // possible cert loader module in a type switch, we can use reflection,
210
+ // which works on any cert loaders that are slice types
211
+ if reflect.TypeOf(cl).Kind() == reflect.Slice {
212
+ combined := reflect.ValueOf(loadersByName[name])
213
+ if !combined.IsValid() {
214
+ combined = reflect.New(reflect.TypeOf(cl)).Elem()
215
+ }
216
+ clVal := reflect.ValueOf(cl)
217
+ for i := 0; i < clVal.Len(); i++ {
218
+ combined = reflect.Append(reflect.Value(combined), clVal.Index(i))
219
+ }
220
+ loadersByName[name] = combined.Interface().(caddytls.CertificateLoader)
221
+ }
222
+ }
223
+ for certLoaderName, loaders := range loadersByName {
224
+ tlsApp.CertificatesRaw[certLoaderName] = caddyconfig.JSON(loaders, &warnings)
225
+ }
226
+ }
227
+
228
+ // set any of the on-demand options, for if/when on-demand TLS is enabled
229
+ if onDemand, ok := options["on_demand_tls"].(*caddytls.OnDemandConfig); ok {
230
+ if tlsApp.Automation == nil {
231
+ tlsApp.Automation = new(caddytls.AutomationConfig)
232
+ }
233
+ tlsApp.Automation.OnDemand = onDemand
234
+ }
235
+
236
+ // if there is a global/catch-all automation policy, ensure it goes last
237
+ if catchAllAP != nil {
238
+ if tlsApp.Automation == nil {
239
+ tlsApp.Automation = new(caddytls.AutomationConfig)
240
+ }
241
+ tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
242
+ }
243
+
244
+ // if any hostnames appear on the same server block as a key with
245
+ // no host, they will not be used with route matchers because the
246
+ // hostless key matches all hosts, therefore, it wouldn't be
247
+ // considered for auto-HTTPS, so we need to make sure those hosts
248
+ // are manually considered for managed certificates
249
+ var al caddytls.AutomateLoader
250
+ for h := range hostsSharedWithHostlessKey {
251
+ al = append(al, h)
252
+ }
253
+ if len(al) > 0 {
254
+ tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
255
+ }
256
+
257
+ // do a little verification & cleanup
258
+ if tlsApp.Automation != nil {
259
+ // ensure automation policies don't overlap subjects (this should be
260
+ // an error at provision-time as well, but catch it in the adapt phase
261
+ // for convenience)
262
+ automationHostSet := make(map[string]struct{})
263
+ for _, ap := range tlsApp.Automation.Policies {
264
+ for _, s := range ap.Subjects {
265
+ if _, ok := automationHostSet[s]; ok {
266
+ return nil, warnings, fmt.Errorf("hostname appears in more than one automation policy, making certificate management ambiguous: %s", s)
267
+ }
268
+ automationHostSet[s] = struct{}{}
269
+ }
270
+ }
271
+
272
+ // consolidate automation policies that are the exact same
273
+ tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies)
274
+ }
275
+
276
+ return tlsApp, warnings, nil
277
+}
278
+
279
+// newBaseAutomationPolicy returns a new TLS automation policy that gets
280
+// its values from the global options map. It should be used as the base
281
+// for any other automation policies. A nil policy (and no error) will be
282
+// returned if there are no default/global options. However, if always is
283
+// true, a non-nil value will always be returned (unless there is an error).
284
+func newBaseAutomationPolicy(options map[string]interface{}, warnings []caddyconfig.Warning, always bool) (*caddytls.AutomationPolicy, error) {
285
+ acmeCA, hasACMECA := options["acme_ca"]
286
+ acmeDNS, hasACMEDNS := options["acme_dns"]
287
+ acmeCARoot, hasACMECARoot := options["acme_ca_root"]
288
+ email, hasEmail := options["email"]
289
+ localCerts, hasLocalCerts := options["local_certs"]
290
+
291
+ hasGlobalAutomationOpts := hasACMECA || hasACMEDNS || hasACMECARoot || hasEmail || hasLocalCerts
292
+
293
+ // if there are no global options related to automation policies
294
+ // set, then we can just return right away
295
+ if !hasGlobalAutomationOpts {
296
+ if always {
297
+ return new(caddytls.AutomationPolicy), nil
298
+ }
299
+ return nil, nil
300
+ }
301
+
302
+ ap := new(caddytls.AutomationPolicy)
303
+
304
+ if localCerts != nil {
305
+ // internal issuer enabled trumps any ACME configurations; useful in testing
306
+ ap.IssuerRaw = caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)
307
+ } else {
308
+ if acmeCA == nil {
309
+ acmeCA = ""
310
+ }
311
+ if email == nil {
312
+ email = ""
313
+ }
314
+ mgr := caddytls.ACMEIssuer{
315
+ CA: acmeCA.(string),
316
+ Email: email.(string),
317
+ }
318
+ if acmeDNS != nil {
319
+ provName := acmeDNS.(string)
320
+ dnsProvModule, err := caddy.GetModule("tls.dns." + provName)
321
+ if err != nil {
322
+ return nil, fmt.Errorf("getting DNS provider module named '%s': %v", provName, err)
323
+ }
324
+ mgr.Challenges = &caddytls.ChallengesConfig{
325
+ DNSRaw: caddyconfig.JSONModuleObject(dnsProvModule.New(), "provider", provName, &warnings),
326
+ }
327
+ }
328
+ if acmeCARoot != nil {
329
+ mgr.TrustedRootsPEMFiles = []string{acmeCARoot.(string)}
330
+ }
331
+ ap.IssuerRaw = caddyconfig.JSONModuleObject(mgr, "module", "acme", &warnings)
332
+ }
333
+
334
+ return ap, nil
335
+}
336
+
337
+// consolidateAutomationPolicies combines automation policies that are the same,
338
+// for a cleaner overall output.
339
+func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls.AutomationPolicy {
340
+ for i := 0; i < len(aps); i++ {
341
+ for j := 0; j < len(aps); j++ {
342
+ if j == i {
343
+ continue
344
+ }
345
+
346
+ // if they're exactly equal in every way, just keep one of them
347
+ if reflect.DeepEqual(aps[i], aps[j]) {
348
+ aps = append(aps[:j], aps[j+1:]...)
349
+ i--
350
+ break
351
+ }
352
+
353
+ // if the policy is the same, we can keep just one, but we have
354
+ // to be careful which one we keep; if only one has any hostnames
355
+ // defined, then we need to keep the one without any hostnames,
356
+ // otherwise the one without any subjects (a catch-all) would be
357
+ // eaten up by the one with subjects; and if both have subjects, we
358
+ // need to combine their lists
359
+ if bytes.Equal(aps[i].IssuerRaw, aps[j].IssuerRaw) &&
360
+ bytes.Equal(aps[i].StorageRaw, aps[j].StorageRaw) &&
361
+ aps[i].MustStaple == aps[j].MustStaple &&
362
+ aps[i].KeyType == aps[j].KeyType &&
363
+ aps[i].OnDemand == aps[j].OnDemand &&
364
+ aps[i].RenewalWindowRatio == aps[j].RenewalWindowRatio &&
365
+ aps[i].ManageSync == aps[j].ManageSync {
366
+ if len(aps[i].Subjects) == 0 && len(aps[j].Subjects) > 0 {
367
+ aps = append(aps[:j], aps[j+1:]...)
368
+ } else if len(aps[i].Subjects) > 0 && len(aps[j].Subjects) == 0 {
369
+ aps = append(aps[:i], aps[i+1:]...)
370
+ } else {
371
+ aps[i].Subjects = append(aps[i].Subjects, aps[j].Subjects...)
372
+ aps = append(aps[:j], aps[j+1:]...)
373
+ }
374
+ i--
375
+ break
376
+ }
377
+ }
378
+ }
379
+
380
+ // ensure any catch-all policies go last
381
+ sort.SliceStable(aps, func(i, j int) bool {
382
+ return len(aps[i].Subjects) > len(aps[j].Subjects)
383
+ })
384
+
385
+ return aps
386
+}
modules/caddyhttp/autohttps.go
+1
-1
@@ -232,7 +232,7 @@ uniqueDomainsLoop:
232
// some names we've found might already have automation policies
233
// explicitly specified for them; we should exclude those from
234
// our hidden/implicit policy, since applying a name to more than
235
- // one automation policy would be confusing and an error
235
+ // one automation policy would be confusing and an error
236
if app.tlsApp.Automation != nil {
237
for _, ap := range app.tlsApp.Automation.Policies {
238
for _, apHost := range ap.Subjects {
modules/caddytls/automation.go
+35
-16
@@ -23,6 +23,7 @@ import (
23
"github.com/caddyserver/caddy/v2"
24
"github.com/caddyserver/certmagic"
25
"github.com/go-acme/lego/v3/challenge"
26
+ "go.uber.org/zap"
27
)
28
29
// AutomationConfig designates configuration for the
@@ -131,31 +132,49 @@ func (ap *AutomationPolicy) provision(tlsApp *TLS) error {
132
133
var ond *certmagic.OnDemandConfig
134
if ap.OnDemand {
134
- var onDemand *OnDemandConfig
135
- if tlsApp.Automation != nil {
136
- onDemand = tlsApp.Automation.OnDemand
137
- }
138
-
135
ond = &certmagic.OnDemandConfig{
136
DecisionFunc: func(name string) error {
141
- if onDemand != nil {
142
- if onDemand.Ask != "" {
143
- err := onDemandAskRequest(onDemand.Ask, name)
144
- if err != nil {
145
- return err
146
- }
147
- }
148
- // check the rate limiter last because
149
- // doing so makes a reservation
150
- if !onDemandRateLimiter.Allow() {
151
- return fmt.Errorf("on-demand rate limit exceeded")
137
+ // if an "ask" endpoint was defined, consult it first
138
+ if tlsApp.Automation != nil &&
139
+ tlsApp.Automation.OnDemand != nil &&
140
+ tlsApp.Automation.OnDemand.Ask != "" {
141
+ err := onDemandAskRequest(tlsApp.Automation.OnDemand.Ask, name)
142
+ if err != nil {
143
+ return err
144
}
145
}
146
+ // check the rate limiter last because
147
+ // doing so makes a reservation
148
+ if !onDemandRateLimiter.Allow() {
149
+ return fmt.Errorf("on-demand rate limit exceeded")
150
+ }
151
return nil
152
},
153
}
154
}
155
156
+ // if this automation policy has no Issuer defined, and
157
+ // none the subjects do not qualify for a public certificate,
158
+ // set the issuer to internal so that these names can all
159
+ // get certificates; critically, we can only do this if an
160
+ // issuer is not explictly configured AND if the list of
161
+ // subjects is non-empty
162
+ if ap.IssuerRaw == nil && len(ap.Subjects) > 0 {
163
+ var anyPublic bool
164
+ for _, s := range ap.Subjects {
165
+ if certmagic.SubjectQualifiesForPublicCert(s) {
166
+ anyPublic = true
167
+ break
168
+ }
169
+ }
170
+ if !anyPublic {
171
+ tlsApp.logger.Info("setting internal issuer for automation policy that has only internal subjects but no issuer configured",
172
+ zap.Strings("subjects", ap.Subjects))
173
+ ap.IssuerRaw = json.RawMessage(`{"module":"internal"}`)
174
+ }
175
+ }
176
+
177
+ // load and provision the issuer module
178
if ap.IssuerRaw != nil {
179
val, err := tlsApp.ctx.LoadModule(ap, "IssuerRaw")
180
if err != nil {
modules/caddytls/connpolicy.go
+1
-1
@@ -173,7 +173,7 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error {
173
// TODO: I don't love how this works: we pre-build certmagic configs
174
// so that handshakes are faster. Unfortunately, certmagic configs are
175
// comprised of settings from both a TLS connection policy and a TLS
176
- // automation policy. The only two fields (as of March 2020; v2 beta 16)
176
+ // automation policy. The only two fields (as of March 2020; v2 beta 17)
177
// of a certmagic config that come from the TLS connection policy are
178
// CertSelection and DefaultServerName, so an automation policy is what
179
// builds the base certmagic config. Since the pre-built config is
modules/caddytls/tls.go
+10
-2
@@ -179,9 +179,17 @@ func (t *TLS) Validate() error {
179
if t.Automation != nil {
180
// ensure that host aren't repeated; since only the first
181
// automation policy is used, repeating a host in the lists
182
- // isn't useful and is probably a mistake
182
+ // isn't useful and is probably a mistake; same for two
183
+ // catch-all/default policies
184
+ var hasDefault bool
185
hostSet := make(map[string]int)
186
for i, ap := range t.Automation.Policies {
187
+ if len(ap.Subjects) == 0 {
188
+ if hasDefault {
189
+ return fmt.Errorf("automation policy %d is the second policy that acts as default/catch-all, but will never be used", i)
190
+ }
191
+ hasDefault = true
192
+ }
193
for _, h := range ap.Subjects {
194
if first, ok := hostSet[h]; ok {
195
return fmt.Errorf("automation policy %d: cannot apply more than one automation policy to host: %s (first match in policy %d)", i, h, first)
@@ -301,7 +309,7 @@ func (t *TLS) AddAutomationPolicy(ap *AutomationPolicy) error {
309
// fewer names) exists, prioritize this new policy
310
if len(other.Subjects) < len(ap.Subjects) {
311
t.Automation.Policies = append(t.Automation.Policies[:i],
304
- append([]*AutomationPolicy{ap}, t.Automation.Policies[i+1:]...)...)
312
+ append([]*AutomationPolicy{ap}, t.Automation.Policies[i:]...)...)
313
return nil
314
}
315
}
modules/filestorage/filestorage.go
+5
-2
@@ -59,5 +59,8 @@ func (s *FileStorage) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
59
return nil
60
}
61
62
-// Interface guard
63
-var _ caddy.StorageConverter = (*FileStorage)(nil)
62
+// Interface guards
63
+var (
64
+ _ caddy.StorageConverter = (*FileStorage)(nil)
65
+ _ caddyfile.Unmarshaler = (*FileStorage)(nil)
66
+)