@@ -20,11 +20,17 @@ import (
2020 "context"
2121 "encoding/json"
2222 "fmt"
23+ "time"
2324
2425 corev1 "k8s.io/api/core/v1"
26+ "k8s.io/apimachinery/pkg/labels"
27+ ctrl "sigs.k8s.io/controller-runtime"
2528 "sigs.k8s.io/controller-runtime/pkg/builder"
29+ "sigs.k8s.io/controller-runtime/pkg/cache"
2630 "sigs.k8s.io/controller-runtime/pkg/client"
31+ "sigs.k8s.io/controller-runtime/pkg/handler"
2732 "sigs.k8s.io/controller-runtime/pkg/log"
33+ "sigs.k8s.io/controller-runtime/pkg/source"
2834 "sigs.k8s.io/gateway-api-inference-extension/pkg/bbr/framework"
2935 errcommon "sigs.k8s.io/gateway-api-inference-extension/pkg/common/error"
3036 logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/common/observability/logging"
@@ -45,23 +51,37 @@ var _ framework.RequestProcessor = &ApiKeyInjectionPlugin{}
4551
4652// APIKeyInjectionFactory defines the factory function for ApiKeyInjectionPlugin.
4753func APIKeyInjectionFactory (name string , _ json.RawMessage , handle framework.Handle ) (framework.BBRPlugin , error ) {
48- plugin , err := NewAPIKeyInjectionPlugin (handle .ReconcilerBuilder , handle .ClientReader ())
54+ plugin , err := NewAPIKeyInjectionPlugin (handle .Context (), handle . ReconcilerBuilder , handle .ClientReader ())
4955 if err != nil {
5056 return nil , fmt .Errorf ("failed to create plugin '%s' - %w" , APIKeyInjectionPluginType , err )
5157 }
5258
5359 return plugin .WithName (name ), nil
5460}
5561
56- // NewAPIKeyInjectionPlugin creates a new apiKeyInjectionPlugin and returns its pointer
57- func NewAPIKeyInjectionPlugin (reconcilerBuilder func () * builder.Builder , clientReader client.Reader ) (* ApiKeyInjectionPlugin , error ) {
62+ // NewAPIKeyInjectionPlugin creates a new apiKeyInjectionPlugin and returns its pointer.
63+ // It sets up a label-filtered informer cache so only Secrets matching the
64+ // ipp-managed label are listed from the API server; Reconcile still checks the
65+ // label after Get before updating the store.
66+ func NewAPIKeyInjectionPlugin (ctx context.Context , reconcilerBuilder func () * builder.Builder , clientReader client.Reader ) (* ApiKeyInjectionPlugin , error ) {
5867 store := newSecretStore ()
5968 reconciler := & secretReconciler {
6069 Reader : clientReader ,
6170 store : store ,
6271 }
6372
64- if err := reconcilerBuilder ().For (& corev1.Secret {}).WithEventFilter (managedLabelPredicate ()).Complete (reconciler ); err != nil {
73+ filteredCache , err := newFilteredSecretCache (ctx )
74+ if err != nil {
75+ return nil , err
76+ }
77+
78+ var secretObj client.Object = & corev1.Secret {}
79+ if err := reconcilerBuilder ().
80+ Named ("apikey-injection-secret-watcher" ).
81+ WatchesRawSource (
82+ source .Kind (filteredCache , secretObj , & handler.EnqueueRequestForObject {}),
83+ ).
84+ Complete (reconciler ); err != nil {
6585 return nil , fmt .Errorf ("failed to register Secret reconciler for plugin '%s' - %w" , APIKeyInjectionPluginType , err )
6686 }
6787
@@ -71,9 +91,9 @@ func NewAPIKeyInjectionPlugin(reconcilerBuilder func() *builder.Builder, clientR
7191 Name : APIKeyInjectionPluginType ,
7292 },
7393 authHeadersGenerators : map [string ]auth.AuthHeadersGenerator {
74- provider .OpenAI : & auth.SimpleAuthGenerator {HeaderName : "Authorization" , HeaderValuePrefix : "Bearer " },
75- provider .Anthropic : & auth.SimpleAuthGenerator {HeaderName : "x-api-key" },
76- provider .AzureOpenAI : & auth.SimpleAuthGenerator {HeaderName : "api-key" },
94+ provider .OpenAI : & auth.SimpleAuthGenerator {HeaderName : "Authorization" , HeaderValuePrefix : "Bearer " },
95+ provider .Anthropic : & auth.SimpleAuthGenerator {HeaderName : "x-api-key" },
96+ provider .AzureOpenAI : & auth.SimpleAuthGenerator {HeaderName : "api-key" },
7797 // provider.Vertex uses the native GenerateContent API — not used in 3.4 ExternalModel flow.
7898 // provider.Vertex: &auth.SimpleAuthGenerator{HeaderName: "Authorization", HeaderValuePrefix: "Bearer "},
7999 provider .VertexOpenAI : & auth.SimpleAuthGenerator {HeaderName : "Authorization" , HeaderValuePrefix : "Bearer " },
@@ -145,3 +165,42 @@ func (p *ApiKeyInjectionPlugin) ProcessRequest(ctx context.Context, cycleState *
145165 log .FromContext (ctx ).V (logutil .VERBOSE ).Info ("auth headers injected" , "provider" , providerName )
146166 return nil
147167}
168+
169+ // newFilteredSecretCache creates a controller-runtime cache that restricts the
170+ // Secret informer to only list/watch Secrets labeled with the managed label.
171+ // This is a defense-in-depth measure: even though the RBAC ClusterRole grants
172+ // broad Secret access (required for cross-namespace watches), the informer
173+ // never fetches or caches unrelated Secrets.
174+ func newFilteredSecretCache (ctx context.Context ) (cache.Cache , error ) {
175+ cfg , err := ctrl .GetConfig ()
176+ if err != nil {
177+ return nil , fmt .Errorf ("failed to get rest config for filtered Secret cache: %w" , err )
178+ }
179+
180+ filteredCache , err := cache .New (cfg , cache.Options {
181+ ByObject : map [client.Object ]cache.ByObject {
182+ & corev1.Secret {}: {
183+ Label : labels .SelectorFromSet (labels.Set {
184+ managedLabel : "true" ,
185+ }),
186+ },
187+ },
188+ })
189+ if err != nil {
190+ return nil , fmt .Errorf ("failed to create filtered Secret cache: %w" , err )
191+ }
192+
193+ go func () {
194+ if err := filteredCache .Start (ctx ); err != nil {
195+ ctrl .Log .WithName (APIKeyInjectionPluginType ).Error (err , "filtered Secret cache stopped unexpectedly" )
196+ }
197+ }()
198+
199+ syncCtx , cancel := context .WithTimeout (ctx , 2 * time .Minute )
200+ defer cancel ()
201+ if ! filteredCache .WaitForCacheSync (syncCtx ) {
202+ return nil , fmt .Errorf ("filtered Secret cache failed to sync within deadline" )
203+ }
204+
205+ return filteredCache , nil
206+ }
0 commit comments