httpcaddyfile: Improve detection of indistinguishable TLS automation policies (#5120)

* httpcaddyfile: Skip some logic if auto_https off

* Try removing this check altogether...

* Refine test timeouts slightly, sigh

* caddyhttp: Assume udp for unrecognized network type

Seems like the reasonable thing to do if a plugin registers its own
network type.

* Add comment to document my lack of knowledge

* Clean up and prepare to merge

Add comments to try to explain what happened
This commit is contained in:
Matt Holt 2022-10-13 11:30:57 -06:00 committed by GitHub
parent 3e1fd2a8d4
commit 6bad878a22
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 56 additions and 58 deletions

View file

@ -44,30 +44,20 @@ func (st ServerType) buildTLSApp(
if hp, ok := options["http_port"].(int); ok { if hp, ok := options["http_port"].(int); ok {
httpPort = strconv.Itoa(hp) httpPort = strconv.Itoa(hp)
} }
httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort)
if hsp, ok := options["https_port"].(int); ok {
httpsPort = strconv.Itoa(hsp)
}
autoHTTPS := "on" autoHTTPS := "on"
if ah, ok := options["auto_https"].(string); ok { if ah, ok := options["auto_https"].(string); ok {
autoHTTPS = ah autoHTTPS = ah
} }
// count how many server blocks have a TLS-enabled key with // find all hosts that share a server block with a hostless
// no host, and find all hosts that share a server block with // key, so that they don't get forgotten/omitted by auto-HTTPS
// a hostless key, so that they don't get forgotten/omitted // (since they won't appear in route matchers)
// by auto-HTTPS (since they won't appear in route matchers)
var serverBlocksWithTLSHostlessKey int
httpsHostsSharedWithHostlessKey := make(map[string]struct{}) httpsHostsSharedWithHostlessKey := make(map[string]struct{})
if autoHTTPS != "off" {
for _, pair := range pairings { for _, pair := range pairings {
for _, sb := range pair.serverBlocks { for _, sb := range pair.serverBlocks {
for _, addr := range sb.keys { for _, addr := range sb.keys {
if addr.Host == "" { if addr.Host == "" {
// this address has no hostname, but if it's explicitly set
// to HTTPS, then we need to count it as being TLS-enabled
if addr.Scheme == "https" || addr.Port == httpsPort {
serverBlocksWithTLSHostlessKey++
}
// this server block has a hostless key, now // this server block has a hostless key, now
// go through and add all the hosts to the set // go through and add all the hosts to the set
for _, otherAddr := range sb.keys { for _, otherAddr := range sb.keys {
@ -83,6 +73,7 @@ func (st ServerType) buildTLSApp(
} }
} }
} }
}
// a catch-all automation policy is used as a "default" for all subjects that // a catch-all automation policy is used as a "default" for all subjects that
// don't have custom configuration explicitly associated with them; this // don't have custom configuration explicitly associated with them; this
@ -138,6 +129,19 @@ func (st ServerType) buildTLSApp(
issuers = append(issuers, issuerVal.Value.(certmagic.Issuer)) issuers = append(issuers, issuerVal.Value.(certmagic.Issuer))
} }
if ap == catchAllAP && !reflect.DeepEqual(ap.Issuers, issuers) { if ap == catchAllAP && !reflect.DeepEqual(ap.Issuers, issuers) {
// this more correctly implements an error check that was removed
// below; try it with this config:
//
// :443 {
// bind 127.0.0.1
// }
//
// :443 {
// bind ::1
// tls {
// issuer acme
// }
// }
return nil, warnings, fmt.Errorf("automation policy from site block is also default/catch-all policy because of key without hostname, and the two are in conflict: %#v != %#v", ap.Issuers, issuers) return nil, warnings, fmt.Errorf("automation policy from site block is also default/catch-all policy because of key without hostname, and the two are in conflict: %#v != %#v", ap.Issuers, issuers)
} }
ap.Issuers = issuers ap.Issuers = issuers
@ -180,30 +184,26 @@ func (st ServerType) buildTLSApp(
} }
} }
// first make sure this block is allowed to create an automation policy; // we used to ensure this block is allowed to create an automation policy;
// doing so is forbidden if it has a key with no host (i.e. ":443") // doing so was forbidden if it has a key with no host (i.e. ":443")
// and if there is a different server block that also has a key with no // and if there is a different server block that also has a key with no
// host -- since a key with no host matches any host, we need its // host -- since a key with no host matches any host, we need its
// associated automation policy to have an empty Subjects list, i.e. no // associated automation policy to have an empty Subjects list, i.e. no
// host filter, which is indistinguishable between the two server blocks // host filter, which is indistinguishable between the two server blocks
// because automation is not done in the context of a particular server... // because automation is not done in the context of a particular server...
// this is an example of a poor mapping from Caddyfile to JSON but that's // this is an example of a poor mapping from Caddyfile to JSON but that's
// the least-leaky abstraction I could figure out // the least-leaky abstraction I could figure out -- however, this check
if len(sblockHosts) == 0 { // was preventing certain listeners, like those provided by plugins, from
if serverBlocksWithTLSHostlessKey > 1 { // being used as desired (see the Tailscale listener plugin), so I removed
// this server block and at least one other has a key with no host, // the check: and I think since I originally wrote the check I added a new
// making the two indistinguishable; it is misleading to define such // check above which *properly* detects this ambiguity without breaking the
// a policy within one server block since it actually will apply to // listener plugin; see the check above with a commented example config
// others as well if len(sblockHosts) == 0 && catchAllAP == nil {
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")
}
if catchAllAP == nil {
// this server block has a key with no hosts, but there is not yet // this server block has a key with no hosts, but there is not yet
// a catch-all automation policy (probably because no global options // a catch-all automation policy (probably because no global options
// were set), so this one becomes it // were set), so this one becomes it
catchAllAP = ap catchAllAP = ap
} }
}
// associate our new automation policy with this server block's hosts // associate our new automation policy with this server block's hosts
ap.Subjects = sblock.hostsFromKeysNotHTTP(httpPort) ap.Subjects = sblock.hostsFromKeysNotHTTP(httpPort)

View file

@ -436,8 +436,8 @@ func TestReverseProxyHealthCheck(t *testing.T) {
health_uri /health health_uri /health
health_port 2021 health_port 2021
health_interval 2s health_interval 10ms
health_timeout 5s health_timeout 100ms
} }
} }
`, "caddyfile") `, "caddyfile")

