diff --git a/charts/linkerd2/templates/web.yaml b/charts/linkerd2/templates/web.yaml index 4798a19ce..3ecf31f57 100644 --- a/charts/linkerd2/templates/web.yaml +++ b/charts/linkerd2/templates/web.yaml @@ -63,6 +63,8 @@ spec: - -grafana-addr=linkerd-grafana.{{.Namespace}}.svc.{{.ClusterDomain}}:3000 - -controller-namespace={{.Namespace}} - -log-level={{.ControllerLogLevel}} + {{- $host := replace "." "\\." (printf "linkerd-web.%s.svc.%s" .Namespace .ClusterDomain) }} + - -enforced-host=^(localhost|127\.0\.0\.1|{{ $host }}|\[::1\])(:\d+)?$ {{- include "partials.linkerd.trace" . | nindent 8 -}} image: {{.WebImage}}:{{default .LinkerdVersion .ControllerImageVersion}} imagePullPolicy: {{.ImagePullPolicy}} diff --git a/cli/cmd/testdata/install_control-plane.golden b/cli/cmd/testdata/install_control-plane.golden index 473401da7..c7e3b4d93 100644 --- a/cli/cmd/testdata/install_control-plane.golden +++ b/cli/cmd/testdata/install_control-plane.golden @@ -825,6 +825,7 @@ spec: - -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000 - -controller-namespace=linkerd - -log-level=info + - -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|\[::1\])(:\d+)?$ image: gcr.io/linkerd-io/web:install-control-plane-version imagePullPolicy: IfNotPresent livenessProbe: diff --git a/cli/cmd/testdata/install_default.golden b/cli/cmd/testdata/install_default.golden index 2fee0e646..4cea4b855 100644 --- a/cli/cmd/testdata/install_default.golden +++ b/cli/cmd/testdata/install_default.golden @@ -1583,6 +1583,7 @@ spec: - -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000 - -controller-namespace=linkerd - -log-level=info + - -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|\[::1\])(:\d+)?$ image: gcr.io/linkerd-io/web:install-control-plane-version imagePullPolicy: IfNotPresent livenessProbe: diff --git a/cli/cmd/testdata/install_ha_output.golden b/cli/cmd/testdata/install_ha_output.golden index 1f273c7a0..966d69fb6 100644 --- a/cli/cmd/testdata/install_ha_output.golden +++ b/cli/cmd/testdata/install_ha_output.golden @@ -1696,6 +1696,7 @@ spec: - -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000 - -controller-namespace=linkerd - -log-level=info + - -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|\[::1\])(:\d+)?$ image: gcr.io/linkerd-io/web:install-control-plane-version imagePullPolicy: IfNotPresent livenessProbe: diff --git a/cli/cmd/testdata/install_ha_with_overrides_output.golden b/cli/cmd/testdata/install_ha_with_overrides_output.golden index b7eba99a9..dcf7e4449 100644 --- a/cli/cmd/testdata/install_ha_with_overrides_output.golden +++ b/cli/cmd/testdata/install_ha_with_overrides_output.golden @@ -1696,6 +1696,7 @@ spec: - -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000 - -controller-namespace=linkerd - -log-level=info + - -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|\[::1\])(:\d+)?$ image: gcr.io/linkerd-io/web:install-control-plane-version imagePullPolicy: IfNotPresent livenessProbe: diff --git a/cli/cmd/testdata/install_helm_output.golden b/cli/cmd/testdata/install_helm_output.golden index c5af06b13..d0c59b1e5 100644 --- a/cli/cmd/testdata/install_helm_output.golden +++ b/cli/cmd/testdata/install_helm_output.golden @@ -1647,6 +1647,7 @@ spec: - -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000 - -controller-namespace=linkerd - -log-level=info + - -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|\[::1\])(:\d+)?$ image: gcr.io/linkerd-io/web:linkerd-version imagePullPolicy: IfNotPresent livenessProbe: diff --git a/cli/cmd/testdata/install_helm_output_ha.golden b/cli/cmd/testdata/install_helm_output_ha.golden index 56b6c7fdf..6393c0bf5 100644 --- a/cli/cmd/testdata/install_helm_output_ha.golden +++ b/cli/cmd/testdata/install_helm_output_ha.golden @@ -1760,6 +1760,7 @@ spec: - -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000 - -controller-namespace=linkerd - -log-level=info + - -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|\[::1\])(:\d+)?$ image: gcr.io/linkerd-io/web:linkerd-version imagePullPolicy: IfNotPresent livenessProbe: diff --git a/cli/cmd/testdata/install_no_init_container.golden b/cli/cmd/testdata/install_no_init_container.golden index acd292a8e..56187559e 100644 --- a/cli/cmd/testdata/install_no_init_container.golden +++ b/cli/cmd/testdata/install_no_init_container.golden @@ -1481,6 +1481,7 @@ spec: - -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000 - -controller-namespace=linkerd - -log-level=info + - -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|\[::1\])(:\d+)?$ image: gcr.io/linkerd-io/web:install-control-plane-version imagePullPolicy: IfNotPresent livenessProbe: diff --git a/cli/cmd/testdata/install_output.golden b/cli/cmd/testdata/install_output.golden index 68d95465c..430df7986 100644 --- a/cli/cmd/testdata/install_output.golden +++ b/cli/cmd/testdata/install_output.golden @@ -1580,6 +1580,7 @@ spec: - -grafana-addr=linkerd-grafana.Namespace.svc.cluster.local:3000 - -controller-namespace=Namespace - -log-level=ControllerLogLevel + - -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.Namespace\.svc\.cluster\.local|\[::1\])(:\d+)?$ image: WebImage:ControllerImageVersion imagePullPolicy: ImagePullPolicy livenessProbe: diff --git a/cli/cmd/testdata/upgrade_default.golden b/cli/cmd/testdata/upgrade_default.golden index a9e7d329c..70d86df4a 100644 --- a/cli/cmd/testdata/upgrade_default.golden +++ b/cli/cmd/testdata/upgrade_default.golden @@ -1586,6 +1586,7 @@ spec: - -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000 - -controller-namespace=linkerd - -log-level=info + - -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|\[::1\])(:\d+)?$ image: gcr.io/linkerd-io/web:UPGRADE-CONTROL-PLANE-VERSION imagePullPolicy: IfNotPresent livenessProbe: diff --git a/cli/cmd/testdata/upgrade_external_issuer.golden b/cli/cmd/testdata/upgrade_external_issuer.golden index 3f403e653..73d5f2242 100644 --- a/cli/cmd/testdata/upgrade_external_issuer.golden +++ b/cli/cmd/testdata/upgrade_external_issuer.golden @@ -1572,6 +1572,7 @@ spec: - -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000 - -controller-namespace=linkerd - -log-level=info + - -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|\[::1\])(:\d+)?$ image: gcr.io/linkerd-io/web:UPGRADE-CONTROL-PLANE-VERSION imagePullPolicy: IfNotPresent livenessProbe: diff --git a/cli/cmd/testdata/upgrade_ha.golden b/cli/cmd/testdata/upgrade_ha.golden index 3d1ac8324..44b2fdd6b 100644 --- a/cli/cmd/testdata/upgrade_ha.golden +++ b/cli/cmd/testdata/upgrade_ha.golden @@ -1699,6 +1699,7 @@ spec: - -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000 - -controller-namespace=linkerd - -log-level=info + - -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|\[::1\])(:\d+)?$ image: gcr.io/linkerd-io/web:UPGRADE-CONTROL-PLANE-VERSION imagePullPolicy: IfNotPresent livenessProbe: diff --git a/web/main.go b/web/main.go index 599f8bd0d..63573faf9 100644 --- a/web/main.go +++ b/web/main.go @@ -6,6 +6,7 @@ import ( "net" "os" "os/signal" + "regexp" "syscall" "time" @@ -31,6 +32,7 @@ func main() { staticDir := cmd.String("static-dir", "app/dist", "directory to search for static files") reload := cmd.Bool("reload", true, "reloading set to true or false") controllerNamespace := cmd.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed") + enforcedHost := cmd.String("enforced-host", "", "regexp describing the allowed values for the Host header; protects from DNS-rebinding attacks") kubeConfigPath := cmd.String("kubeconfig", "", "path to kube config") traceCollector := flags.AddTraceFlags(cmd) @@ -73,7 +75,13 @@ func main() { } } - server := srv.NewServer(*addr, *grafanaAddr, *templateDir, *staticDir, uuid, *controllerNamespace, clusterDomain, *reload, client, k8sAPI) + reHost, err := regexp.Compile(*enforcedHost) + if err != nil { + log.Fatalf("invalid --enforced-host parameter: %s", err) + } + + server := srv.NewServer(*addr, *grafanaAddr, *templateDir, *staticDir, uuid, + *controllerNamespace, clusterDomain, *reload, reHost, client, k8sAPI) go func() { log.Infof("starting HTTP server on %+v", *addr) diff --git a/web/srv/server.go b/web/srv/server.go index 57196d85a..56c26f424 100644 --- a/web/srv/server.go +++ b/web/srv/server.go @@ -1,10 +1,13 @@ package srv import ( + "fmt" + "html" "html/template" "net/http" "path" "path/filepath" + "regexp" "time" "github.com/julienschmidt/httprouter" @@ -27,6 +30,7 @@ type ( reload bool templates map[string]*template.Template router *httprouter.Router + reHost *regexp.Regexp } templatePayload struct { @@ -44,6 +48,16 @@ type ( // this is called by the HTTP server to actually respond to a request func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if !s.reHost.MatchString(req.Host) { + error := fmt.Sprintf(`It appears that you are trying to reach this service with a host of '%s'. +This does not match /%s/ and has been denied for security reasons. +Please see https://linkerd.io/dns-rebinding for an explanation of what is happening and how to fix it.`, + html.EscapeString(req.Host), + html.EscapeString(s.reHost.String())) + + http.Error(w, error, http.StatusBadRequest) + return + } w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Frame-Options", "SAMEORIGIN") w.Header().Set("X-XSS-Protection", "1; mode=block") @@ -62,12 +76,14 @@ func NewServer( controllerNamespace string, clusterDomain string, reload bool, + reHost *regexp.Regexp, apiClient public.APIClient, k8sAPI *k8s.KubernetesAPI, ) *http.Server { server := &Server{ templateDir: templateDir, reload: reload, + reHost: reHost, } server.router = &httprouter.Router{