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)
+ })
+ }
+}