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: Refactor site key parsing; detect conflicting schemes
We now store the parsed site/server block keys with the server block, rather than parsing the addresses every time we read them. Also detect conflicting schemes, i.e. TLS and non-TLS cannot be served from the same server (natively -- modules could be built for it). Also do not add site subroutes (subroutes generated specifically from site blocks in the Caddyfile) that are empty.
Matthew Holt committed
Apr 2, 2020 at 14:20 UTC
1c190b001b95e57d21dc63c01ae3c6de2a3fec57
4 files changed
+155
-101
caddyconfig/httpcaddyfile/addresses.go
+11
-1
@@ -106,12 +106,22 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
106
// server block are only the ones which use the address; but
107
// the contents (tokens) are of course the same
108
for addr, keys := range addrToKeys {
109
+ // parse keys so that we only have to do it once
110
+ parsedKeys := make([]Address, 0, len(keys))
111
+ for _, key := range keys {
112
+ addr, err := ParseAddress(key)
113
+ if err != nil {
114
+ return nil, fmt.Errorf("parsing key '%s': %v", key, err)
115
+ }
116
+ parsedKeys = append(parsedKeys, addr.Normalize())
117
+ }
118
sbmap[addr] = append(sbmap[addr], serverBlock{
119
block: caddyfile.ServerBlock{
120
Keys: keys,
121
Segments: sblock.block.Segments,
122
},
123
pile: sblock.pile,
124
+ keys: parsedKeys,
125
})
126
}
127
}
@@ -165,7 +175,7 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
175
176
// figure out the HTTP and HTTPS ports; either
177
// use defaults, or override with user config
168
- httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort)
178
+ httpPort, httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPPort), strconv.Itoa(caddyhttp.DefaultHTTPSPort)
179
if hport, ok := options["http_port"]; ok {
180
httpPort = strconv.Itoa(hport.(int))
181
}
caddyconfig/httpcaddyfile/directives.go
+42
-3
@@ -16,7 +16,9 @@ package httpcaddyfile
16
17
import (
18
"encoding/json"
19
+ "net"
20
"sort"
21
+ "strconv"
22
"strings"
23
24
"github.com/caddyserver/caddy/v2"
@@ -381,12 +383,49 @@ func parseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
383
return buildSubroute(allResults, h.groupCounter)
384
}
385
384
-// serverBlock pairs a Caddyfile server block
385
-// with a "pile" of config values, keyed by class
386
-// name.
386
+// serverBlock pairs a Caddyfile server block with
387
+// a "pile" of config values, keyed by class name,
388
+// as well as its parsed keys for convenience.
389
type serverBlock struct {
390
block caddyfile.ServerBlock
391
pile map[string][]ConfigValue // config values obtained from directives
392
+ keys []Address
393
+}
394
+
395
+// hostsFromKeys returns a list of all the non-empty hostnames found in
396
+// the keys of the server block sb, unless allowEmpty is true, in which
397
+// case a key with no host (e.g. ":443") will be added to the list as an
398
+// empty string. Otherwise, if allowEmpty is false, and if sb has a key
399
+// that omits the hostname (i.e. is a catch-all/empty host), then the returned
400
+// list is empty, because the server block effectively matches ALL hosts.
401
+// The list may not be in a consistent order. If includePorts is true, then
402
+// any non-empty, non-standard ports will be included.
403
+func (sb serverBlock) hostsFromKeys(allowEmpty, includePorts bool) []string {
404
+ // first get each unique hostname
405
+ hostMap := make(map[string]struct{})
406
+ for _, addr := range sb.keys {
407
+ if addr.Host == "" && !allowEmpty {
408
+ // server block contains a key like ":443", i.e. the host portion
409
+ // is empty / catch-all, which means to match all hosts
410
+ return []string{}
411
+ }
412
+ if includePorts &&
413
+ addr.Port != "" &&
414
+ addr.Port != strconv.Itoa(caddyhttp.DefaultHTTPPort) &&
415
+ addr.Port != strconv.Itoa(caddyhttp.DefaultHTTPSPort) {
416
+ hostMap[net.JoinHostPort(addr.Host, addr.Port)] = struct{}{}
417
+ } else {
418
+ hostMap[addr.Host] = struct{}{}
419
+ }
420
+ }
421
+
422
+ // convert map to slice
423
+ sblockHosts := make([]string, 0, len(hostMap))
424
+ for host := range hostMap {
425
+ sblockHosts = append(sblockHosts, host)
426
+ }
427
+
428
+ return sblockHosts
429
}
430
431
type (
caddyconfig/httpcaddyfile/httptype.go
+97
-79
@@ -17,7 +17,6 @@ package httpcaddyfile
17
import (
18
"encoding/json"
19
"fmt"
20
- "net"
20
"reflect"
21
"sort"
22
"strconv"
@@ -320,47 +319,6 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
319
return serverBlocks[1:], nil
320
}
321
323
-// hostsFromServerBlockKeys returns a list of all the non-empty hostnames
324
-// found in the keys of the server block sb, unless allowEmpty is true, in
325
-// which case a key with no host (e.g. ":443") will be added to the list as
326
-// an empty string. Otherwise, if allowEmpty is false, and if sb has a key
327
-// that omits the hostname (i.e. is a catch-all/empty host), then the returned
328
-// list is empty, because the server block effectively matches ALL hosts.
329
-// The list may not be in a consistent order. If includePorts is true, then
330
-// any non-empty, non-standard ports will be included.
331
-func (st *ServerType) hostsFromServerBlockKeys(sb caddyfile.ServerBlock, allowEmpty, includePorts bool) ([]string, error) {
332
- // first get each unique hostname
333
- hostMap := make(map[string]struct{})
334
- for _, sblockKey := range sb.Keys {
335
- addr, err := ParseAddress(sblockKey)
336
- if err != nil {
337
- return nil, fmt.Errorf("parsing server block key: %v", err)
338
- }
339
- addr = addr.Normalize()
340
- if addr.Host == "" && !allowEmpty {
341
- // server block contains a key like ":443", i.e. the host portion
342
- // is empty / catch-all, which means to match all hosts
343
- return []string{}, nil
344
- }
345
- if includePorts &&
346
- addr.Port != "" &&
347
- addr.Port != strconv.Itoa(caddyhttp.DefaultHTTPPort) &&
348
- addr.Port != strconv.Itoa(caddyhttp.DefaultHTTPSPort) {
349
- hostMap[net.JoinHostPort(addr.Host, addr.Port)] = struct{}{}
350
- } else {
351
- hostMap[addr.Host] = struct{}{}
352
- }
353
- }
354
-
355
- // convert map to slice
356
- sblockHosts := make([]string, 0, len(hostMap))
357
- for host := range hostMap {
358
- sblockHosts = append(sblockHosts, host)
359
- }
360
-
361
- return sblockHosts, nil
362
-}
363
-
322
// serversFromPairings creates the servers for each pairing of addresses
323
// to server blocks. Each pairing is essentially a server definition.
324
func (st *ServerType) serversFromPairings(
@@ -384,11 +342,10 @@ func (st *ServerType) serversFromPairings(
342
// descending sort by length of host then path
343
sort.SliceStable(p.serverBlocks, func(i, j int) bool {
344
// TODO: we could pre-process the specificities for efficiency,
387
- // but I don't expect many blocks will have SO many keys...
345
+ // but I don't expect many blocks will have THAT many keys...
346
var iLongestPath, jLongestPath string
347
var iLongestHost, jLongestHost string
390
- for _, key := range p.serverBlocks[i].block.Keys {
391
- addr, _ := ParseAddress(key)
348
+ for _, addr := range p.serverBlocks[i].keys {
349
if specificity(addr.Host) > specificity(iLongestHost) {
350
iLongestHost = addr.Host
351
}
@@ -396,8 +353,7 @@ func (st *ServerType) serversFromPairings(
353
iLongestPath = addr.Path
354
}
355
}
399
- for _, key := range p.serverBlocks[j].block.Keys {
400
- addr, _ := ParseAddress(key)
356
+ for _, addr := range p.serverBlocks[j].keys {
357
if specificity(addr.Host) > specificity(jLongestHost) {
358
jLongestHost = addr.Host
359
}
@@ -415,15 +371,12 @@ func (st *ServerType) serversFromPairings(
371
372
// create a subroute for each site in the server block
373
for _, sblock := range p.serverBlocks {
418
- matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock.block)
374
+ matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock)
375
if err != nil {
376
return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.block.Keys, err)
377
}
378
423
- hosts, err := st.hostsFromServerBlockKeys(sblock.block, false, false)
424
- if err != nil {
425
- return nil, err
426
- }
379
+ hosts := sblock.hostsFromKeys(false, false)
380
381
// tls: connection policies
382
if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
@@ -455,12 +408,7 @@ func (st *ServerType) serversFromPairings(
408
409
// exclude any hosts that were defined explicitly with
410
// "http://" in the key from automated cert management (issue #2998)
458
- for _, key := range sblock.block.Keys {
459
- addr, err := ParseAddress(key)
460
- if err != nil {
461
- return nil, err
462
- }
463
- addr = addr.Normalize()
411
+ for _, addr := range sblock.keys {
412
if addr.Scheme == "http" && addr.Host != "" {
413
if srv.AutoHTTPS == nil {
414
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
@@ -500,16 +448,19 @@ func (st *ServerType) serversFromPairings(
448
LoggerNames: make(map[string]string),
449
}
450
}
503
- hosts, err := st.hostsFromServerBlockKeys(sblock.block, true, true)
504
- if err != nil {
505
- return nil, err
506
- }
507
- for _, h := range hosts {
451
+ for _, h := range sblock.hostsFromKeys(true, true) {
452
srv.Logs.LoggerNames[h] = ncl.name
453
}
454
}
455
}
456
457
+ // a server cannot (natively) serve both HTTP and HTTPS at the
458
+ // same time, so make sure the configuration isn't in conflict
459
+ err := detectConflictingSchemes(srv, p.serverBlocks, options)
460
+ if err != nil {
461
+ return nil, err
462
+ }
463
+
464
// a catch-all TLS conn policy is necessary to ensure TLS can
465
// be offered to all hostnames of the server; even though only
466
// one policy is needed to enable TLS for the server, that
@@ -527,7 +478,6 @@ func (st *ServerType) serversFromPairings(
478
}
479
480
// tidy things up a bit
530
- var err error
481
srv.TLSConnPolicies, err = consolidateConnPolicies(srv.TLSConnPolicies)
482
if err != nil {
483
return nil, fmt.Errorf("consolidating TLS connection policies for server %d: %v", i, err)
@@ -540,6 +490,64 @@ func (st *ServerType) serversFromPairings(
490
return servers, nil
491
}
492
493
+func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock, options map[string]interface{}) error {
494
+ httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
495
+ if hp, ok := options["http_port"].(int); ok {
496
+ httpPort = strconv.Itoa(hp)
497
+ }
498
+ httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort)
499
+ if hsp, ok := options["https_port"].(int); ok {
500
+ httpsPort = strconv.Itoa(hsp)
501
+ }
502
+
503
+ var httpOrHTTPS string
504
+ checkAndSetHTTP := func(addr Address) error {
505
+ if httpOrHTTPS == "HTTPS" {
506
+ errMsg := fmt.Errorf("server listening on %v is configured for HTTPS and cannot natively multiplex HTTP and HTTPS: %s",
507
+ srv.Listen, addr.Original)
508
+ if addr.Scheme == "" && addr.Host == "" {
509
+ errMsg = fmt.Errorf("%s (try specifying https:// in the address)", errMsg)
510
+ }
511
+ return errMsg
512
+ }
513
+ if len(srv.TLSConnPolicies) > 0 {
514
+ // any connection policies created for an HTTP server
515
+ // is a logical conflict, as it would enable HTTPS
516
+ return fmt.Errorf("server listening on %v is HTTP, but attempts to configure TLS connection policies", srv.Listen)
517
+ }
518
+ httpOrHTTPS = "HTTP"
519
+ return nil
520
+ }
521
+ checkAndSetHTTPS := func(addr Address) error {
522
+ if httpOrHTTPS == "HTTP" {
523
+ return fmt.Errorf("server listening on %v is configured for HTTP and cannot natively multiplex HTTP and HTTPS: %s",
524
+ srv.Listen, addr.Original)
525
+ }
526
+ httpOrHTTPS = "HTTPS"
527
+ return nil
528
+ }
529
+
530
+ for _, sblock := range serverBlocks {
531
+ for _, addr := range sblock.keys {
532
+ if addr.Scheme == "http" || addr.Port == httpPort {
533
+ if err := checkAndSetHTTP(addr); err != nil {
534
+ return err
535
+ }
536
+ } else if addr.Scheme == "https" || addr.Port == httpsPort {
537
+ if err := checkAndSetHTTPS(addr); err != nil {
538
+ return err
539
+ }
540
+ } else if addr.Host == "" {
541
+ if err := checkAndSetHTTP(addr); err != nil {
542
+ return err
543
+ }
544
+ }
545
+ }
546
+ }
547
+
548
+ return nil
549
+}
550
+
551
// consolidateConnPolicies combines TLS connection policies that are the same,
552
// for a cleaner overall output.
553
func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.ConnectionPolicies, error) {
@@ -664,18 +672,34 @@ func appendSubrouteToRouteList(routeList caddyhttp.RouteList,
672
matcherSetsEnc []caddy.ModuleMap,
673
p sbAddrAssociation,
674
warnings *[]caddyconfig.Warning) caddyhttp.RouteList {
675
+
676
+ // nothing to do if... there's nothing to do
677
+ if len(matcherSetsEnc) == 0 && len(subroute.Routes) == 0 && subroute.Errors == nil {
678
+ return routeList
679
+ }
680
+
681
if len(matcherSetsEnc) == 0 && len(p.serverBlocks) == 1 {
682
// no need to wrap the handlers in a subroute if this is
683
// the only server block and there is no matcher for it
684
routeList = append(routeList, subroute.Routes...)
685
} else {
672
- routeList = append(routeList, caddyhttp.Route{
673
- MatcherSetsRaw: matcherSetsEnc,
674
- HandlersRaw: []json.RawMessage{
686
+ route := caddyhttp.Route{
687
+ // the semantics of a site block in the Caddyfile dictate
688
+ // that only the first matching one is evaluated, since
689
+ // site blocks do not cascade nor inherit
690
+ Terminal: true,
691
+ }
692
+ if len(matcherSetsEnc) > 0 {
693
+ route.MatcherSetsRaw = matcherSetsEnc
694
+ }
695
+ if len(subroute.Routes) > 0 || subroute.Errors != nil {
696
+ route.HandlersRaw = []json.RawMessage{
697
caddyconfig.JSONModuleObject(subroute, "handler", "subroute", warnings),
676
- },
677
- Terminal: true, // only first matching site block should be evaluated
678
- })
698
+ }
699
+ }
700
+ if len(route.MatcherSetsRaw) > 0 || len(route.HandlersRaw) > 0 {
701
+ routeList = append(routeList, route)
702
+ }
703
}
704
return routeList
705
}
@@ -822,7 +846,7 @@ func matcherSetFromMatcherToken(
846
return nil, false, nil
847
}
848
825
-func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([]caddy.ModuleMap, error) {
849
+func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.ModuleMap, error) {
850
type hostPathPair struct {
851
hostm caddyhttp.MatchHost
852
pathm caddyhttp.MatchPath
@@ -832,13 +856,7 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
856
var matcherPairs []*hostPathPair
857
858
var catchAllHosts bool
835
- for _, key := range sblock.Keys {
836
- addr, err := ParseAddress(key)
837
- if err != nil {
838
- return nil, fmt.Errorf("server block %v: parsing and standardizing address '%s': %v", sblock.Keys, key, err)
839
- }
840
- addr = addr.Normalize()
841
-
859
+ for _, addr := range sblock.keys {
860
// choose a matcher pair that should be shared by this
861
// server block; if none exists yet, create one
862
var chosenMatcherPair *hostPathPair
@@ -905,7 +923,7 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
923
for _, ms := range matcherSets {
924
msEncoded, err := encodeMatcherSet(ms)
925
if err != nil {
908
- return nil, fmt.Errorf("server block %v: %v", sblock.Keys, err)
926
+ return nil, fmt.Errorf("server block %v: %v", sblock.block.Keys, err)
927
}
928
matcherSetsEnc = append(matcherSetsEnc, msEncoded)
929
}
caddyconfig/httpcaddyfile/tlsapp.go
+5
-18
@@ -43,26 +43,16 @@ func (st ServerType) buildTLSApp(
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()
46
+ for _, addr := range sb.keys {
47
if addr.Host == "" {
48
serverBlocksWithHostlessKey++
49
// this server block has a hostless key, now
50
// go through and add all the hosts to the set
56
- for _, otherKey := range sb.block.Keys {
57
- if otherKey == key {
51
+ for _, otherAddr := range sb.keys {
52
+ if otherAddr.Original == addr.Original {
53
continue
54
}
60
- addr, err := ParseAddress(otherKey)
61
- if err != nil {
62
- return nil, warnings, err
63
- }
64
- addr = addr.Normalize()
65
- if addr.Host != "" {
55
+ if otherAddr.Host != "" {
56
hostsSharedWithHostlessKey[addr.Host] = struct{}{}
57
}
58
}
@@ -82,10 +72,7 @@ func (st ServerType) buildTLSApp(
72
// get values that populate an automation policy for this block
73
var ap *caddytls.AutomationPolicy
74
85
- sblockHosts, err := st.hostsFromServerBlockKeys(sblock.block, false, false)
86
- if err != nil {
87
- return nil, warnings, err
88
- }
75
+ sblockHosts := sblock.hostsFromKeys(false, false)
76
if len(sblockHosts) == 0 {
77
ap = catchAllAP
78
}