{# Managed by Ansible (roles/k3s_traefik) — do not edit on the node. Plain manifests, not a HelmChart — same pattern as roles/k3s_metallb/templates/metallb-config.yaml.j2 and roles/k3s_cert_manager/templates/cluster-issuer.yaml.j2: CRs belonging to charts installed elsewhere, dropped in the auto-deploying directory and retried by k3s's deploy controller until the CRDs they need exist. Rendered only when k3s_traefik_dashboard_host is set; tasks/main.yml deletes this file when it isn't, and k3s's deploy controller garbage-collects what the file created. traefik.io/v1alpha1, not traefik.containo.us/v1alpha1 — the group changed with Traefik v3, which is what current k3s bundles. -#} {# Explicit Certificate rather than the cert-manager.io/cluster-issuer annotation every app's Ingress uses: cert-manager watches Ingress resources, and this route can't be one (see below). DNS-01, so this issues without the hostname resolving anywhere yet. -#} apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: traefik-dashboard namespace: {{ k3s_traefik_namespace }} spec: secretName: {{ k3s_traefik_dashboard_tls_secret }} issuerRef: name: {{ k3s_traefik_dashboard_cert_issuer }} kind: ClusterIssuer dnsNames: - {{ k3s_traefik_dashboard_host }} --- {# Authentik, in forward-auth (domain level) mode — see defaults/main.yml for why domain level and not single-application. Fails closed: if Authentik is down this returns an error rather than passing the request through, which is the correct direction and the reason the port-forward path stays. -#} apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: name: {{ k3s_traefik_dashboard_auth_middleware }} namespace: {{ k3s_traefik_namespace }} spec: forwardAuth: address: {{ k3s_traefik_dashboard_auth_address }} # Traefik strips X-Forwarded-* from client requests by default; Authentik # needs them to know which host and scheme the user actually asked for, # and builds its redirect back out of them. Safe here because the only # thing that can set them is Traefik itself — nothing reaches this # middleware without passing through the entrypoint first. trustForwardHeader: true authResponseHeaders: {% for header in k3s_traefik_dashboard_auth_response_headers %} - {{ header }} {% endfor %} --- {# An IngressRoute rather than an Ingress, and not by preference: the dashboard is served by api@internal, a Traefik-internal service with no Kubernetes Service behind it, so there is nothing for an Ingress backend to name. That single fact is why the Certificate above is explicit and why the middleware is attached here rather than by annotation. -#} apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: traefik-dashboard namespace: {{ k3s_traefik_namespace }} spec: entryPoints: - websecure routes: # Both prefixes, because the dashboard is a static bundle under # /dashboard/ that reads its data from /api — serving the first without # the second gets you a page that loads and then stays empty. - kind: Rule match: Host(`{{ k3s_traefik_dashboard_host }}`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`)) middlewares: - name: {{ k3s_traefik_dashboard_auth_middleware }} namespace: {{ k3s_traefik_namespace }} services: - kind: TraefikService name: api@internal tls: secretName: {{ k3s_traefik_dashboard_tls_secret }}