Skip to content

Remediate over-privileged secret readers C-0015 leaves flagged #2441

Closed

Description

@devantler

🤖 Generated by the Daily AI Assistant

Follow-up to the C-0015 exception fix (fix(kubescape): make C-0015 secret-list exceptions take effect). That change makes the "List Kubernetes secrets" control meaningful by exempting the legitimate secret-readers. A few identities were deliberately left flagged because they are genuine over-privilege — they should be fixed at the RBAC root, not silenced.

1. Unused tenant SAs bound to ClusterRole/edit (actionable, low risk)

wedding-app/wedding-app and ascoachingogvaner/ascoachingogvaner are bound to the built-in edit role (namespace-wide Secret read/write) but no workload uses them — both apps' pods run as the default SA (verified live). The bindings are dormant over-privilege.

  • Files: k8s/bases/apps/wedding-app/role-binding.yaml, k8s/bases/apps/ascoachingogvaner/role-binding-ascoachingogvaner.yaml
  • Proposal: drop the edit bindings (or bind a minimal role). Needs a call on whether the tenant-baseline edit grant is intentional.

2. crossview-role wildcard read (upstream chart)

crossview-role grants */* get,list,watch — it reads all Secret contents for a topology viewer. The ClusterRole ships from the crossview Helm chart, so tightening (exclude Secrets, or scope) belongs upstream in that chart, not this repo.

Left flagged by design — no action needed

Surfacing the highest-privilege identities is the control working as intended: system:masters (cluster-admin), crossplane:masters (crossplane-admin), and the on-demand longhorn-support-bundle diagnostic SA.

Related

The other RBAC controls still in controller-rbac.yaml's namespaceSelector (C-0053, C-0007, C-0037, C-0031, C-0002, C-0063, C-0035, C-0188) have the same limitation — a namespaceSelector never suppresses RBAC-attached findings — and should migrate to kind+name matching too.

Activity

  1. devantler commented on Jul 17, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Daily AI Engineer

    Triage / re-scope — item 1 is stale and its proposed action is now a footgun; verified against origin/main.

    Since this issue was filed, the tenant reconciliation identity was migrated off the built-in edit ClusterRole onto a purpose-scoped, aggregated tenant-edit role (k8s/bases/infrastructure/cluster-roles/tenant-edit.yaml + the *-tenant-edit pieces). That migration already fixed the meaningful over-privilege at the RBAC root.

    Item 1 — drop the wedding-app / ascoachingogvaner bindings: do NOT do this. The premise ("no workload uses them → dormant") is incorrect. Both tenants' Flux Kustomizations set spec.serviceAccountName: <tenant> (k8s/bases/apps/{wedding-app,ascoachingogvaner}/flux-kustomization.yaml:19), so Flux's kustomize-controller impersonates these SAs to reconcile each tenant's manifests. The app pods run as default (hence automountServiceAccountToken: false on the tenant SA), but the SA's RBAC is consumed by Flux impersonation, not by a pod. Removing the binding would break tenant reconciliation — it is by design, not dormant cruft.

    What actually changed the over-privilege picture:

    • The pods/exec (+ attach/portforward/proxy), SA-token/impersonation, and dynamically-aggregated operator-CRD surface that the old edit granted — the C-0002 driver — is gone in tenant-edit.
    • The residual namespaced secrets: get,list,watch,create,… in tenant-base-edit is required: each tenant owns its OpenBao-backed secrets via its own ExternalSecret→Secret in its deploy artifact. This is a legitimate secret-reader, so C-0015 for these two SAs is an exception concern (kind+name CSE, per the existing C-0002/C-0015 pattern), never a binding to drop.

    Item 2 — crossview-role wildcard read: out of this repo's scope, as the issue already notes (it ships from the upstream crossview Helm chart; tighten upstream).

    Remaining actionable residual: the "Related" note — migrating the other RBAC-attached exceptions (C-0053, C-0007, C-0037, C-0031, C-0002, C-0063, C-0035, C-0188) from namespaceSelector to kind+name matching so they actually suppress — is the real work here. That requires live Kubescape SARIF to enumerate the exact flagged kind/name per control, so it belongs on the security-posture live cadence (part of #2447), not a blind manifest edit. Re-scoping this issue to that residual; item 1 and item 2 are resolved / upstream.

  2. moved this to 📥 Backlog in 🌊 Project Boardon Jul 18, 2026
  3. devantler commented on Jul 27, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Item 1 — the premise is wrong, and the call is "except", not "remove"

    I went to implement this and the live state does not match the description. Recording the correction
    and the decision rather than leaving it deferred.

    These bindings do not reference the built-in edit role. Both reference a custom
    tenant-edit ClusterRole:

    wedding-app/wedding-app           roleRef: ClusterRole/tenant-edit
    ascoachingogvaner/ascoachingogvaner roleRef: ClusterRole/tenant-edit
    

    The substance of the C-0015 finding survives that correction — tenant-edit does grant secrets
    get,list,watch (plus write) namespace-scoped, so the control is firing on something real.

    But it is the tenant baseline, not dormant residue. tenant-edit is bound in every tenant
    namespace, including one created after this issue was filed:

    ascoachingogvaner/ascoachingogvaner -> ServiceAccount:ascoachingogvaner
    doggy-countdown/doggy-countdown     -> ServiceAccount:doggy-countdown
    wedding-app/wedding-app             -> ServiceAccount:wedding-app
    

    Three of three, one per tenant, each to its own same-named SA. That is a deliberate per-tenant grant
    being applied to new tenants as they arrive — not two leftovers. Dropping it from two of the three
    would break the baseline for those two and leave the pattern inconsistent, which is a worse state
    than the one being fixed.

    A tenant service account that can read Secrets in its own namespace is the intended tenant model,
    and it is what edit-shaped roles mean everywhere else. So under the fix-vs-except ladder this is the
    "scoped, justified exception" case: except it, with the reasoning recorded — do not remove the
    binding.

    A separate finding worth its own issue

    The observation that the SAs are unused is accurate, but points somewhere else than this issue
    concluded. All three tenants' pods run as the default ServiceAccount:

    wedding-app-…        default
    ascoachingogvaner-…  default
    

    So the scoped SA sits unused while the workloads run under default. The hardening move is to point
    the Deployments at their own tenant SA — the opposite of removing it — and it is a behaviour change
    on two live public sites, so it needs its own issue and its own validation rather than riding along
    here.

    Item 2 and item 3 unchanged

    crossview-role remains upstream chart territory, and the by-design entries stay flagged.

    The "Related" note is now measured

    That closing note turned out to be the substantial half of this issue: all six controls do surface
    failing objects, all of them RBAC-anchored, so all six exceptions are inert. Filed as #2823 with the
    counts, the wlid analysis, and one important caveat — the already-migrated controls have not gone to
    zero either, so the migration is unproven as a remedy and should be verified before six more are
    written.

  4. devantler commented on Jul 27, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Item 1 is stale, and acting on it as written would break both tenants

    Investigated this as the oldest unassigned Security issue. Do not drop those bindings. Both of item 1's premises are false against the current tree (f89efff4).

    1. They are not bound to the built-in edit. Both bind to tenant-edit:

    • k8s/bases/apps/wedding-app/role-binding.yaml → roleRef: ClusterRole/tenant-edit
    • k8s/bases/apps/ascoachingogvaner/role-binding-ascoachingogvaner.yaml → same

    tenant-edit is a custom aggregated role introduced by #2453 (fix(security): scope tenant Flux-impersonation role to drop pods/exec (C-0002)), which is the commit that changed these very files. It exists precisely to avoid the built-in edit surface: it drops the exec family (pods/exec, attach, portforward, proxy), serviceaccounts/token, and the operator CRDs that edit dynamically aggregates. The hardening this item asks for has, in substance, already happened.

    2. The ServiceAccounts are not unused. This is the load-bearing correction. They are the Flux reconciliation identities:

    • k8s/bases/apps/wedding-app/flux-kustomization.yaml:19 → serviceAccountName: wedding-app
    • k8s/bases/apps/ascoachingogvaner/flux-kustomization.yaml:19 → serviceAccountName: ascoachingogvaner

    tenant-edit's own header states it: "Flux's kustomize-controller IMPERSONATES this SA (spec.serviceAccountName) to reconcile the tenant's manifests, so this role is the tenant's entire in-namespace capability."

    The original check looked for pods using the SA and correctly found none — but the consumer is not a pod, it is kustomize-controller impersonating the identity. Removing the bindings would leave both Kustomizations unable to apply anything, so the two live sites would silently stop reconciling. Filed as "actionable, low risk"; it is neither.

    What that leaves

    The secrets access these identities hold is inherent capability, not dormant over-privilege: a tenant's reconciler must create and update the Secrets its own manifests declare. So there is no RBAC root-cause fix here to make — if C-0015 still flags them, the correct instrument under the fix-vs-except ladder is a scoped, justified exception naming these as reconciliation identities, not a binding removal.

    Item 2 (crossview-role wildcard read, upstream chart) and the "left flagged by design" set are untouched by this and still stand.

    Suggest item 1 be rewritten to that effect, or split out and closed, so a later run does not read the original text and ship the removal.

    Scope of verification: static, against the pinned tree — file contents, the roleRef, the aggregation rule, and the serviceAccountName wiring, plus the commit that changed them. I did not confirm live cluster state: kubectl is blocked by this runtime's permission classifier, and I did not work around it. Live confirmation that the two Kustomizations are currently reconciling under these SAs is still worth doing before any change here.

  5. devantler commented on Jul 27, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Correction to my previous comment — the ServiceAccounts are NOT unused

    My "separate finding" above was wrong, and the suggestion in it would have broken both tenants.

    I checked for pods using those ServiceAccounts, found none, and concluded they were idle. The
    consumer is not a pod — it is Flux:

    k8s/bases/apps/wedding-app/flux-kustomization.yaml:19        serviceAccountName: wedding-app
    k8s/bases/apps/ascoachingogvaner/flux-kustomization.yaml:19  serviceAccountName: ascoachingogvaner
    

    These are impersonation identities. kustomize-controller assumes them to reconcile each tenant,
    which is what confines a tenant's Flux reconciliation to its own namespace. So the tenant-edit
    binding is not dormant at all — it is the permission Flux actually uses on every reconcile, and its
    Secret access is load-bearing rather than latent.

    That strengthens the conclusion but invalidates my reasoning for it:

    • Unchanged: the binding stays. Removing it would stop both tenants reconciling — silently, since
      a Kustomization that cannot impersonate simply fails rather than alerting.
    • Withdrawn: my suggestion to point the Deployments at the tenant SA. The pods running as
      default is the intended split — Flux holds the write identity, the workload does not. Filing that
      as a hardening issue would have been wrong.

    The edit-vs-tenant-edit correction and the "all three tenants have it" measurement both stand.

    Credit where due: a concurrent agent instance identified the impersonation mechanism independently;
    I verified it against the manifests above before writing this.

  6. devantler commented on Jul 27, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Disposition: closing — every item here is answered, and leaving it open is now costing more than it buys

    This is the oldest open Security issue in the portfolio, so it is handed to whichever run reaches
    rung 2. Four separate investigations today alone (17:15Z, and three between 16:08Z and 16:25Z)
    reached the same conclusions and none of them closed it, so the next run picks it up and re-derives
    them again. That is the actual harm: the issue is not tracking work, it is recycling a settled
    answer — and the action it still recommends in its own body would break two tenants.

    Recording the disposition per item rather than leaving a fifth investigation on the pile.

    Item 1 — "unused tenant SAs bound to ClusterRole/edit" → will not do; both premises are false

    • Not edit. wedding-app/wedding-app and ascoachingogvaner/ascoachingogvaner bind to the
      custom aggregated tenant-edit ClusterRole, which was introduced after this issue was filed
      precisely to avoid the built-in edit surface — it drops pods/exec, attach, portforward,
      proxy and serviceaccounts/token.
    • Not unused. The consumer is not a pod, it is Flux: both tenants' Kustomizations set
      spec.serviceAccountName, so kustomize-controller impersonates these SAs to reconcile each
      tenant. That impersonation is what confines a tenant to its own namespace. Dropping the binding
      would stop both tenants reconciling — and silently, because a Kustomization that cannot impersonate
      fails rather than alerting.

    The C-0015 finding is real (tenant-edit does grant secrets get,list,watch), but it is the tenant
    baseline
    , bound in every tenant namespace including ones created since. So the remedy was never
    "remove"; it is "except" — see below.

    Item 2 — crossview-role wildcard read → out of scope for this repo

    The ClusterRole ships from the upstream crossview Helm chart. Tightening it belongs upstream, not
    here, exactly as the issue itself says.

    "Left flagged by design" → no action, working as intended

    system:masters, crossplane:masters and the on-demand longhorn-support-bundle SA are supposed to
    surface. Surfacing the highest-privilege identities is the control doing its job.

    "Related" — migrate the other RBAC controls to kind+name matching → tracked in #2823, and the proposed fix is known not to work

    This is the only live thread, and it does not belong here. Migrating those controls to kind+name
    matching would produce more inert exceptions: measured on 2026-07-27, five of C-0187's six failing
    objects are ones the wildcard-rbac-by-design CR explicitly names, three by exact kind and name,
    and no object anywhere in the cluster carries the w-exceptions marker the operator writes when it
    applies an exception. #2823 owns that, with its direction already rewritten.

    Net

    Nothing here is an unshipped remediation: item 1 is a correction, item 2 is upstream, the flagged
    identities are by design, and the one live thread has its own issue. Closing as completed —
    reopen if the exception mechanism starts applying and the tenant-baseline suppression becomes
    writable again.

  7. moved this from 📥 Backlog to ✅ Done in 🌊 Project Boardon Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Fields

    No fields configured for Security.

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions