summaryrefslogtreecommitdiff
path: root/caddyconfig
diff options
context:
space:
mode:
authorMatthew Holt <mholt@users.noreply.github.com>2020-04-02 14:20:30 -0600
committerMatthew Holt <mholt@users.noreply.github.com>2020-04-02 14:24:53 -0600
commit1c190b001b95e57d21dc63c01ae3c6de2a3fec57 (patch)
tree762fca56efffb3cedc7de7128f42c6c479e3a34f /caddyconfig
parent3634c4593f2d9999dca2d7a02e23edc29bf7bd11 (diff)
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.
Diffstat (limited to 'caddyconfig')
-rw-r--r--caddyconfig/httpcaddyfile/addresses.go12
-rw-r--r--caddyconfig/httpcaddyfile/directives.go45
-rw-r--r--caddyconfig/httpcaddyfile/httptype.go176
-rw-r--r--caddyconfig/httpcaddyfile/tlsapp.go23
4 files changed, 155 insertions, 101 deletions
diff --git a/caddyconfig/httpcaddyfile/addresses.go b/caddyconfig/httpcaddyfile/addresses.go
index f5fd6b6..6545585 100644
--- a/caddyconfig/httpcaddyfile/addresses.go
+++ b/caddyconfig/httpcaddyfile/addresses.go
@@ -106,12 +106,22 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
// server block are only the ones which use the address; but
// the contents (tokens) are of course the same
for addr, keys := range addrToKeys {
+ // parse keys so that we only have to do it once
+ parsedKeys := make([]Address, 0, len(keys))
+ for _, key := range keys {
+ addr, err := ParseAddress(key)
+ if err != nil {
+ return nil, fmt.Errorf("parsing key '%s': %v", key, err)
+ }
+ parsedKeys = append(parsedKeys, addr.Normalize())
+ }
sbmap[addr] = append(sbmap[addr], serverBlock{
block: caddyfile.ServerBlock{
Keys: keys,
Segments: sblock.block.Segments,
},
pile: sblock.pile,
+ keys: parsedKeys,
})
}
}
@@ -165,7 +175,7 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
// figure out the HTTP and HTTPS ports; either
// use defaults, or override with user config
- httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort)
+ httpPort, httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPPort), strconv.Itoa(caddyhttp.DefaultHTTPSPort)
if hport, ok := options["http_port"]; ok {
httpPort = strconv.Itoa(hport.(int))
}
diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go
index 4c2b2d9..8fa48cd 100644
--- a/caddyconfig/httpcaddyfile/directives.go
+++ b/caddyconfig/httpcaddyfile/directives.go
@@ -16,7 +16,9 @@ package httpcaddyfile
import (
"encoding/json"
+ "net"
"sort"
+ "strconv"
"strings"
"github.com/caddyserver/caddy/v2"
@@ -381,12 +383,49 @@ func parseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
return buildSubroute(allResults, h.groupCounter)
}
-// serverBlock pairs a Caddyfile server block
-// with a "pile" of config values, keyed by class
-// name.
+// serverBlock pairs a Caddyfile server block with
+// a "pile" of config values, keyed by class name,
+// as well as its parsed keys for convenience.
type serverBlock struct {
block caddyfile.ServerBlock
pile map[string][]ConfigValue // config values obtained from directives
+ keys []Address
+}
+
+// hostsFromKeys returns a list of all the non-empty hostnames found in
+// the keys of the server block sb, unless allowEmpty is true, in which
+// case a key with no host (e.g. ":443") will be added to the list as an
+// empty string. Otherwise, if allowEmpty is false, and if sb has a key
+// that omits the hostname (i.e. is a catch-all/empty host), then the returned
+// list is empty, because the server block effectively matches ALL hosts.
+// The list may not be in a consistent order. If includePorts is true, then
+// any non-empty, non-standard ports will be included.
+func (sb serverBlock) hostsFromKeys(allowEmpty, includePorts bool) []string {
+ // first get each unique hostname
+ hostMap := make(map[string]struct{})
+ for _, addr := range sb.keys {
+ if addr.Host == "" && !allowEmpty {
+ // server block contains a key like ":443", i.e. the host portion
+ // is empty / catch-all, which means to match all hosts
+ return []string{}
+ }
+ if includePorts &&
+ addr.Port != "" &&
+ addr.Port != strconv.Itoa(caddyhttp.DefaultHTTPPort) &&
+ addr.Port != strconv.Itoa(caddyhttp.DefaultHTTPSPort) {
+ hostMap[net.JoinHostPort(addr.Host, addr.Port)] = struct{}{}
+ } else {
+ hostMap[addr.Host] = struct{}{}
+ }
+ }
+
+ // convert map to slice
+ sblockHosts := make([]string, 0, len(hostMap))
+ for host := range hostMap {
+ sblockHosts = append(sblockHosts, host)
+ }
+
+ return sblockHosts
}
type (
diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go
index 4df5421..7fbf724 100644
--- a/caddyconfig/httpcaddyfile/httptype.go
+++ b/caddyconfig/httpcaddyfile/httptype.go
@@ -17,7 +17,6 @@ package httpcaddyfile
import (
"encoding/json"
"fmt"
- "net"
"reflect"
"sort"
"strconv"
@@ -320,47 +319,6 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
return serverBlocks[1:], nil
}
-// hostsFromServerBlockKeys returns a list of all the non-empty hostnames
-// found in the keys of the server block sb, unless allowEmpty is true, in
-// which case a key with no host (e.g. ":443") will be added to the list as
-// an empty string. Otherwise, if allowEmpty is false, and if sb has a key
-// that omits the hostname (i.e. is a catch-all/empty host), then the returned
-// list is empty, because the server block effectively matches ALL hosts.
-// The list may not be in a consistent order. If includePorts is true, then
-// any non-empty, non-standard ports will be included.
-func (st *ServerType) hostsFromServerBlockKeys(sb caddyfile.ServerBlock, allowEmpty, includePorts bool) ([]string, error) {
- // first get each unique hostname
- hostMap := make(map[string]struct{})
- for _, sblockKey := range sb.Keys {
- addr, err := ParseAddress(sblockKey)
- if err != nil {
- return nil, fmt.Errorf("parsing server block key: %v", err)
- }
- addr = addr.Normalize()
- if addr.Host == "" && !allowEmpty {
- // server block contains a key like ":443", i.e. the host portion
- // is empty / catch-all, which means to match all hosts
- return []string{}, nil
- }
- if includePorts &&
- addr.Port != "" &&
- addr.Port != strconv.Itoa(caddyhttp.DefaultHTTPPort) &&
- addr.Port != strconv.Itoa(caddyhttp.DefaultHTTPSPort) {
- hostMap[net.JoinHostPort(addr.Host, addr.Port)] = struct{}{}
- } else {
- hostMap[addr.Host] = struct{}{}
- }
- }
-
- // convert map to slice
- sblockHosts := make([]string, 0, len(hostMap))
- for host := range hostMap {
- sblockHosts = append(sblockHosts, host)
- }
-
- return sblockHosts, nil
-}
-
// serversFromPairings creates the servers for each pairing of addresses
// to server blocks. Each pairing is essentially a server definition.
func (st *ServerType) serversFromPairings(
@@ -384,11 +342,10 @@ func (st *ServerType) serversFromPairings(
// descending sort by length of host then path
sort.SliceStable(p.serverBlocks, func(i, j int) bool {
// TODO: we could pre-process the specificities for efficiency,
- // but I don't expect many blocks will have SO many keys...
+ // but I don't expect many blocks will have THAT many keys...
var iLongestPath, jLongestPath string
var iLongestHost, jLongestHost string
- for _, key := range p.serverBlocks[i].block.Keys {
- addr, _ := ParseAddress(key)
+ for _, addr := range p.serverBlocks[i].keys {
if specificity(addr.Host) > specificity(iLongestHost) {
iLongestHost = addr.Host
}
@@ -396,8 +353,7 @@ func (st *ServerType) serversFromPairings(
iLongestPath = addr.Path
}
}
- for _, key := range p.serverBlocks[j].block.Keys {
- addr, _ := ParseAddress(key)
+ for _, addr := range p.serverBlocks[j].keys {
if specificity(addr.Host) > specificity(jLongestHost) {
jLongestHost = addr.Host
}
@@ -415,15 +371,12 @@ func (st *ServerType) serversFromPairings(
// create a subroute for each site in the server block
for _, sblock := range p.serverBlocks {
- matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock.block)
+ matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock)
if err != nil {
return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.block.Keys, err)
}
- hosts, err := st.hostsFromServerBlockKeys(sblock.block, false, false)
- if err != nil {
- return nil, err
- }
+ hosts := sblock.hostsFromKeys(false, false)
// tls: connection policies
if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
@@ -455,12 +408,7 @@ func (st *ServerType) serversFromPairings(
// exclude any hosts that were defined explicitly with
// "http://" in the key from automated cert management (issue #2998)
- for _, key := range sblock.block.Keys {
- addr, err := ParseAddress(key)
- if err != nil {
- return nil, err
- }
- addr = addr.Normalize()
+ for _, addr := range sblock.keys {
if addr.Scheme == "http" && addr.Host != "" {
if srv.AutoHTTPS == nil {
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
@@ -500,16 +448,19 @@ func (st *ServerType) serversFromPairings(
LoggerNames: make(map[string]string),
}
}
- hosts, err := st.hostsFromServerBlockKeys(sblock.block, true, true)
- if err != nil {
- return nil, err
- }
- for _, h := range hosts {
+ for _, h := range sblock.hostsFromKeys(true, true) {
srv.Logs.LoggerNames[h] = ncl.name
}
}
}
+ // a server cannot (natively) serve both HTTP and HTTPS at the
+ // same time, so make sure the configuration isn't in conflict
+ err := detectConflictingSchemes(srv, p.serverBlocks, options)
+ if err != nil {
+ return nil, err
+ }
+
// a catch-all TLS conn policy is necessary to ensure TLS can
// be offered to all hostnames of the server; even though only
// one policy is needed to enable TLS for the server, that
@@ -527,7 +478,6 @@ func (st *ServerType) serversFromPairings(
}
// tidy things up a bit
- var err error
srv.TLSConnPolicies, err = consolidateConnPolicies(srv.TLSConnPolicies)
if err != nil {
return nil, fmt.Errorf("consolidating TLS connection policies for server %d: %v", i, err)
@@ -540,6 +490,64 @@ func (st *ServerType) serversFromPairings(
return servers, nil
}
+func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock, options map[string]interface{}) error {
+ httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
+ if hp, ok := options["http_port"].(int); ok {
+ httpPort = strconv.Itoa(hp)
+ }
+ httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort)
+ if hsp, ok := options["https_port"].(int); ok {
+ httpsPort = strconv.Itoa(hsp)
+ }
+
+ var httpOrHTTPS string
+ checkAndSetHTTP := func(addr Address) error {
+ if httpOrHTTPS == "HTTPS" {
+ errMsg := fmt.Errorf("server listening on %v is configured for HTTPS and cannot natively multiplex HTTP and HTTPS: %s",
+ srv.Listen, addr.Original)
+ if addr.Scheme == "" && addr.Host == "" {
+ errMsg = fmt.Errorf("%s (try specifying https:// in the address)", errMsg)
+ }
+ return errMsg
+ }
+ if len(srv.TLSConnPolicies) > 0 {
+ // any connection policies created for an HTTP server
+ // is a logical conflict, as it would enable HTTPS
+ return fmt.Errorf("server listening on %v is HTTP, but attempts to configure TLS connection policies", srv.Listen)
+ }
+ httpOrHTTPS = "HTTP"
+ return nil
+ }
+ checkAndSetHTTPS := func(addr Address) error {
+ if httpOrHTTPS == "HTTP" {
+ return fmt.Errorf("server listening on %v is configured for HTTP and cannot natively multiplex HTTP and HTTPS: %s",
+ srv.Listen, addr.Original)
+ }
+ httpOrHTTPS = "HTTPS"
+ return nil
+ }
+
+ for _, sblock := range serverBlocks {
+ for _, addr := range sblock.keys {
+ if addr.Scheme == "http" || addr.Port == httpPort {
+ if err := checkAndSetHTTP(addr); err != nil {
+ return err
+ }
+ } else if addr.Scheme == "https" || addr.Port == httpsPort {
+ if err := checkAndSetHTTPS(addr); err != nil {
+ return err
+ }
+ } else if addr.Host == "" {
+ if err := checkAndSetHTTP(addr); err != nil {
+ return err
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
// consolidateConnPolicies combines TLS connection policies that are the same,
// for a cleaner overall output.
func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.ConnectionPolicies, error) {
@@ -664,18 +672,34 @@ func appendSubrouteToRouteList(routeList caddyhttp.RouteList,
matcherSetsEnc []caddy.ModuleMap,
p sbAddrAssociation,
warnings *[]caddyconfig.Warning) caddyhttp.RouteList {
+
+ // nothing to do if... there's nothing to do
+ if len(matcherSetsEnc) == 0 && len(subroute.Routes) == 0 && subroute.Errors == nil {
+ return routeList
+ }
+
if len(matcherSetsEnc) == 0 && len(p.serverBlocks) == 1 {
// no need to wrap the handlers in a subroute if this is
// the only server block and there is no matcher for it
routeList = append(routeList, subroute.Routes...)
} else {
- routeList = append(routeList, caddyhttp.Route{
- MatcherSetsRaw: matcherSetsEnc,
- HandlersRaw: []json.RawMessage{
+ route := caddyhttp.Route{
+ // the semantics of a site block in the Caddyfile dictate
+ // that only the first matching one is evaluated, since
+ // site blocks do not cascade nor inherit
+ Terminal: true,
+ }
+ if len(matcherSetsEnc) > 0 {
+ route.MatcherSetsRaw = matcherSetsEnc
+ }
+ if len(subroute.Routes) > 0 || subroute.Errors != nil {
+ route.HandlersRaw = []json.RawMessage{
caddyconfig.JSONModuleObject(subroute, "handler", "subroute", warnings),
- },
- Terminal: true, // only first matching site block should be evaluated
- })
+ }
+ }
+ if len(route.MatcherSetsRaw) > 0 || len(route.HandlersRaw) > 0 {
+ routeList = append(routeList, route)
+ }
}
return routeList
}
@@ -822,7 +846,7 @@ func matcherSetFromMatcherToken(
return nil, false, nil
}
-func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([]caddy.ModuleMap, error) {
+func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.ModuleMap, error) {
type hostPathPair struct {
hostm caddyhttp.MatchHost
pathm caddyhttp.MatchPath
@@ -832,13 +856,7 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
var matcherPairs []*hostPathPair
var catchAllHosts bool
- for _, key := range sblock.Keys {
- addr, err := ParseAddress(key)
- if err != nil {
- return nil, fmt.Errorf("server block %v: parsing and standardizing address '%s': %v", sblock.Keys, key, err)
- }
- addr = addr.Normalize()
-
+ for _, addr := range sblock.keys {
// choose a matcher pair that should be shared by this
// server block; if none exists yet, create one
var chosenMatcherPair *hostPathPair
@@ -905,7 +923,7 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
for _, ms := range matcherSets {
msEncoded, err := encodeMatcherSet(ms)
if err != nil {
- return nil, fmt.Errorf("server block %v: %v", sblock.Keys, err)
+ return nil, fmt.Errorf("server block %v: %v", sblock.block.Keys, err)
}
matcherSetsEnc = append(matcherSetsEnc, msEncoded)
}
diff --git a/caddyconfig/httpcaddyfile/tlsapp.go b/caddyconfig/httpcaddyfile/tlsapp.go
index db3d13b..6214d61 100644
--- a/caddyconfig/httpcaddyfile/tlsapp.go
+++ b/caddyconfig/httpcaddyfile/tlsapp.go
@@ -43,26 +43,16 @@ func (st ServerType) buildTLSApp(
hostsSharedWithHostlessKey := make(map[string]struct{})
for _, pair := range pairings {
for _, sb := range pair.serverBlocks {
- for _, key := range sb.block.Keys {
- addr, err := ParseAddress(key)
- if err != nil {
- return nil, warnings, err
- }
- addr = addr.Normalize()
+ for _, addr := range sb.keys {
if addr.Host == "" {
serverBlocksWithHostlessKey++
// this server block has a hostless key, now
// go through and add all the hosts to the set
- for _, otherKey := range sb.block.Keys {
- if otherKey == key {
+ for _, otherAddr := range sb.keys {
+ if otherAddr.Original == addr.Original {
continue
}
- addr, err := ParseAddress(otherKey)
- if err != nil {
- return nil, warnings, err
- }
- addr = addr.Normalize()
- if addr.Host != "" {
+ if otherAddr.Host != "" {
hostsSharedWithHostlessKey[addr.Host] = struct{}{}
}
}
@@ -82,10 +72,7 @@ func (st ServerType) buildTLSApp(
// get values that populate an automation policy for this block
var ap *caddytls.AutomationPolicy
- sblockHosts, err := st.hostsFromServerBlockKeys(sblock.block, false, false)
- if err != nil {
- return nil, warnings, err
- }
+ sblockHosts := sblock.hostsFromKeys(false, false)
if len(sblockHosts) == 0 {
ap = catchAllAP
}