@marcel91200 / caddy Mirror · read-only / commits / 97ed9e11
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: Add nil check to prevent panic, fix validation logic

Panic would happen if an automation policy was specified in a singular server block that had no hostnames in its address. Definitely an edge case. Fixed a bug related to checking for server blocks with a host-less key that tried to make an automation policy. Previously if you had only two server blocks like ":443" and another one at ":80", the one at ":443" could not create a TLS automation policy because it thought it would interfere with TLS automation for the block at ":80", but obviously that key doesn't enable TLS because it is on the HTTP port. So now we are a little smarter and count only non-HTTP-empty-hostname keys. Also fixed a bug so that a key like "https://:1234" is sure to have TLS enabled by giving it a TLS connection policy. (Relaxed conditions slightly; the previous conditions were too strict, requiring there to be a TLS conn policy already or a default SNI to be non-empty.) Also clarified a comment thanks to feedback from @Mohammed90

Matthew Holt committed Apr 24, 2020 at 18:58 UTC 97ed9e111d04718583c8e0cd141a464c993e224a
3 files changed +61 -27
caddyconfig/httpcaddyfile/httptype.go
+20 -8
@@ -324,6 +324,10 @@ func (st *ServerType) serversFromPairings(
324 if hp, ok := options["http_port"].(int); ok {
325 httpPort = strconv.Itoa(hp)
326 }
327 + httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort)
328 + if hsp, ok := options["https_port"].(int); ok {
329 + httpsPort = strconv.Itoa(hsp)
330 + }
331
332 for i, p := range pairings {
333 srv := &caddyhttp.Server{
@@ -362,7 +366,8 @@ func (st *ServerType) serversFromPairings(
366 return specificity(iLongestHost) > specificity(jLongestHost)
367 })
368
365 - var hasCatchAllTLSConnPolicy, usesTLS bool
369 + var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool
370 + autoHTTPSWillAddConnPolicy := true
371
372 // create a subroute for each site in the server block
373 for _, sblock := range p.serverBlocks {
@@ -401,9 +406,9 @@ func (st *ServerType) serversFromPairings(
406 }
407 }
408
404 - // exclude any hosts that were defined explicitly with
405 - // "http://" in the key from automated cert management (issue #2998)
409 for _, addr := range sblock.keys {
410 + // exclude any hosts that were defined explicitly with "http://"
411 + // in the key from automated cert management (issue #2998)
412 if addr.Scheme == "http" && addr.Host != "" {
413 if srv.AutoHTTPS == nil {
414 srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
@@ -412,9 +417,16 @@ func (st *ServerType) serversFromPairings(
417 srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, addr.Host)
418 }
419 }
415 - if addr.Scheme != "http" && addr.Host != "" && addr.Port != httpPort {
416 - usesTLS = true
420 + // we'll need to remember if the address qualifies for auto-HTTPS, so we
421 + // can add a TLS conn policy if necessary
422 + if addr.Scheme == "https" ||
423 + (addr.Scheme != "http" && addr.Host != "" && addr.Port != httpPort) {
424 + addressQualifiesForTLS = true
425 }
426 + // predict whether auto-HTTPS will add the conn policy for us; if so, we
427 + // may not need to add one for this server
428 + autoHTTPSWillAddConnPolicy = autoHTTPSWillAddConnPolicy &&
429 + (addr.Port == httpsPort || (addr.Port != httpPort && addr.Host != ""))
430 }
431
432 // set up each handler directive, making sure to honor directive order
@@ -477,9 +489,9 @@ func (st *ServerType) serversFromPairings(
489 // TODO: maybe a smarter way to handle this might be to just make the
490 // auto-HTTPS logic at provision-time detect if there is any connection
491 // policy missing for any HTTPS-enabled hosts, if so, add it... maybe?
480 - if usesTLS &&
492 + if addressQualifiesForTLS &&
493 !hasCatchAllTLSConnPolicy &&
482 - (len(srv.TLSConnPolicies) > 0 || defaultSNI != "") {
494 + (len(srv.TLSConnPolicies) > 0 || !autoHTTPSWillAddConnPolicy || defaultSNI != "") {
495 srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{DefaultSNI: defaultSNI})
496 }
497
@@ -539,7 +551,7 @@ func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock,
551 if err := checkAndSetHTTP(addr); err != nil {
552 return err
553 }
542 - } else if addr.Scheme == "https" || addr.Port == httpsPort {
554 + } else if addr.Scheme == "https" || addr.Port == httpsPort || len(srv.TLSConnPolicies) > 0 {
555 if err := checkAndSetHTTPS(addr); err != nil {
556 return err
557 }
caddyconfig/httpcaddyfile/tlsapp.go
+28 -13
@@ -20,9 +20,11 @@ import (
20 "fmt"
21 "reflect"
22 "sort"
23 + "strconv"
24
25 "github.com/caddyserver/caddy/v2"
26 "github.com/caddyserver/caddy/v2/caddyconfig"
27 + "github.com/caddyserver/caddy/v2/modules/caddyhttp"
28 "github.com/caddyserver/caddy/v2/modules/caddytls"
29 "github.com/caddyserver/certmagic"
30 )
@@ -36,17 +38,26 @@ func (st ServerType) buildTLSApp(
38 tlsApp := &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}
39 var certLoaders []caddytls.CertificateLoader
40
39 - // count how many server blocks have a key with no host,
40 - // and find all hosts that share a server block with a
41 - // hostless key, so that they don't get forgotten/omitted
41 + httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort)
42 + if hsp, ok := options["https_port"].(int); ok {
43 + httpsPort = strconv.Itoa(hsp)
44 + }
45 +
46 + // count how many server blocks have a TLS-enabled key with
47 + // no host, and find all hosts that share a server block with
48 + // a hostless key, so that they don't get forgotten/omitted
49 // by auto-HTTPS (since they won't appear in route matchers)
43 - var serverBlocksWithHostlessKey int
50 + var serverBlocksWithTLSHostlessKey int
51 hostsSharedWithHostlessKey := make(map[string]struct{})
52 for _, pair := range pairings {
53 for _, sb := range pair.serverBlocks {
54 for _, addr := range sb.keys {
55 if addr.Host == "" {
49 - serverBlocksWithHostlessKey++
56 + // this address has no hostname, but if it's explicitly set
57 + // to HTTPS, then we need to count it as being TLS-enabled
58 + if addr.Scheme == "https" || addr.Port == httpsPort {
59 + serverBlocksWithTLSHostlessKey++
60 + }
61 // this server block has a hostless key, now
62 // go through and add all the hosts to the set
63 for _, otherAddr := range sb.keys {
@@ -149,9 +160,11 @@ func (st ServerType) buildTLSApp(
160 }
161
162 if ap != nil {
152 - // encode issuer now that it's all set up
153 - issuerName := ap.Issuer.(caddy.Module).CaddyModule().ID.Name()
154 - ap.IssuerRaw = caddyconfig.JSONModuleObject(ap.Issuer, "module", issuerName, &warnings)
163 + if ap.Issuer != nil {
164 + // encode issuer now that it's all set up
165 + issuerName := ap.Issuer.(caddy.Module).CaddyModule().ID.Name()
166 + ap.IssuerRaw = caddyconfig.JSONModuleObject(ap.Issuer, "module", issuerName, &warnings)
167 + }
168
169 // first make sure this block is allowed to create an automation policy;
170 // doing so is forbidden if it has a key with no host (i.e. ":443")
@@ -163,12 +176,12 @@ func (st ServerType) buildTLSApp(
176 // this is an example of a poor mapping from Caddyfile to JSON but that's
177 // the least-leaky abstraction I could figure out
178 if len(sblockHosts) == 0 {
166 - if serverBlocksWithHostlessKey > 1 {
179 + if serverBlocksWithTLSHostlessKey > 1 {
180 // this server block and at least one other has a key with no host,
181 // making the two indistinguishable; it is misleading to define such
182 // a policy within one server block since it actually will apply to
183 // others as well
171 - 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")
184 + 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 TLS-enabled server block addresses lacking a host")
185 }
186 if catchAllAP == nil {
187 // this server block has a key with no hosts, but there is not yet
@@ -293,9 +306,11 @@ func (st ServerType) buildTLSApp(
306
307 // if there is a global/catch-all automation policy, ensure it goes last
308 if catchAllAP != nil {
296 - // first, encode its issuer
297 - issuerName := catchAllAP.Issuer.(caddy.Module).CaddyModule().ID.Name()
298 - catchAllAP.IssuerRaw = caddyconfig.JSONModuleObject(catchAllAP.Issuer, "module", issuerName, &warnings)
309 + // first, encode its issuer, if there is one
310 + if catchAllAP.Issuer != nil {
311 + issuerName := catchAllAP.Issuer.(caddy.Module).CaddyModule().ID.Name()
312 + catchAllAP.IssuerRaw = caddyconfig.JSONModuleObject(catchAllAP.Issuer, "module", issuerName, &warnings)
313 + }
314
315 // then append it to the end of the policies list
316 if tlsApp.Automation == nil {
modules/caddyhttp/autohttps.go
+13 -6
@@ -152,12 +152,12 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
152 }
153
154 // nothing more to do here if there are no domains that qualify for
155 - // automatic HTTPS or there are no explicit TLS connection policies;
156 - // if there is at least one domain but no TLS conn policy, we'll add
157 - // one below; if there is a TLS conn policy (meaning TLS is enabled)
158 - // and no domains, it could be a catch-all with on-demand TLS, and
159 - // in that case we would still need HTTP->HTTPS redirects, which we
160 - // do below
155 + // automatic HTTPS and there are no explicit TLS connection policies:
156 + // if there is at least one domain but no TLS conn policy (F&&T), we'll
157 + // add one below; if there are no domains but at least one TLS conn
158 + // policy (meaning TLS is enabled) (T&&F), it could be a catch-all with
159 + // on-demand TLS -- and in that case we would still need HTTP->HTTPS
160 + // redirects, which we set up below; hence these two conditions
161 if len(serverDomainSet) == 0 && len(srv.TLSConnPolicies) == 0 {
162 continue
163 }
@@ -345,6 +345,13 @@ uniqueDomainsLoop:
345 // not entirely clear what the redirect destination should be,
346 // so I'm going to just hard-code the app's HTTPS port and call
347 // it good for now...
348 + // TODO: This implies that all plaintext requests will be blindly
349 + // redirected to their HTTPS equivalent, even if this server
350 + // doesn't handle that hostname at all; I don't think this is a
351 + // bad thing, and it also obscures the actual hostnames that this
352 + // server is configured to match on, which may be desirable, but
353 + // it's not something that should be relied on. We can change this
354 + // if we want to.
355 appendCatchAll := func(routes []Route) []Route {
356 redirTo := "https://{http.request.host}"
357 if app.httpsPort() != DefaultHTTPSPort {