mirror of
https://github.com/caddyserver/caddy.git
synced 2026-01-16 17:20:34 +00:00
autohttps: deterministic logic and strict bind checking on Linux (#7435)
* http: fix non-deterministic auto-https and improve Linux bind matching * docs: restore historical context about Linux bind behavior
This commit is contained in:
@@ -90,7 +90,16 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
||||
// the log configuration for an HTTPS enabled server
|
||||
var logCfg *ServerLogConfig
|
||||
|
||||
for srvName, srv := range app.Servers {
|
||||
// Sort server names to ensure deterministic iteration.
|
||||
// This prevents race conditions where the order of server processing
|
||||
// could affect which server gets assigned the HTTP->HTTPS redirect listener.
|
||||
srvNames := make([]string, 0, len(app.Servers))
|
||||
for name := range app.Servers {
|
||||
srvNames = append(srvNames, name)
|
||||
}
|
||||
slices.Sort(srvNames)
|
||||
for _, srvName := range srvNames {
|
||||
srv := app.Servers[srvName]
|
||||
// as a prerequisite, provision route matchers; this is
|
||||
// required for all routes on all servers, and must be
|
||||
// done before we attempt to do phase 1 of auto HTTPS,
|
||||
@@ -398,15 +407,26 @@ uniqueDomainsLoop:
|
||||
return append(routes, app.makeRedirRoute(uint(app.httpsPort()), MatcherSet{MatchProtocol("http")}))
|
||||
}
|
||||
|
||||
// Sort redirect addresses to ensure deterministic process
|
||||
redirServerAddrsSorted := make([]string, 0, len(redirServers))
|
||||
for addr := range redirServers {
|
||||
redirServerAddrsSorted = append(redirServerAddrsSorted, addr)
|
||||
}
|
||||
slices.Sort(redirServerAddrsSorted)
|
||||
|
||||
redirServersLoop:
|
||||
for redirServerAddr, routes := range redirServers {
|
||||
for _, redirServerAddr := range redirServerAddrsSorted {
|
||||
routes := redirServers[redirServerAddr]
|
||||
// for each redirect listener, see if there's already a
|
||||
// server configured to listen on that exact address; if so,
|
||||
// insert the redirect route to the end of its route list
|
||||
// after any other routes with host matchers; otherwise,
|
||||
// we'll create a new server for all the listener addresses
|
||||
// that are unused and serve the remaining redirects from it
|
||||
for _, srv := range app.Servers {
|
||||
|
||||
// Use the sorted srvNames to consistently find the target server
|
||||
for _, srvName := range srvNames {
|
||||
srv := app.Servers[srvName]
|
||||
// only look at servers which listen on an address which
|
||||
// we want to add redirects to
|
||||
if !srv.hasListenerAddress(redirServerAddr) {
|
||||
|
||||
@@ -556,15 +556,21 @@ func (s *Server) hasListenerAddress(fullAddr string) bool {
|
||||
// The second issue seems very similar to a discussion here:
|
||||
// https://github.com/nodejs/node/issues/9390
|
||||
//
|
||||
// This is very easy to reproduce by creating an HTTP server
|
||||
// that listens to both addresses or just one with a host
|
||||
// interface; or for a more confusing reproduction, try
|
||||
// listening on "127.0.0.1:80" and ":443" and you'll see
|
||||
// the error, if you take away the GOOS condition below.
|
||||
//
|
||||
// So, an address is equivalent if the port is in the port
|
||||
// range, and if not on Linux, the host is the same... sigh.
|
||||
if (runtime.GOOS == "linux" || thisAddrs.Host == laddrs.Host) &&
|
||||
// However, binding to *different specific* interfaces
|
||||
// (e.g. 127.0.0.2:80 and 127.0.0.3:80) IS allowed on Linux.
|
||||
// The conflict only happens when mixing specific IPs with
|
||||
// wildcards (0.0.0.0 or ::).
|
||||
|
||||
// Hosts match exactly (e.g. 127.0.0.2 == 127.0.0.2) -> Conflict.
|
||||
hostMatch := thisAddrs.Host == laddrs.Host
|
||||
|
||||
// On Linux, specific IP vs Wildcard fails to bind.
|
||||
// So if we are on Linux AND either host is empty (wildcard), we treat
|
||||
// it as a match (conflict). But if both are specific and different
|
||||
// (127.0.0.2 vs 127.0.0.3), this remains false (no conflict).
|
||||
linuxWildcardConflict := runtime.GOOS == "linux" && (thisAddrs.Host == "" || laddrs.Host == "")
|
||||
|
||||
if (hostMatch || linuxWildcardConflict) &&
|
||||
(laddrs.StartPort <= thisAddrs.EndPort) &&
|
||||
(laddrs.StartPort >= thisAddrs.StartPort) {
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user