This is an automated email from the ASF dual-hosted git repository.

AlinsRan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new 78840341 fix: resolve ApisixPluginConfig secretRef in its own 
namespace (#2901)
78840341 is described below

commit 7884034159b671480788c2f9e08ff6a7132aceb1
Author: AlinsRan <[email protected]>
AuthorDate: Fri Oct 9 18:01:33 2026 +0800

    fix: resolve ApisixPluginConfig secretRef in its own namespace (#2901)
---
 internal/controller/apisixroute_controller.go      |  17 ++--
 .../controller/apisixroute_pluginconfig_test.go    | 101 +++++++++++++++++++++
 2 files changed, 110 insertions(+), 8 deletions(-)

diff --git a/internal/controller/apisixroute_controller.go 
b/internal/controller/apisixroute_controller.go
index 099e1fdf..a64aaec9 100644
--- a/internal/controller/apisixroute_controller.go
+++ b/internal/controller/apisixroute_controller.go
@@ -195,7 +195,7 @@ func (r *ApisixRouteReconciler) processApisixRoute(tctx 
*provider.TranslateConte
                rules[http.Name] = struct{}{}
 
                // check secret
-               if err := r.validatePlugins(tctx, in, http.Plugins); err != nil 
{
+               if err := r.validatePlugins(tctx, in.Namespace, http.Plugins); 
err != nil {
                        return err
                }
 
@@ -243,7 +243,7 @@ func (r *ApisixRouteReconciler) processApisixRoute(tctx 
*provider.TranslateConte
                rules[stream.Name] = struct{}{}
 
                // check secret
-               if err := r.validatePlugins(tctx, in, stream.Plugins); err != 
nil {
+               if err := r.validatePlugins(tctx, in.Namespace, 
stream.Plugins); err != nil {
                        return err
                }
 
@@ -256,14 +256,14 @@ func (r *ApisixRouteReconciler) processApisixRoute(tctx 
*provider.TranslateConte
        return nil
 }
 
-func (r *ApisixRouteReconciler) validatePlugins(tctx 
*provider.TranslateContext, in *apiv2.ApisixRoute, plugins 
[]apiv2.ApisixRoutePlugin) error {
+func (r *ApisixRouteReconciler) validatePlugins(tctx 
*provider.TranslateContext, namespace string, plugins 
[]apiv2.ApisixRoutePlugin) error {
        // check secret
        for _, plugin := range plugins {
                if !plugin.Enable {
                        continue
                }
                // check secret
-               if err := r.validateSecrets(tctx, in, plugin.SecretRef); err != 
nil {
+               if err := r.validateSecrets(tctx, namespace, plugin.SecretRef); 
err != nil {
                        return err
                }
        }
@@ -324,14 +324,15 @@ func (r *ApisixRouteReconciler) validatePluginConfig(tctx 
*provider.TranslateCon
 
        tctx.ApisixPluginConfigs[pcNN] = &pc
 
-       // Also check secrets referenced by plugin config
-       if err := r.validatePlugins(tctx, in, pc.Spec.Plugins); err != nil {
+       // Secrets referenced by the plugin config resolve in its own namespace,
+       // which differs from the route's when plugin_config_namespace is set.
+       if err := r.validatePlugins(tctx, pc.Namespace, pc.Spec.Plugins); err 
!= nil {
                return err
        }
        return nil
 }
 
-func (r *ApisixRouteReconciler) validateSecrets(tctx 
*provider.TranslateContext, in *apiv2.ApisixRoute, secretRef string) error {
+func (r *ApisixRouteReconciler) validateSecrets(tctx 
*provider.TranslateContext, namespace string, secretRef string) error {
        if secretRef == "" {
                return nil
        }
@@ -339,7 +340,7 @@ func (r *ApisixRouteReconciler) validateSecrets(tctx 
*provider.TranslateContext,
                secret = corev1.Secret{
                        ObjectMeta: metav1.ObjectMeta{
                                Name:      secretRef,
-                               Namespace: in.Namespace,
+                               Namespace: namespace,
                        },
                }
                secretNN = utils.NamespacedName(&secret)
diff --git a/internal/controller/apisixroute_pluginconfig_test.go 
b/internal/controller/apisixroute_pluginconfig_test.go
new file mode 100644
index 00000000..d07337fb
--- /dev/null
+++ b/internal/controller/apisixroute_pluginconfig_test.go
@@ -0,0 +1,101 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package controller
+
+import (
+       "context"
+       "testing"
+
+       "github.com/go-logr/logr"
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+       corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/runtime"
+       k8stypes "k8s.io/apimachinery/pkg/types"
+       clientgoscheme "k8s.io/client-go/kubernetes/scheme"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+       "sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+       apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
+       "github.com/apache/apisix-ingress-controller/internal/provider"
+)
+
+// A secretRef inside an ApisixPluginConfig resolves in the plugin config's
+// namespace, which is where the translator looks it up. Loading it from the
+// route's namespace instead silently dropped the secret values whenever
+// plugin_config_namespace pointed elsewhere.
+func TestValidatePluginConfig_CrossNamespaceSecretRef(t *testing.T) {
+       const (
+               pcNamespace    = "ns-a"
+               routeNamespace = "ns-b"
+               secretName     = "my-secret"
+               secretKey      = "credential"
+       )
+
+       pcSecret := &corev1.Secret{
+               ObjectMeta: metav1.ObjectMeta{Namespace: pcNamespace, Name: 
secretName},
+               Data:       map[string][]byte{secretKey: 
[]byte("from-pc-namespace")},
+       }
+       routeSecret := &corev1.Secret{
+               ObjectMeta: metav1.ObjectMeta{Namespace: routeNamespace, Name: 
secretName},
+               Data:       map[string][]byte{secretKey: 
[]byte("from-route-namespace")},
+       }
+       pc := &apiv2.ApisixPluginConfig{
+               ObjectMeta: metav1.ObjectMeta{Namespace: pcNamespace, Name: 
"shared-config"},
+               Spec: apiv2.ApisixPluginConfigSpec{
+                       Plugins: []apiv2.ApisixRoutePlugin{{Name: "echo", 
Enable: true, SecretRef: secretName}},
+               },
+       }
+       route := &apiv2.ApisixRoute{
+               ObjectMeta: metav1.ObjectMeta{Namespace: routeNamespace, Name: 
"cross-ns-route"},
+       }
+       rule := apiv2.ApisixRouteHTTP{
+               Name:                  "cross-ns-rule",
+               PluginConfigName:      pc.Name,
+               PluginConfigNamespace: pcNamespace,
+       }
+
+       pcSecretKey := k8stypes.NamespacedName{Namespace: pcNamespace, Name: 
secretName}
+       routeSecretKey := k8stypes.NamespacedName{Namespace: routeNamespace, 
Name: secretName}
+
+       for name, tc := range map[string]struct {
+               objects []client.Object
+       }{
+               "secret only in plugin config namespace": {objects: 
[]client.Object{pc, pcSecret}},
+               "same-named secret in both namespaces":   {objects: 
[]client.Object{pc, pcSecret, routeSecret}},
+       } {
+               t.Run(name, func(t *testing.T) {
+                       scheme := runtime.NewScheme()
+                       require.NoError(t, clientgoscheme.AddToScheme(scheme))
+                       require.NoError(t, apiv2.AddToScheme(scheme))
+                       r := &ApisixRouteReconciler{
+                               Client: 
fake.NewClientBuilder().WithScheme(scheme).WithObjects(tc.objects...).Build(),
+                               Scheme: scheme,
+                               Log:    logr.Discard(),
+                       }
+                       tctx := 
provider.NewDefaultTranslateContext(context.Background())
+
+                       require.NoError(t, r.validatePluginConfig(tctx, route, 
rule))
+
+                       require.Contains(t, tctx.Secrets, pcSecretKey)
+                       assert.Equal(t, "from-pc-namespace", 
string(tctx.Secrets[pcSecretKey].Data[secretKey]))
+                       assert.NotContains(t, tctx.Secrets, routeSecretKey)
+               })
+       }
+}

Reply via email to