A single Traefik fronting many tenants is efficient but only safe if route ownership is bounded — one tenant cannot claim another’s hostname, reference another’s secrets, or smuggle their traffic through a shared middleware. This article covers the isolation patterns we use when multiple teams share a Traefik install: K8s RBAC scoped per namespace, a Host(...) rule lock, a platform-managed middleware library, and the audits that catch drift before it becomes an incident.
How to verify
# Confirm the controller only watches the namespaces it should
kubectl describe deploy -n traefik traefik | grep -A2 'kubernetes.namespaces\|kubernetesIngress.namespaces'
# Confirm allowCrossNamespace is off
kubectl describe deploy -n traefik traefik | grep allowCrossNamespace
# List all IngressRoutes by namespace and hostname — the audit surface
kubectl get ingressroute -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.spec.routes[*].match}{"\n"}{end}'
# Look for tenants stepping outside their assigned host space
kubectl get ingressroute -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.spec.routes[*].match}{"\n"}{end}' \
| awk -F'\t' '$1=="tenant-a" && $2 !~ /tenant-a\./ {print}'
What’s happening
Multi-tenancy in Traefik comes from layering several controls. The first is provider scope: the kubernetesCRD provider can be told to watch a label selector or a namespace list, so a single Traefik fleet can serve only the namespaces explicitly granted to it. The second is cross-namespace deny: allowCrossNamespace: false keeps an IngressRoute in namespace A from referencing a Middleware, Service, or TLS secret in namespace B unless both sides opt in. The third is RBAC: the controller’s ServiceAccount is read-only on the resources it watches, and tenant ServiceAccounts have CRUD only on their own namespace’s IngressRoute / Middleware objects.
Those three controls cover everything except the hostname namespace. Traefik does not enforce “tenant A owns *.tenant-a.example.com” — any team can author an IngressRoute matching Host(tenant-b.example.com) and it works. The fix is a validating admission webhook (Kyverno, OPA Gatekeeper, kubewarden) that rejects IngressRoutes whose match rule mentions a host outside the tenant’s assigned space.
The pattern that keeps everyone honest: the platform namespace owns a curated middleware library — secure-headers, rate-limit-default, compress, cors-default — that tenants reference rather than redefine. That gives one place to update security headers when a CVE drops, one place to tighten rate limits when an abuser shows up, and one place to audit what every tenant is opting into.
The procedure
-
Scope the controller to specific namespaces. On the kubernetesCRD provider:
providers: kubernetesCRD: enabled: true namespaces: - tenant-a - tenant-b - platform allowCrossNamespace: false allowExternalNameServices: false -
Set the controller ServiceAccount’s ClusterRole to read-only on Traefik CRDs. Do not give it the cluster-wide secret read that the chart’s default RBAC grants — narrow to the namespaces it serves.
apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: traefik-secrets-reader namespace: tenant-a rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: traefik-secrets-reader namespace: tenant-a roleRef: kind: Role name: traefik-secrets-reader apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount name: traefik namespace: traefikRepeat per tenant namespace. The chart needs a values override to drop the default cluster-wide secret get.
-
Publish the platform middleware library. Tenants reference by
namespace:name.apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: name: secure-headers namespace: platform spec: headers: stsSeconds: 31536000 contentTypeNosniff: true referrerPolicy: strict-origin-when-cross-origin --- apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: name: rate-limit-default namespace: platform spec: rateLimit: average: 100 burst: 50Because
allowCrossNamespace: falseis on, tenants cannot use these by default — flip the flag totrueand rely on the Kyverno guard below to scope which platform middlewares are referenceable, or add specific allow-list rules. Most teams find it cleaner to ship a tenant-namespace-scoped clone of each platform middleware via GitOps and treat the platform-namespace versions as the canonical source. -
Add a Kyverno (or OPA Gatekeeper) policy that enforces hostname ownership.
apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: tenant-hostname-scope spec: validationFailureAction: enforce rules: - name: tenant-a-must-own-tenant-a-hosts match: any: - resources: kinds: [IngressRoute] namespaces: [tenant-a] validate: message: "tenant-a IngressRoute must match a host within tenant-a.example.com" pattern: spec: routes: - match: "*tenant-a.example.com*" -
Audit weekly. The audit surface is small and worth wiring into the same pipeline that emits other compliance signals.
kubectl get ingressroute -A -o json \ | jq -r '.items[] | [.metadata.namespace, .spec.routes[].match] | @tsv' \ | sort
Operational notes
- The chart’s default ClusterRole grants cluster-wide secret read; narrow this before going to a multi-tenant install or any tenant can craft a route that lets Traefik leak another tenant’s TLS secrets.
- Tenants who insist on bringing their own custom middleware can have it — keep their middlewares in their namespace, and never accept a PR that adds a middleware to
platformwithout platform-team review. - The Host rule lock is the most common gap. Without admission policy, the first team to author
Host(other-tenant.example.com)gets the route and the legitimate owner’s traffic. - A shared Traefik fleet is a shared failure domain — a bug in a tenant-supplied custom plugin can bring down every tenant. Either disallow custom plugins in shared fleets or split per-tenant fleets at high blast-radius thresholds.
- For shared TLS, store wildcard certs in the platform namespace and reference them via
secretNamefrom tenant IngressRoutes — but the wildcard now becomes a high-impact secret; rotate accordingly.
Multi-tenant Traefik is a balance: efficient enough to share, isolated enough that one tenant cannot hurt another. The runbook that captures who owns which namespace, which platform middleware they consume, and the admission policies that keep them honest is part of our managed Kubernetes operations. For the underlying CRD shape, see traefik-k8s-ingressroute-crd.