6
go.mod
View file

@ -14,7 +14,7 @@ require (
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/klauspost/compress v1.15.11 github.com/klauspost/compress v1.15.11
github.com/klauspost/cpuid/v2 v2.1.1 github.com/klauspost/cpuid/v2 v2.1.1
github.com/lucas-clemente/quic-go v0.29.1 github.com/lucas-clemente/quic-go v0.29.2
github.com/mholt/acmez v1.0.4 github.com/mholt/acmez v1.0.4
github.com/prometheus/client_golang v1.12.2 github.com/prometheus/client_golang v1.12.2
github.com/smallstep/certificates v0.22.1 github.com/smallstep/certificates v0.22.1
@ -87,8 +87,8 @@ require (
github.com/libdns/libdns v0.2.1 // indirect github.com/libdns/libdns v0.2.1 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect github.com/manifoldco/promptui v0.9.0 // indirect
github.com/marten-seemann/qpack v0.2.1 // indirect github.com/marten-seemann/qpack v0.2.1 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect github.com/mattn/go-isatty v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect

12
go.sum
View file

@ -423,18 +423,18 @@ github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lucas-clemente/quic-go v0.29.1 h1:Z+WMJ++qMLhvpFkRZA+jl3BTxUjm415YBmWanXB8zP0= github.com/lucas-clemente/quic-go v0.29.2 h1:O8Mt0O6LpvEW+wfC40vZdcw0DngwYzoxq5xULZNzSI8=
github.com/lucas-clemente/quic-go v0.29.1/go.mod h1:CTcNfLYJS2UuRNB+zcNlgvkjBhxX6Hm3WUxxAQx2mgE= github.com/lucas-clemente/quic-go v0.29.2/go.mod h1:g6/h9YMmLuU54tL1gW25uIi3VlBp3uv+sBihplIuskE=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs= github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM= github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI=
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU= github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE=
github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=

View file

@ -500,14 +500,12 @@ func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error
switch addr.Network { switch addr.Network {
case "unix": case "unix":
addr.Network = "unixgram" addr.Network = "unixgram"
case "tcp":
addr.Network = "udp"
case "tcp4": case "tcp4":
addr.Network = "udp4" addr.Network = "udp4"
case "tcp6": case "tcp6":
addr.Network = "udp6" addr.Network = "udp6"
default: default:
return fmt.Errorf("unsure what network to use for HTTP/3 given network type: %s", addr.Network) addr.Network = "udp" // TODO: Maybe a better default is to not enable HTTP/3 if we do not know the network?
} }
lnAny, err := addr.Listen(s.ctx, 0, net.ListenConfig{}) lnAny, err := addr.Listen(s.ctx, 0, net.ListenConfig{})