Repository navigation
Description
What steps did you take:
- Install a kapp-controller build that vendors
k8s.io/apiserver v0.36.0+on a Kubernetes 1.36+ cluster (reproduced on kind 1.36 and EKS 1.36). - Ensure the deployed ClusterRole for the kapp-controller ServiceAccount does NOT include
list+watchonmutatingadmissionpoliciesandmutatingadmissionpolicybindingsin the
admissionregistration.k8s.iogroup. This state arises naturally when: (a) an operator upgrades kapp-controller in-place without re-applying the shipped ClusterRole, (b) a distribution ships its own ClusterRole
derived from an older release, or (c) an operator has deliberately narrowed RBAC. - Create a
PackageRepositorytargeting any OCI bundle:
kubectl apply -f - <<EOF
apiVersion: packaging.carvel.dev/v1alpha1
kind: PackageRepository
metadata:
name: repro
namespace: default
spec:
fetch:
imgpkgBundle:
image:
EOF - Observe the reconciler state:
kubectl describe packagerepository/repro
What happened:
The PackageRepository fails to reconcile with:
Reconcile failed: kapp: Error: create packagemetadata/<name> (data.packaging.carvel.dev/v1alpha1) namespace: default:
Creating resource packagemetadata/<name> (data.packaging.carvel.dev/v1alpha1) namespace: default:
API server says: packagemetadatas.data.packaging.carvel.dev "<name>" is forbidden:
not yet ready to handle request (reason: Forbidden)
Every mutating request into data.packaging.carvel.dev/v1alpha1 fails the same way. Reads (get, list, watch) continue to work. The kapp-controller pod reports Ready 2/2. The aggregated APIService reports
Status.Available=True. Pod logs contain a high-frequency stream of Failed to watch *v1.MutatingAdmissionPolicy: ... is forbidden: User "system:serviceaccount:kapp-controller:kapp-controller-sa" cannot list resource "mutatingadmissionpolicies".
Root cause:
k8s.io/apiserver v0.36 added the MutatingAdmissionPolicy admission plugin to RecommendedPluginOrder by default, and the plugin's feature gate went GA-on in Kubernetes 1.36. On 1.36+ clusters the plugin loads automatically; its SharedInformer tries to list+watch MutatingAdmissionPolicy and MutatingAdmissionPolicyBinding; if RBAC blocks that, HasSynced() never returns true and every mutating
admission decision returns admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) from vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/plugin.go. pkg/apiserver/apiserver.go
already guards each admission-policy plugin behind a discovery-based serverResourceExists check, but discovery only proves the resource exists in the cluster — not that the ServiceAccount can watch it. On 1.36+
the resource always exists, so the guard passes and the plugin loads even when RBAC is insufficient.
The same failure mode can affect ValidatingAdmissionPolicy on k8s 1.30+ when its RBAC is out of sync.
What did you expect:
Loading an admission-policy plugin whose backing SharedInformer cannot start should not permanently break the aggregated apiserver. The apiserver should either:
- Not load the plugin when the ServiceAccount lacks the RBAC required to run its informers, and emit a clear, actionable warning naming the specific resource and verb that would need to be granted; OR
- Surface the failure loudly at startup (fail-fast or prominent error-level log) rather than passing readiness checks and silently rejecting every write.
At a minimum, APIService.Status.Available=True should not be reported when the admission chain is permanently in the "not yet ready" state.
Anything else you would like to add:
Suggested fix: extend the existing plugin guard in pkg/apiserver/apiserver.go to also probe the ServiceAccount's permissions via SelfSubjectAccessReview before loading the plugin. Concretely, replace the two serverResourceExists(...) calls in the plugin-order block with a new helper canUseAdmissionPolicyPlugin(discoveryClient, coreClient, policyResource, bindingResource, logger) that returns true only when:
- Both resources are present under
admissionregistration.k8s.io/v1in discovery, AND - A
SelfSubjectAccessReviewforlistand forwatchon each of the two resources comes back withAllowed=true.
When the check fails due to missing RBAC, log a single warning naming the specific (resource, verb) pair the SA is missing, so operators can grep and fix. The SelfSubjectAccessReview probe uses
subjectaccessreviews/create, which is already granted in the shipped ClusterRole — no new RBAC required for the probe itself.
The proposed fix keeps the aggregated apiserver functional across every supported k8s version regardless of the operator's RBAC state, and turns a silent 403-forever loop into a single actionable log line at startup.
Environment:
- kapp-controller version: any build vendoring
k8s.io/apiserver v0.36.0+(includingdevelopat HEAD). Releases vendoring olderk8s.io/apiserverdo not exhibit this bug because theMutatingAdmissionPolicy
plugin does not exist in those library versions. - Kubernetes version:
Client Version: v1.36
Server Version: v1.36
Vote on this request
This is an invitation to the community to vote on issues, to help us prioritize our backlog. Use the "smiley face" up to the right of this comment to vote.
👍 "I would like to see this addressed as soon as possible"
👎 "There are other more important things to focus on right now"
We are also happy to receive and review Pull Requests if you want to help working on this issue.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsClosed
Activity
fix(apiserver): probe SA permissions before loading admission-policy …
github-actions commentedon Aug 18, 2026
This issue is being marked as stale due to a long period of inactivity and will be closed in 5 days if there is no response.