@marcel91200 / caddy Mirror · read-only / commits / 1c190b00
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 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 }