Skip to content

HelmRelease delete unnecessarily requires list+watch RBAC on serviceaccounts #1586

Description

@tsaarni

When a HelmRelease uses impersonation spec.serviceAccountName, deleting the HelmRelease triggers a ServiceAccount existence check here

if err = r.Client.Get(ctx, types.NamespacedName{
Namespace: obj.GetNamespace(),
Name: serviceAccount,
}, &corev1.ServiceAccount{}); err != nil {

This call only needs the get verb, but because controller-runtime's cache handles the request, it creates a cluster-wide list+watch informer for ServiceAccount. This unexpectedly requires list and watch RBAC on serviceaccounts.

With least-privilege RBAC, the delete fails with repeating errors and the uninstall never completes:

{"level":"error","ts":"2026-09-10T17:14:32.785Z","logger":"controller-runtime.cache.UnhandledError","msg":"Failed to watch","reflector":"k8s.io/client-go@v0.36.4/tools/cache/reflector.go:343","type":"*v1.ServiceAccount","error":"failed to list *v1.ServiceAccount: serviceaccounts is forbidden: User \"system:serviceaccount:flux-system:restricted\" cannot list resource \"serviceaccounts\" in API group \"\" at the cluster scope"}

Here are manual reproduction steps

kubectl apply -f https://github.com/fluxcd/flux2/releases/download/v2.9.5/install.yaml

# Run helm-controller with RBAC that lacks list and watch on serviceaccounts
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: ServiceAccount
metadata:
  name: restricted
  namespace: flux-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: restricted
rules:
  - apiGroups: [helm.toolkit.fluxcd.io]
    resources: ['*']
    verbs: ['*']
  - apiGroups: [source.toolkit.fluxcd.io]
    resources: ['*']
    verbs: ['*']
  - apiGroups: ['']
    resources: [secrets, configmaps]
    verbs: [get, list, watch]
  - apiGroups: ['']
    resources: [events]
    verbs: [create, patch]
  - apiGroups: [coordination.k8s.io]
    resources: [leases]
    verbs: ['*']
  - apiGroups: ['']
    resources: [serviceaccounts]
    verbs: [get, impersonate]   # list and watch removed
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: restricted
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: restricted
subjects:
  - kind: ServiceAccount
    name: restricted
    namespace: flux-system
EOF
kubectl -n flux-system set serviceaccount deployment/helm-controller restricted
kubectl -n flux-system delete pod -l app=helm-controller --force

# ServiceAccount to impersonate, plus a HelmRelease that uses it
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: ServiceAccount
metadata:
  name: deployer
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: deployer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: deployer
    namespace: default
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
  name: podinfo
spec:
  url: https://stefanprodan.github.io/podinfo
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: podinfo
spec:
  serviceAccountName: deployer
  interval: 5m
  chart:
    spec:
      chart: podinfo
      sourceRef:
        kind: HelmRepository
        name: podinfo
EOF

kubectl wait helmrelease/podinfo --for=condition=ready

# Delete hangs to a forbidden list request
kubectl delete helmrelease podinfo --wait=false
sleep 5
kubectl -n flux-system logs deployment/helm-controller --since=30s | grep ServiceAccount

# The error, repeating:
#   {"level":"error","ts":"2026-09-10T17:14:32.785Z","logger":"controller-runtime.cache.UnhandledError","msg":"Failed to watch","reflector":"k8s.io/client-go@v0.36.4/tools/cache/reflector.go:343","type":"*v1.ServiceAccount","error":"failed to list *v1.ServiceAccount: serviceaccounts is forbidden: User \"system:serviceaccount:flux-system:restricted\" cannot list resource \"serviceaccounts\" in API group \"\" at the cluster scope"}

# Podinfo still exists
kubectl get deployment podinfo

Activity

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions