summaryrefslogtreecommitdiff
path: root/admin.go
diff options
context:
space:
mode:
authorMatthew Holt <mholt@users.noreply.github.com>2020-04-10 17:31:38 -0600
committerMatthew Holt <mholt@users.noreply.github.com>2020-04-10 17:31:38 -0600
commita3bdc22234b75e9420f8810918072fa34732ffb7 (patch)
tree1b9fd4cc16f1770f5535a929266f2528022d7bc5 /admin.go
parentd3383ced2abb8dfb36e56fa9bbe3136a2a379281 (diff)
admin: Always enforce Host header checks
With a simple heuristic for loopback addresses, we can enable this by default without adding unnecessary inconvenience.
Diffstat (limited to 'admin.go')
-rw-r--r--admin.go56
1 files changed, 37 insertions, 19 deletions
diff --git a/admin.go b/admin.go
index 0b509f2..7217eb7 100644
--- a/admin.go
+++ b/admin.go
@@ -21,6 +21,7 @@ import (
"expvar"
"fmt"
"io"
+ "net"
"net/http"
"net/http/pprof"
"net/url"
@@ -78,27 +79,27 @@ type ConfigSettings struct {
// listenAddr extracts a singular listen address from ac.Listen,
// returning the network and the address of the listener.
-func (admin AdminConfig) listenAddr() (string, string, error) {
+func (admin AdminConfig) listenAddr() (NetworkAddress, error) {
input := admin.Listen
if input == "" {
input = DefaultAdminListen
}
listenAddr, err := ParseNetworkAddress(input)
if err != nil {
- return "", "", fmt.Errorf("parsing admin listener address: %v", err)
+ return NetworkAddress{}, fmt.Errorf("parsing admin listener address: %v", err)
}
if listenAddr.PortRangeSize() != 1 {
- return "", "", fmt.Errorf("admin endpoint must have exactly one address; cannot listen on %v", listenAddr)
+ return NetworkAddress{}, fmt.Errorf("admin endpoint must have exactly one address; cannot listen on %v", listenAddr)
}
- return listenAddr.Network, listenAddr.JoinHostPort(0), nil
+ return listenAddr, nil
}
// newAdminHandler reads admin's config and returns an http.Handler suitable
// for use in an admin endpoint server, which will be listening on listenAddr.
-func (admin AdminConfig) newAdminHandler(listenAddr string) adminHandler {
+func (admin AdminConfig) newAdminHandler(addr NetworkAddress) adminHandler {
muxWrap := adminHandler{
enforceOrigin: admin.EnforceOrigin,
- allowedOrigins: admin.allowedOrigins(listenAddr),
+ allowedOrigins: admin.allowedOrigins(addr),
mux: http.NewServeMux(),
}
@@ -140,14 +141,30 @@ func (admin AdminConfig) newAdminHandler(listenAddr string) adminHandler {
// If admin.Origins is nil (null), the provided listen address
// will be used as the default origin. If admin.Origins is
// empty, no origins will be allowed, effectively bricking the
-// endpoint, but whatever.
-func (admin AdminConfig) allowedOrigins(listen string) []string {
+// endpoint for non-unix-socket endpoints, but whatever.
+func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []string {
uniqueOrigins := make(map[string]struct{})
for _, o := range admin.Origins {
uniqueOrigins[o] = struct{}{}
}
if admin.Origins == nil {
- uniqueOrigins[listen] = struct{}{}
+ if addr.isLoopback() {
+ if addr.IsUnixNetwork() {
+ // RFC 2616, Section 14.26:
+ // "A client MUST include a Host header field in all HTTP/1.1 request
+ // messages. If the requested URI does not include an Internet host
+ // name for the service being requested, then the Host header field MUST
+ // be given with an empty value."
+ uniqueOrigins[""] = struct{}{}
+ } else {
+ uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
+ uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
+ uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
+ }
+ }
+ if !addr.IsUnixNetwork() {
+ uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
+ }
}
allowed := make([]string, 0, len(uniqueOrigins))
for origin := range uniqueOrigins {
@@ -195,14 +212,14 @@ func replaceAdmin(cfg *Config) error {
}
// extract a singular listener address
- netw, addr, err := adminConfig.listenAddr()
+ addr, err := adminConfig.listenAddr()
if err != nil {
return err
}
handler := adminConfig.newAdminHandler(addr)
- ln, err := Listen(netw, addr)
+ ln, err := Listen(addr.Network, addr.JoinHostPort(0))
if err != nil {
return err
}
@@ -219,7 +236,7 @@ func replaceAdmin(cfg *Config) error {
Log().Named("admin").Info(
"admin endpoint started",
- zap.String("address", addr),
+ zap.String("address", addr.String()),
zap.Bool("enforce_origin", adminConfig.EnforceOrigin),
zap.Strings("origins", handler.allowedOrigins),
)
@@ -263,6 +280,7 @@ type adminHandler struct {
func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Log().Named("admin.api").Info("received request",
zap.String("method", r.Method),
+ zap.String("host", r.Host),
zap.String("uri", r.RequestURI),
zap.String("remote_addr", r.RemoteAddr),
zap.Reflect("headers", r.Header),
@@ -274,14 +292,14 @@ func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// be called more than once per request, for example if a request
// is rewritten (i.e. internal redirect).
func (h adminHandler) serveHTTP(w http.ResponseWriter, r *http.Request) {
- if h.enforceOrigin {
- // DNS rebinding mitigation
- err := h.checkHost(r)
- if err != nil {
- h.handleError(w, r, err)
- return
- }
+ // DNS rebinding mitigation
+ err := h.checkHost(r)
+ if err != nil {
+ h.handleError(w, r, err)
+ return
+ }
+ if h.enforceOrigin {
// cross-site mitigation
origin, err := h.checkOrigin(r)
if err != nil {