http: Improve auto HTTP->HTTPS redirects, fix edge cases

See https://caddy.community/t/v2-issues-with-multiple-server-blocks-in-caddyfile-style-config/6206/13?u=matt

Also print pid when using `caddy start`
This commit is contained in:
Matthew Holt 2019-09-18 18:01:32 -06:00
parent 39d61cad2d
commit 40e05e5a01
No known key found for this signature in database
GPG key ID: 2A349DD577D586A5
3 changed files with 86 additions and 74 deletions

View file

@ -130,7 +130,7 @@ func cmdStart() (int, error) {
// when one of the goroutines unblocks, we're done and can exit // when one of the goroutines unblocks, we're done and can exit
select { select {
case <-success: case <-success:
fmt.Println("Successfully started Caddy") fmt.Printf("Successfully started Caddy (pid=%d)\n", cmd.Process.Pid)
case err := <-exit: case err := <-exit:
return caddy.ExitCodeFailedStartup, return caddy.ExitCodeFailedStartup,
fmt.Errorf("caddy process exited with error: %v", err) fmt.Errorf("caddy process exited with error: %v", err)

View file

@ -183,12 +183,8 @@ func (app *App) Start() error {
} }
// enable TLS // enable TLS
httpPort := app.HTTPPort
if httpPort == 0 {
httpPort = DefaultHTTPPort
}
_, port, _ := net.SplitHostPort(addr) _, port, _ := net.SplitHostPort(addr)
if len(srv.TLSConnPolicies) > 0 && port != strconv.Itoa(httpPort) { if len(srv.TLSConnPolicies) > 0 && port != strconv.Itoa(app.httpPort()) {
tlsCfg, err := srv.TLSConnPolicies.TLSConfig(app.ctx) tlsCfg, err := srv.TLSConnPolicies.TLSConfig(app.ctx)
if err != nil { if err != nil {
return fmt.Errorf("%s/%s: making TLS configuration: %v", network, addr, err) return fmt.Errorf("%s/%s: making TLS configuration: %v", network, addr, err)
@ -273,8 +269,9 @@ func (app *App) automaticHTTPS() error {
} }
tlsApp := tlsAppIface.(*caddytls.TLS) tlsApp := tlsAppIface.(*caddytls.TLS)
lnAddrMap := make(map[string]struct{}) // this map will store associations of HTTP listener
var redirRoutes RouteList // addresses to the routes that do HTTP->HTTPS redirects
lnAddrRedirRoutes := make(map[string]Route)
for srvName, srv := range app.Servers { for srvName, srv := range app.Servers {
srv.tlsApp = tlsApp srv.tlsApp = tlsApp
@ -284,9 +281,9 @@ func (app *App) automaticHTTPS() error {
} }
// skip if all listeners use the HTTP port // skip if all listeners use the HTTP port
if !srv.listenersUseAnyPortOtherThan(app.HTTPPort) { if !srv.listenersUseAnyPortOtherThan(app.httpPort()) {
log.Printf("[INFO] Server %v is only listening on the HTTP port %d, so no automatic HTTPS will be applied to this server", log.Printf("[INFO] Server %s is only listening on the HTTP port %d, so no automatic HTTPS will be applied to this server",
srv.Listen, app.HTTPPort) srvName, app.httpPort())
continue continue
} }
@ -334,10 +331,10 @@ func (app *App) automaticHTTPS() error {
acmeManager := &caddytls.ACMEManagerMaker{ acmeManager := &caddytls.ACMEManagerMaker{
Challenges: caddytls.ChallengesConfig{ Challenges: caddytls.ChallengesConfig{
HTTP: caddytls.HTTPChallengeConfig{ HTTP: caddytls.HTTPChallengeConfig{
AlternatePort: app.HTTPPort, AlternatePort: app.HTTPPort, // we specifically want the user-configured port, if any
}, },
TLSALPN: caddytls.TLSALPNChallengeConfig{ TLSALPN: caddytls.TLSALPNChallengeConfig{
AlternatePort: app.HTTPSPort, AlternatePort: app.HTTPSPort, // we specifically want the user-configured port, if any
}, },
}, },
} }
@ -367,12 +364,6 @@ func (app *App) automaticHTTPS() error {
log.Printf("[INFO] Enabling automatic HTTP->HTTPS redirects for %v", domains) log.Printf("[INFO] Enabling automatic HTTP->HTTPS redirects for %v", domains)
// notify user if their config might override the HTTP->HTTPS redirects
if srv.listenersIncludePort(app.HTTPPort) {
log.Printf("[WARNING] Server %v is listening on HTTP port %d, so automatic HTTP->HTTPS redirects may be overridden by your own configuration",
srv.Listen, app.HTTPPort)
}
// create HTTP->HTTPS redirects // create HTTP->HTTPS redirects
for _, addr := range srv.Listen { for _, addr := range srv.Listen {
netw, host, port, err := caddy.SplitNetworkAddress(addr) netw, host, port, err := caddy.SplitNetworkAddress(addr)
@ -380,28 +371,22 @@ func (app *App) automaticHTTPS() error {
return fmt.Errorf("%s: invalid listener address: %v", srvName, addr) return fmt.Errorf("%s: invalid listener address: %v", srvName, addr)
} }
httpPort := app.HTTPPort
if httpPort == 0 {
httpPort = DefaultHTTPPort
}
httpRedirLnAddr := caddy.JoinNetworkAddress(netw, host, strconv.Itoa(httpPort))
lnAddrMap[httpRedirLnAddr] = struct{}{}
if parts := strings.SplitN(port, "-", 2); len(parts) == 2 { if parts := strings.SplitN(port, "-", 2); len(parts) == 2 {
port = parts[0] port = parts[0]
} }
redirTo := "https://{http.request.host}" redirTo := "https://{http.request.host}"
httpsPort := app.HTTPSPort if port != strconv.Itoa(app.httpsPort()) {
if httpsPort == 0 {
httpsPort = DefaultHTTPSPort
}
if port != strconv.Itoa(httpsPort) {
redirTo += ":" + port redirTo += ":" + port
} }
redirTo += "{http.request.uri}" redirTo += "{http.request.uri}"
redirRoutes = append(redirRoutes, Route{ // build the plaintext HTTP variant of this address
httpRedirLnAddr := caddy.JoinNetworkAddress(netw, host, strconv.Itoa(app.httpPort()))
// create the route that does the redirect and associate
// it with the listener address it will be served from
lnAddrRedirRoutes[httpRedirLnAddr] = Route{
MatcherSets: []MatcherSet{ MatcherSets: []MatcherSet{
{ {
MatchProtocol("http"), MatchProtocol("http"),
@ -418,52 +403,70 @@ func (app *App) automaticHTTPS() error {
Close: true, Close: true,
}, },
}, },
}) }
} }
} }
} }
if len(lnAddrMap) > 0 { // if there are HTTP->HTTPS redirects to add, do so now
var lnAddrs []string if len(lnAddrRedirRoutes) > 0 {
mapLoop: var redirServerAddrs []string
for addr := range lnAddrMap { var redirRoutes []Route
netw, addrs, err := caddy.ParseNetworkAddress(addr)
if err != nil { // for each redirect listener, see if there's already a
continue // server configured to listen on that exact address; if
} // so, simply the redirect route to the end of its route
for _, a := range addrs { // list; otherwise, we'll create a new server for all the
if app.listenerTaken(netw, a) { // listener addresses that are unused and serve the
continue mapLoop // remaining redirects from it
redirRoutesLoop:
for addr, redirRoute := range lnAddrRedirRoutes {
for srvName, srv := range app.Servers {
if srv.hasListenerAddress(addr) {
// user has configured a server for the same address
// that the redirect runs from; simply append our
// redirect route to the existing routes, with a
// caveat that their config might override ours
log.Printf("[WARNING] Server %s is listening on %s, so automatic HTTP->HTTPS redirects might be overridden by your own configuration",
srvName, addr)
srv.Routes = append(srv.Routes, redirRoute)
continue redirRoutesLoop
} }
} }
lnAddrs = append(lnAddrs, addr) // no server with this listener address exists;
// save this address and route for custom server
redirServerAddrs = append(redirServerAddrs, addr)
redirRoutes = append(redirRoutes, redirRoute)
} }
app.Servers["auto_https_redirects"] = &Server{
Listen: lnAddrs, // if there are routes remaining which do not belong
Routes: redirRoutes, // in any existing server, make our own to serve the
AutoHTTPS: &AutoHTTPSConfig{Disabled: true}, // rest of the redirects
tlsApp: tlsApp, // required to solve HTTP challenge if len(redirServerAddrs) > 0 {
app.Servers["remaining_auto_https_redirects"] = &Server{
Listen: redirServerAddrs,
Routes: redirRoutes,
tlsApp: tlsApp, // required to solve HTTP challenge
}
} }
} }
return nil return nil
} }
func (app *App) listenerTaken(network, address string) bool { func (app *App) httpPort() int {
for _, srv := range app.Servers { if app.HTTPPort == 0 {
for _, addr := range srv.Listen { return DefaultHTTPPort
netw, addrs, err := caddy.ParseNetworkAddress(addr)
if err != nil || netw != network {
continue
}
for _, a := range addrs {
if a == address {
return true
}
}
}
} }
return false return app.HTTPPort
}
func (app *App) httpsPort() int {
if app.HTTPSPort == 0 {
return DefaultHTTPSPort
}
return app.HTTPSPort
} }
var defaultALPN = []string{"h2", "http/1.1"} var defaultALPN = []string{"h2", "http/1.1"}

View file

@ -196,17 +196,26 @@ func (s *Server) listenersUseAnyPortOtherThan(otherPort int) bool {
return false return false
} }
// listenersIncludePort returns true if there are any func (s *Server) hasListenerAddress(fullAddr string) bool {
// listeners in s that use otherPort. netw, addrs, err := caddy.ParseNetworkAddress(fullAddr)
func (s *Server) listenersIncludePort(otherPort int) bool { if err != nil {
return false
}
if len(addrs) != 1 {
return false
}
addr := addrs[0]
for _, lnAddr := range s.Listen { for _, lnAddr := range s.Listen {
_, addrs, err := caddy.ParseNetworkAddress(lnAddr) thisNetw, thisAddrs, err := caddy.ParseNetworkAddress(lnAddr)
if err == nil { if err != nil {
for _, a := range addrs { continue
_, port, err := net.SplitHostPort(a) }
if err == nil && port == strconv.Itoa(otherPort) { if thisNetw != netw {
return true continue
} }
for _, a := range thisAddrs {
if a == addr {
return true
} }
} }
} }