From c7fe92d3eeb3d05f849d2b8200540161f0b07d0f Mon Sep 17 00:00:00 2001 From: Arnob Kumar Saha Date: Tue, 3 Nov 2026 22:07:01 +0600 Subject: [PATCH] Build impersonated client without the shared cache DelegatingClient.Impersonate reused the base client's options, including the cache reader. The cache is populated with the base client's credentials, so any cachable typed Get/List through an impersonated client was served from that cache without the impersonation headers, bypassing the impersonated user's authorization. Drop the cache from the options passed to the impersonated client so it reads directly from the API, and treat a nil CacheReader as always-bypass in the delegating reader. Signed-off-by: Arnob Kumar Saha --- client/delegated.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/delegated.go b/client/delegated.go index b1a1364e9..7e2886826 100644 --- a/client/delegated.go +++ b/client/delegated.go @@ -136,6 +136,11 @@ func (d *DelegatingClient) Impersonate(u user.Info) (*restclient.Config, client. // share the transport between all clients optionsShallowCopy := d.options + // The shared cache is populated with the base client's credentials, so serving an + // impersonated read from it would skip the impersonated user's authorization. Drop it + // here: the impersonated client reads directly from the API with the impersonation + // headers set on config above. + optionsShallowCopy.Cache = nil if d.options.HTTPClient != nil { optionsShallowCopy.HTTPClient = &http.Client{ Transport: transport.NewImpersonatingRoundTripper(transport.ImpersonationConfig{ @@ -186,6 +191,9 @@ type delegatingReader struct { } func (d *delegatingReader) shouldBypassCache(obj runtime.Object) (bool, error) { + if d.CacheReader == nil { + return true, nil + } gvk, err := apiutil.GVKForObject(obj, d.scheme) if err != nil { return false, err