Exposing the Dashboard

Instead of using linkerd viz dashboard every time you’d like to see what’s going on, you can expose the dashboard via an ingress. This will also expose Grafana.

Nginx

Nginx with basic auth

A sample ingress definition is:

  1. apiVersion: v1
  2. kind: Secret
  3. type: Opaque
  4. metadata:
  5. name: web-ingress-auth
  6. namespace: linkerd-viz
  7. data:
  8. auth: YWRtaW46JGFwcjEkbjdDdTZnSGwkRTQ3b2dmN0NPOE5SWWpFakJPa1dNLgoK
  9. ---
  10. # apiVersion: networking.k8s.io/v1beta1 # for k8s < v1.19
  11. apiVersion: networking.k8s.io/v1
  12. kind: Ingress
  13. metadata:
  14. name: web-ingress
  15. namespace: linkerd-viz
  16. annotations:
  17. nginx.ingress.kubernetes.io/upstream-vhost: $service_name.$namespace.svc.cluster.local:8084
  18. nginx.ingress.kubernetes.io/configuration-snippet: |
  19. proxy_set_header Origin "";
  20. proxy_hide_header l5d-remote-ip;
  21. proxy_hide_header l5d-server-id;
  22. nginx.ingress.kubernetes.io/auth-type: basic
  23. nginx.ingress.kubernetes.io/auth-secret: web-ingress-auth
  24. nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required'
  25. spec:
  26. ingressClassName: nginx
  27. rules:
  28. - host: dashboard.example.com
  29. http:
  30. paths:
  31. - path: /
  32. pathType: Prefix
  33. backend:
  34. service:
  35. name: web
  36. port:
  37. number: 8084

This exposes the dashboard at dashboard.example.com and protects it with basic auth using admin/admin. Take a look at the ingress-nginx documentation for details on how to change the username and password.

Nginx with oauth2-proxy

A more secure alternative to basic auth is using an authentication proxy, such as oauth2-proxy.

For reference on how to deploy and configure oauth2-proxy in kubernetes, see this blog post by Don Bowman.

tl;dr: If you deploy oauth2-proxy via the helm chart, the following values are required:

  1. config:
  2. existingSecret: oauth2-proxy
  3. configFile: |-
  4. email_domains = [ "example.com" ]
  5. upstreams = [ "file:///dev/null" ]
  6. ingress:
  7. enabled: true
  8. annotations:
  9. kubernetes.io/ingress.class: nginx
  10. path: /oauth2
  11. ingress:
  12. hosts:
  13. - linkerd.example.com

Where the oauth2-proxy secret would contain the required oauth2 config such as, client-id client-secret and cookie-secret.

Once setup, a sample ingress would be:

  1. # apiVersion: networking.k8s.io/v1beta1 # for k8s < v1.19
  2. apiVersion: networking.k8s.io/v1
  3. kind: Ingress
  4. metadata:
  5. name: web
  6. namespace: linkerd-viz
  7. annotations:
  8. nginx.ingress.kubernetes.io/upstream-vhost: $service_name.$namespace.svc.cluster.local:8084
  9. nginx.ingress.kubernetes.io/configuration-snippet: |
  10. proxy_set_header Origin "";
  11. proxy_hide_header l5d-remote-ip;
  12. proxy_hide_header l5d-server-id;
  13. nginx.ingress.kubernetes.io/auth-signin: https://$host/oauth2/start?rd=$escaped_request_uri
  14. nginx.ingress.kubernetes.io/auth-url: https://$host/oauth2/auth
  15. spec:
  16. ingressClassName: nginx
  17. rules:
  18. - host: linkerd.example.com
  19. http:
  20. paths:
  21. - path: /
  22. pathType: Prefix
  23. backend:
  24. service:
  25. name: web
  26. port:
  27. number: 8084

Traefik

A sample ingress definition is:

  1. apiVersion: v1
  2. kind: Secret
  3. type: Opaque
  4. metadata:
  5. name: web-ingress-auth
  6. namespace: linkerd-viz
  7. data:
  8. auth: YWRtaW46JGFwcjEkbjdDdTZnSGwkRTQ3b2dmN0NPOE5SWWpFakJPa1dNLgoK
  9. ---
  10. # apiVersion: networking.k8s.io/v1beta1 # for k8s < v1.19
  11. apiVersion: networking.k8s.io/v1
  12. kind: Ingress
  13. metadata:
  14. name: web-ingress
  15. namespace: linkerd-viz
  16. annotations:
  17. ingress.kubernetes.io/custom-request-headers: l5d-dst-override:web.linkerd-viz.svc.cluster.local:8084
  18. traefik.ingress.kubernetes.io/auth-type: basic
  19. traefik.ingress.kubernetes.io/auth-secret: web-ingress-auth
  20. spec:
  21. ingressClassName: traefik
  22. rules:
  23. - host: dashboard.example.com
  24. http:
  25. paths:
  26. - path: /
  27. pathType: Prefix
  28. backend:
  29. service:
  30. name: web
  31. port:
  32. number: 8084

This exposes the dashboard at dashboard.example.com and protects it with basic auth using admin/admin. Take a look at the Traefik documentation for details on how to change the username and password.

Ambassador

Ambassador works by defining a mapping as an annotation on a service.

The below annotation exposes the dashboard at dashboard.example.com.

  1. annotations:
  2. getambassador.io/config: |-
  3. ---
  4. apiVersion: getambassador.io/v2
  5. kind: Mapping
  6. name: web-mapping
  7. host: dashboard.example.com
  8. prefix: /
  9. host_rewrite: web.linkerd-viz.svc.cluster.local:8084
  10. service: web.linkerd-viz.svc.cluster.local:8084

DNS Rebinding Protection

To prevent DNS-rebinding attacks, the dashboard rejects any request whose Host header is not localhost, 127.0.0.1 or the service name web.linkerd-viz.svc.

Note that this protection also covers the Grafana dashboard.

The ingress-nginx config above uses the nginx.ingress.kubernetes.io/upstream-vhost annotation to properly set the upstream Host header. Traefik on the other hand doesn’t offer that option, so you’ll have to manually set the required Host as explained below.

Tweaking Host Requirement

If your HTTP client (Ingress or otherwise) doesn’t allow to rewrite the Host header, you can change the validation regexp that the dashboard server uses, which is fed into the web deployment via the enforced-host container argument.

If you’re managing Linkerd with Helm, then you can set the host using the enforcedHostRegexp value.

Another way of doing that is through Kustomize, as explained in Customizing Installation, using an overlay like this one:

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: web
  5. spec:
  6. template:
  7. spec:
  8. containers:
  9. - name: web
  10. args:
  11. - -linkerd-controller-api-addr=linkerd-controller-api.linkerd.svc.cluster.local:8085
  12. - -linkerd-metrics-api-addr=metrics-api.linkerd-viz.svc.cluster.local:8085
  13. - -cluster-domain=cluster.local
  14. - -grafana-addr=grafana.linkerd-viz.svc.cluster.local:3000
  15. - -controller-namespace=linkerd
  16. - -viz-namespace=linkerd-viz
  17. - -log-level=info
  18. - -enforced-host=^dashboard\.example\.com$

If you want to completely disable the Host header check, simply use a catch-all regexp .* for -enforced-host.