Skip to content

feat(traefik): legacy group, CRD secret redaction, detail renderers, audit checks - #1012

Merged
nadaverell merged 8 commits into
mainfrom
feat/traefik-depth
Jun 25, 2026
Merged

nadaverell merged 8 commits into
mainfrom
feat/traefik-depth

Conversation

@nadaverell

@nadaverell nadaverell commented Jun 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A deep-dive into Traefik support surfaced four real gaps behind the (already broad) integration. This closes them: legacy-API-group support, a CRD-spec secret-redaction hole in the AI/MCP context, missing resource-detail renderers, and the absence of any Traefik best-practice checks. Each workstream is independent; they're bundled here as one PR.

What changed

1. Legacy traefik.containo.us group (internal/k8s/dynamic_cache.go)

Warm-list the 10 legacy-group CRDs next to the traefik.io block. Discovery already resolved single-group clusters either way; this restores pre-warm parity so pre-v2.11 clusters don't pay first-list latency. True dual-served (traefik.io + containo.us simultaneously) topology is rare/transient and tracked separately — not solved here.

2. Key-aware secret redaction for CRD context (pkg/ai/context/)

RedactSecrets previously ran only over log lines and env values; an inline credential in a CRD spec reached the LLM/get_resource verbatim. New RedactInlineSecrets walks spec/status and redacts a string value when either its key is a value-bearing credential name (exact match: password, clientSecret, token, …) or the value matches a high-confidence pattern (prefixed tokens, AWS keys, bearer, htpasswd/crypt hashes).

  • Reference keys are preserved — secretName, secretRef, rootCAsSecrets, basicAuth.secret hold the name of a Secret, which an operator needs to diagnose a bad reference.
  • The broad base64 rule is not applied to arbitrary CRD fields (it would eat SHA-like IDs and config hashes); it stays scoped to free-text logs/env.
  • General CRD hardening — Traefik Middleware is one example. (Note: Traefik v3's CRD schema rejects inline basicAuth.users, so the concrete inline-credential case is mostly v2/plugin/annotation territory; the value-pattern still catches hashes wherever they appear.)

3. Traefik detail renderers (packages/k8s-ui/.../renderers/)

Middleware (+MiddlewareTCP), TraefikService, TLSOption, and ServersTransport (+TCP) previously fell through to the generic renderer. New renderers, registered in the dispatch (every added KNOWN_KINDS plural has a matching render line — adding one without the other blanks the drawer):

  • Middleware — type-aware (chain links members; basicAuth/digestAuth/forwardAuth show secret refs only, never values; errors; generic key/value fallback for plugin/unknown types, with nested credential keys redacted).
  • TraefikService — weighted backends with weight bars, mirroring (primary + mirrors), HRW.
  • TLSOption — versions, cipher suites, client auth.
  • ServersTransport — SNI, an insecureSkipVerify warning banner, CA/cert secret refs, timeouts.

4. Reference-integrity audit checks (pkg/audit/, internal/audit/runner.go)

Two new Reliability checks — a router referencing a non-existent Service/TraefikService (traefikRouteMissingService) or Middleware (traefikRouteMissingMiddleware). Traefik ships no admission webhook or linter, so these dangle silently until someone reads controller logs (Kubevious built a dedicated validator for exactly this). Mirrors the existing ingressNoMatchingService pattern.

Sound under RBAC, by design. The central guarantee is inventory authority: the check asserts a "missing X" finding only when that target kind is served by a synced cluster-wide informer (IsClusterWideSynced for the dynamic Middleware/TraefikService kinds; IsKindClusterWide("services") for core Services). When the cache only covers a namespace subset (RBAC-scoped fallback), it skips rather than fabricate a false "missing". Because cluster-wide coverage is authoritative for every namespace, that same gate lets it correctly resolve cross-namespace references too. Matching is group-aware (a traefik.io router isn't satisfied by a traefik.containo.us target) and per-kind independent (IngressRouteTCP→MiddlewareTCP resolves separately from Middleware, so a stuck/unwatched kind neither suppresses the others nor fabricates findings). Findings say "not found in the cluster" — Radar can't observe Traefik's allowCrossNamespace / provider namespace filters, so it asserts inventory existence, not Traefik resolvability. Scope is deliberately partial (route → Service/Middleware); nested TraefikService services, chain members, errors.service, and TLS-secret refs are not yet covered.

Review

Heavily cross-reviewed. A /plan-loop pass with Codex surfaced the load-bearing constraint — under RBAC-scoped / namespace-fallback caching the audit cannot distinguish "object absent" from "not cached" — which reshaped the design around the cluster-wide inventory-authority gate above (rather than a naive "resolve against the cache" that would false-positive). Implementation diff + Bugbot passes then drove the redaction precision (secret references preserved; users/htpasswd entries redacted; nested credential keys hidden at every depth) and the renderer fixes (TCP-aware chain links, surgical deep-redact over blanket collapse). All confirmed findings fixed; the audit's four ref-target kinds are now uniformly coverage-gated.

Testing

Unit: make test (full Go suite) ✓ — incl. pkg/audit/traefik_test.go (cross-namespace ref resolves; non-authoritative kind skips; per-kind independence; group-aware matching; findings carry empty Group for drill-down) and redact_test.go (key-aware redaction, secret-reference preservation, plaintext-users redaction, high-confidence value catch). internal/k8s green, so the warm-list addition didn't break the RBAC-coverage / capability-alignment guards. make tsc ✓.

Live (kind cluster, Traefik v3 CRDs + fixtures):

  • Renderers — Middleware chain, basicAuth (secret ref only), MiddlewareTCP, TraefikService weighted (weight bars) and mirroring (primary + mirror %), ServersTransport (insecure-skip-verify warning), TLSOption (ciphers), and the generic/unknown plugin path (values shown, credential keys hidden).
  • Audit — dangling Service, TraefikService, Middleware (incl. a cross-namespace ref) and MiddlewareTCP refs each flagged; a valid cross-namespace ref (target exists in another namespace) correctly not flagged; findings visible in the per-resource drill-down.
  • Redaction — via a real MCP get_resource: inline plugin apiKey/token → [REDACTED], a Bearer header value → Bearer [REDACTED], while basicAuth.secret (a reference name) and a benign endpoint are preserved.
  • RBAC soundness — with a namespace-scoped identity that can't list Traefik cluster-wide, the same genuinely-dangling refs produce zero findings (the check skips rather than false-positive) while the rest of the audit still runs; cluster-wide access flags all of them. This is the central correctness guarantee.

Out of scope (considered, rejected)

Status badges (Traefik doesn't populate CRD .status), standard-Ingress-via-Traefik association, entrypoint nodes, true dual-group topology.


Note

Medium Risk
Touches audit false-positive logic and LLM-facing CRD redaction; changes are conservative (authority gates, reference preservation) but affect reliability findings and AI context for arbitrary CRDs.

Overview
This PR extends Traefik support across cache warmup, AI/MCP context safety, the resource drawer, and reliability audits.

Legacy API group — The dynamic cache warm-list now includes traefik.containo.us alongside traefik.io so older clusters avoid first-list latency on the same CRD set.

CRD secret redaction — RedactInlineSecrets walks unstructured spec/status before compact/detail minification, redacting credential keys and high-confidence patterns while keeping secret reference names. The Middleware drawer mirrors the same key set with nested (hidden) redaction.

UI renderers — Middleware/MiddlewareTCP, TraefikService, TLSOption, and ServersTransport (+TCP) get dedicated drawers (chains, auth refs only, weighted/mirror backends, TLS settings, insecure-skip-verify warning) and are wired through KNOWN_KINDS and dispatch.

Audit — The runner loads namespace-scoped routes and cluster-wide middleware/TraefikService targets (both groups), gated by IsClusterWideSynced / IsKindClusterWide so partial cache coverage never asserts “missing.” New checks traefikRouteMissingService and traefikRouteMissingMiddleware flag broken refs within the same API group; nested TraefikService/chain refs are out of scope.

Reviewed by Cursor Bugbot for commit 2354b4b. Bugbot is set up for automated code reviews on this repo. Configure here.

…erers, audit checks

Four independent improvements to Traefik support:

1. Warm-list the legacy traefik.containo.us CRD group alongside traefik.io so
   pre-v2.11 clusters get pre-warmed informers (resolution already worked for
   single-group clusters; this restores first-list latency parity).

2. Key-aware secret redaction for unstructured CRD specs sent to the LLM/MCP
   context. Previously only core Secrets and env values were redacted; an inline
   credential in a CRD spec passed through verbatim. Now spec/status string
   values are redacted when their key is a value-bearing credential name, or
   when the value matches a high-confidence pattern (token shapes, htpasswd
   hashes) — reference keys (secretName, secretRef, rootCAsSecrets) are
   deliberately preserved, and the broad base64 rule is not applied to arbitrary
   fields to avoid eating hashes/IDs.

3. Detail renderers for Middleware (+MiddlewareTCP), TraefikService, TLSOption,
   and ServersTransport (+TCP) — previously these fell through to the generic
   renderer. Middleware renders type-specific config (chain/auth/forwardAuth/
   errors + generic fallback) showing secret references only, never values;
   TraefikService shows weighted/mirroring backends with weight bars.

4. Two reference-integrity audit checks: a Traefik router referencing a
   non-existent Service/TraefikService or Middleware. Traefik ships no admission
   webhook, so these dangle silently. Matching is conservative (only genuinely-
   absent refs, group-aware, synced informers only) to avoid false positives.
@nadaverell
nadaverell requested a review from hisco as a code owner June 24, 2026 23:57
Comment thread internal/audit/runner.go Outdated
Avoids a partial-sync window where one target kind (e.g. middlewaretcps) is
ready but another (middlewares) isn't, which could fabricate a transient
'missing reference' finding. Skip the round entirely until every watched
Traefik GVR has synced; the next audit runs once they catch up.
Comment thread pkg/ai/context/redact.go
…n link kind

- redactConfig walks the generic middleware config at every depth, so a secret
  buried under e.g. plugin.config.clientSecret can't survive JSON.stringify
  (shallow redaction missed depth >= 2).
- MiddlewareTCP chain member links now resolve to middlewaretcps, not middlewares.
Comment thread packages/k8s-ui/src/components/resources/renderers/TraefikMiddlewareRenderer.tsx Outdated
getMiddlewareType returning 'unknown' left the Configuration section empty
because config was hardcoded to {}. Fall back to the spec's sole top-level key
so the raw config the banner promises actually renders (deep-redacted).
Comment thread internal/audit/runner.go Outdated
Comment thread packages/k8s-ui/src/components/resources/renderers/TraefikMiddlewareRenderer.tsx Outdated
…action set

- The Traefik audit sync-gate required EVERY watched Traefik GVR synced; an
  unsynced TLSOption/ServersTransport informer (irrelevant to the checks) would
  drop the whole scan. Gate only on the kinds the checks actually read.
- The renderer's generic-config redaction set was narrower than the backend's
  (missed apiKey/privateKey/passphrase/...). Align it with the backend
  value-key set so plugin/unknown middleware configs can't leak those inline.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4b62077. Configure here.

Close the whole 'nested/array inline credential' class structurally: the
generic (unknown/plugin) config view now renders only top-level scalars with
credential keys redacted, and summarizes nested objects/arrays without their
values. The frontend has no value-pattern redaction to safely classify an
arbitrary nested string, and the full structure remains available in the YAML
tab. Flat configs (rateLimit, redirectScheme) still render in full.
…erer config

Reworks the Traefik dangling-ref audit around inventory authority, and fixes
two redaction issues, per a deep review pass:

- Drop Group from Traefik findings so the audit's builtin backfill leaves it ""
  — which is what the per-resource audit drill-down looks up. Findings were
  invisible on a route's own detail before (only the cluster-wide list showed
  them).
- Resolve route refs against CLUSTER-WIDE target inventory (scope only the route
  subjects), so a legitimate cross-namespace Middleware/Service reference
  resolves instead of being fabricated as "missing" in a scoped audit.
- Assert absence only for target kinds backed by a SYNCED, CLUSTER-WIDE informer
  (new DynamicResourceCache.IsClusterWideSynced). This single per-GVR gate fixes
  three issues together: partial-coverage false positives, Middleware-vs-
  MiddlewareTCP granularity, and one-stuck-informer suppressing the whole scan —
  each GVR is now independent. Under namespace-scoped caching the check skips
  (no finding) rather than risking a false positive.
- Reword findings to "not found in the cluster" — Radar can't observe Traefik's
  allowCrossNamespace / provider namespace filters, so it only asserts inventory
  existence, not Traefik resolvability.
- Redaction: add `users` to the backend sensitive-key set (aligns with the
  frontend mirror; covers htpasswd basicAuth entries).
- Renderer: restore deep recursive credential-key redaction for generic/plugin
  middleware config (show non-secret nested values, hide credential keys at every
  depth) instead of collapsing all nested values — the drawer is not a secrecy
  boundary (the YAML tab shows the full spec).
… coverage

Completes the coverage-gating: the missing-Service check now only asserts
absence when the typed Services informer is cluster-wide (new
ResourceCache.IsKindClusterWide), matching the gate already applied to the
dynamic Middleware/TraefikService kinds. Under a namespace-scoped Services
fallback the check skips rather than risk a false 'missing Service' — so all
four Traefik ref-target kinds are now uniformly sound, with no residual gap.
@nadaverell
nadaverell merged commit 6b8ca2e into main Jun 25, 2026
9 checks passed
@nadaverell
nadaverell deleted the feat/traefik-depth branch June 25, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant