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
admin: Always enforce Host header checks
With a simple heuristic for loopback addresses, we can enable this by default without adding unnecessary inconvenience.
Matthew Holt committed
Apr 10, 2020 at 17:31 UTC
a3bdc22234b75e9420f8810918072fa34732ffb7
3 files changed
+69
-26
admin.go
+37
-19
@@ -21,6 +21,7 @@ import (
21
"expvar"
22
"fmt"
23
"io"
24
+ "net"
25
"net/http"
26
"net/http/pprof"
27
"net/url"
@@ -78,27 +79,27 @@ type ConfigSettings struct {
79
80
// listenAddr extracts a singular listen address from ac.Listen,
81
// returning the network and the address of the listener.
81
-func (admin AdminConfig) listenAddr() (string, string, error) {
82
+func (admin AdminConfig) listenAddr() (NetworkAddress, error) {
83
input := admin.Listen
84
if input == "" {
85
input = DefaultAdminListen
86
}
87
listenAddr, err := ParseNetworkAddress(input)
88
if err != nil {
88
- return "", "", fmt.Errorf("parsing admin listener address: %v", err)
89
+ return NetworkAddress{}, fmt.Errorf("parsing admin listener address: %v", err)
90
}
91
if listenAddr.PortRangeSize() != 1 {
91
- return "", "", fmt.Errorf("admin endpoint must have exactly one address; cannot listen on %v", listenAddr)
92
+ return NetworkAddress{}, fmt.Errorf("admin endpoint must have exactly one address; cannot listen on %v", listenAddr)
93
}
93
- return listenAddr.Network, listenAddr.JoinHostPort(0), nil
94
+ return listenAddr, nil
95
}
96
97
// newAdminHandler reads admin's config and returns an http.Handler suitable
98
// for use in an admin endpoint server, which will be listening on listenAddr.
98
-func (admin AdminConfig) newAdminHandler(listenAddr string) adminHandler {
99
+func (admin AdminConfig) newAdminHandler(addr NetworkAddress) adminHandler {
100
muxWrap := adminHandler{
101
enforceOrigin: admin.EnforceOrigin,
101
- allowedOrigins: admin.allowedOrigins(listenAddr),
102
+ allowedOrigins: admin.allowedOrigins(addr),
103
mux: http.NewServeMux(),
104
}
105
@@ -140,14 +141,30 @@ func (admin AdminConfig) newAdminHandler(listenAddr string) adminHandler {
141
// If admin.Origins is nil (null), the provided listen address
142
// will be used as the default origin. If admin.Origins is
143
// empty, no origins will be allowed, effectively bricking the
143
-// endpoint, but whatever.
144
-func (admin AdminConfig) allowedOrigins(listen string) []string {
144
+// endpoint for non-unix-socket endpoints, but whatever.
145
+func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []string {
146
uniqueOrigins := make(map[string]struct{})
147
for _, o := range admin.Origins {
148
uniqueOrigins[o] = struct{}{}
149
}
150
if admin.Origins == nil {
150
- uniqueOrigins[listen] = struct{}{}
151
+ if addr.isLoopback() {
152
+ if addr.IsUnixNetwork() {
153
+ // RFC 2616, Section 14.26:
154
+ // "A client MUST include a Host header field in all HTTP/1.1 request
155
+ // messages. If the requested URI does not include an Internet host
156
+ // name for the service being requested, then the Host header field MUST
157
+ // be given with an empty value."
158
+ uniqueOrigins[""] = struct{}{}
159
+ } else {
160
+ uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
161
+ uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
162
+ uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
163
+ }
164
+ }
165
+ if !addr.IsUnixNetwork() {
166
+ uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
167
+ }
168
}
169
allowed := make([]string, 0, len(uniqueOrigins))
170
for origin := range uniqueOrigins {
@@ -195,14 +212,14 @@ func replaceAdmin(cfg *Config) error {
212
}
213
214
// extract a singular listener address
198
- netw, addr, err := adminConfig.listenAddr()
215
+ addr, err := adminConfig.listenAddr()
216
if err != nil {
217
return err
218
}
219
220
handler := adminConfig.newAdminHandler(addr)
221
205
- ln, err := Listen(netw, addr)
222
+ ln, err := Listen(addr.Network, addr.JoinHostPort(0))
223
if err != nil {
224
return err
225
}
@@ -219,7 +236,7 @@ func replaceAdmin(cfg *Config) error {
236
237
Log().Named("admin").Info(
238
"admin endpoint started",
222
- zap.String("address", addr),
239
+ zap.String("address", addr.String()),
240
zap.Bool("enforce_origin", adminConfig.EnforceOrigin),
241
zap.Strings("origins", handler.allowedOrigins),
242
)
@@ -263,6 +280,7 @@ type adminHandler struct {
280
func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
281
Log().Named("admin.api").Info("received request",
282
zap.String("method", r.Method),
283
+ zap.String("host", r.Host),
284
zap.String("uri", r.RequestURI),
285
zap.String("remote_addr", r.RemoteAddr),
286
zap.Reflect("headers", r.Header),
@@ -274,14 +292,14 @@ func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
292
// be called more than once per request, for example if a request
293
// is rewritten (i.e. internal redirect).
294
func (h adminHandler) serveHTTP(w http.ResponseWriter, r *http.Request) {
277
- if h.enforceOrigin {
278
- // DNS rebinding mitigation
279
- err := h.checkHost(r)
280
- if err != nil {
281
- h.handleError(w, r, err)
282
- return
283
- }
295
+ // DNS rebinding mitigation
296
+ err := h.checkHost(r)
297
+ if err != nil {
298
+ h.handleError(w, r, err)
299
+ return
300
+ }
301
302
+ if h.enforceOrigin {
303
// cross-site mitigation
304
origin, err := h.checkOrigin(r)
305
if err != nil {
listeners.go
+24
-7
@@ -289,14 +289,31 @@ func (na NetworkAddress) PortRangeSize() uint {
289
return (na.EndPort - na.StartPort) + 1
290
}
291
292
+func (na NetworkAddress) isLoopback() bool {
293
+ if na.IsUnixNetwork() {
294
+ return true
295
+ }
296
+ if na.Host == "localhost" {
297
+ return true
298
+ }
299
+ if ip := net.ParseIP(na.Host); ip != nil {
300
+ return ip.IsLoopback()
301
+ }
302
+ return false
303
+}
304
+
305
+func (na NetworkAddress) port() string {
306
+ if na.StartPort == na.EndPort {
307
+ return strconv.FormatUint(uint64(na.StartPort), 10)
308
+ }
309
+ return fmt.Sprintf("%d-%d", na.StartPort, na.EndPort)
310
+}
311
+
312
// String reconstructs the address string to the form expected
293
-// by ParseNetworkAddress().
313
+// by ParseNetworkAddress(). If the address is a unix socket,
314
+// any non-zero port will be dropped.
315
func (na NetworkAddress) String() string {
295
- port := strconv.FormatUint(uint64(na.StartPort), 10)
296
- if na.StartPort != na.EndPort {
297
- port += "-" + strconv.FormatUint(uint64(na.EndPort), 10)
298
- }
299
- return JoinNetworkAddress(na.Network, na.Host, port)
316
+ return JoinNetworkAddress(na.Network, na.Host, na.port())
317
}
318
319
func isUnixNetwork(netw string) bool {
@@ -378,7 +395,7 @@ func JoinNetworkAddress(network, host, port string) string {
395
if network != "" {
396
a = network + "/"
397
}
381
- if host != "" && port == "" {
398
+ if (host != "" && port == "") || isUnixNetwork(network) {
399
a += host
400
} else if port != "" {
401
a += net.JoinHostPort(host, port)
listeners_test.go
+8
@@ -138,6 +138,14 @@ func TestJoinNetworkAddress(t *testing.T) {
138
network: "unix", host: "/foo/bar", port: "",
139
expect: "unix//foo/bar",
140
},
141
+ {
142
+ network: "unix", host: "/foo/bar", port: "0",
143
+ expect: "unix//foo/bar",
144
+ },
145
+ {
146
+ network: "unix", host: "/foo/bar", port: "1234",
147
+ expect: "unix//foo/bar",
148
+ },
149
{
150
network: "", host: "::1", port: "1234",
151
expect: "[::1]:1234",