From a08d0064ab6b3253cec6caf838a6079ba8743178 Mon Sep 17 00:00:00 2001 From: Abhishek Choudhary Date: Thu, 8 Oct 2026 22:56:29 +0545 Subject: [PATCH] fix: hide cross-namespace owner in duplicate key-auth error Signed-off-by: Abhishek Choudhary --- internal/webhook/v1/consumer_webhook.go | 4 +++ internal/webhook/v1/consumer_webhook_test.go | 35 +++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/internal/webhook/v1/consumer_webhook.go b/internal/webhook/v1/consumer_webhook.go index 0f141e85..5f9b4201 100644 --- a/internal/webhook/v1/consumer_webhook.go +++ b/internal/webhook/v1/consumer_webhook.go @@ -193,6 +193,10 @@ func (v *ConsumerCustomValidator) validateDuplicateKeyAuthCredentials(ctx contex if _, ok := keys[key]; ok { // Do not include the credential value in the error: it is returned to // API clients and logged, which would leak the secret key material. + // Name the owner only within the caller's own namespace. + if existing.Namespace != consumer.Namespace { + return fmt.Errorf("duplicate key-auth credential already used by another Consumer") + } return fmt.Errorf("duplicate key-auth credential already used by Consumer %s/%s", existing.Namespace, existing.Name) } } diff --git a/internal/webhook/v1/consumer_webhook_test.go b/internal/webhook/v1/consumer_webhook_test.go index 67c8a300..aa4ff8de 100644 --- a/internal/webhook/v1/consumer_webhook_test.go +++ b/internal/webhook/v1/consumer_webhook_test.go @@ -371,6 +371,39 @@ func TestConsumerValidator_DenyDuplicateKeyAuthCredential(t *testing.T) { require.NotContains(t, err.Error(), "shared-key") } +// A collision with a Consumer in another namespace must not reveal its identity. +func TestConsumerValidator_DenyDuplicateKeyAuthCredentialCrossNamespace(t *testing.T) { + gatewayNS := "default" + existing := &apisixv1alpha1.Consumer{ + ObjectMeta: metav1.ObjectMeta{Name: "victim", Namespace: authNS}, + Spec: apisixv1alpha1.ConsumerSpec{ + GatewayRef: apisixv1alpha1.GatewayRef{Name: "test-gateway", Namespace: &gatewayNS}, + Credentials: []apisixv1alpha1.Credential{{ + Type: "key-auth", + Config: apiextensionsv1.JSON{Raw: []byte(`{"key":"shared-key"}`)}, + }}, + }, + } + consumer := &apisixv1alpha1.Consumer{ + ObjectMeta: metav1.ObjectMeta{Name: "demo", Namespace: "default"}, + Spec: apisixv1alpha1.ConsumerSpec{ + GatewayRef: apisixv1alpha1.GatewayRef{Name: "test-gateway"}, + Credentials: []apisixv1alpha1.Credential{{ + Type: "key-auth", + Config: apiextensionsv1.JSON{Raw: []byte(`{"key":"shared-key"}`)}, + }}, + }, + } + + validator := buildConsumerValidator(t, existing) + + _, err := validator.ValidateCreate(context.Background(), consumer) + require.Error(t, err) + require.Contains(t, err.Error(), "duplicate key-auth credential already used by another Consumer") + require.NotContains(t, err.Error(), authNS+"/victim") + require.NotContains(t, err.Error(), "shared-key") +} + // The duplicate-key check must not become a cross-namespace oracle: a key-auth // credential whose secretRef points across namespaces without a ReferenceGrant // must respond the same whether the Secret exists with a colliding key or is @@ -416,5 +449,5 @@ func TestConsumerValidator_CrossNamespaceKeyAuthDuplicateOracleSuppressed(t *tes // Identical response either way, and no duplicate-key error ever surfaces. require.Equal(t, absentWarnings, collidesWarnings) require.Equal(t, fmt.Sprint(absentErr), fmt.Sprint(collidesErr)) - require.NotContains(t, fmt.Sprint(collidesErr), "duplicate key-auth credential key") + require.NotContains(t, fmt.Sprint(collidesErr), "duplicate key-auth credential") }