From 5ff979747d336a346ea30206d8c0df91825c75bb Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Fri, 13 Feb 2026 09:48:10 +0800 Subject: [PATCH 1/2] add test for multiple keys Signed-off-by: Huabing (Robin) Zhao --- test/e2e/tests/api_key_auth.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/e2e/tests/api_key_auth.go b/test/e2e/tests/api_key_auth.go index e328e3783c..bacb64b834 100644 --- a/test/e2e/tests/api_key_auth.go +++ b/test/e2e/tests/api_key_auth.go @@ -67,6 +67,31 @@ var APIKeyAuthTest = suite.ConformanceTest{ http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) + // Verify non-first client IDs from the same secret are retained in cache and accepted. + expectedResponse = http.ExpectedResponse{ + Request: http.Request{ + Path: "/api-key-auth-header", + Headers: map[string]string{ + "X-API-KEY": "key2", + }, + }, + ExpectedRequest: &http.ExpectedRequest{ + Request: http.Request{ + Path: "/api-key-auth-header", + Headers: map[string]string{ + "X-API-KEY-CLIENT-ID": "client2", + }, + }, + AbsentHeaders: []string{"X-API-KEY"}, + }, + Response: http.Response{ + StatusCodes: []int{200}, + }, + Namespace: ns, + } + + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) + expectedResponse = http.ExpectedResponse{ Request: http.Request{ Path: "/api-key-auth-header", From de88d635342afeccecde132716de2f3b74c71bbb Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Fri, 13 Feb 2026 10:11:17 +0800 Subject: [PATCH 2/2] revert secret transform Signed-off-by: Huabing (Robin) Zhao --- internal/provider/kubernetes/helpers.go | 45 +--- internal/provider/kubernetes/helpers_test.go | 211 ------------------- internal/provider/kubernetes/kubernetes.go | 1 - release-notes/current.yaml | 1 + 4 files changed, 9 insertions(+), 249 deletions(-) diff --git a/internal/provider/kubernetes/helpers.go b/internal/provider/kubernetes/helpers.go index c3ccfb5dac..f3210f7b78 100644 --- a/internal/provider/kubernetes/helpers.go +++ b/internal/provider/kubernetes/helpers.go @@ -27,32 +27,14 @@ const ( gatewayClassFinalizer = gwapiv1.GatewayClassFinalizerGatewaysExist ) -var ( - // cachedConfigMapKeys defines the keys to keep in ConfigMap data cache - cachedConfigMapKeys = map[string]bool{ - gatewayapi.JWKSConfigMapKey: true, - gatewayapi.LuaConfigMapKey: true, - gatewayapi.ResponseBodyConfigMapKey: true, - gatewayapi.CACertKey: true, - gatewayapi.CRLKey: true, - } - - // cachedSecretKeys defines the keys to keep in Secret data cache - cachedSecretKeys = map[string]bool{ - corev1.TLSCertKey: true, - corev1.TLSPrivateKeyKey: true, - egv1a1.TLSOCSPKey: true, - egv1a1.OIDCClientIDKey: true, - egv1a1.OIDCClientSecretKey: true, - egv1a1.BasicAuthUsersSecretKey: true, - egv1a1.InjectedCredentialKey: true, - egv1a1.APIKeysSecretKey: true, - corev1.DockerConfigJsonKey: true, - gatewayapi.CACertKey: true, - gatewayapi.CRLKey: true, - hmacSecretKey: true, - } -) +// cachedConfigMapKeys defines the keys to keep in ConfigMap data cache +var cachedConfigMapKeys = map[string]bool{ + gatewayapi.JWKSConfigMapKey: true, + gatewayapi.LuaConfigMapKey: true, + gatewayapi.ResponseBodyConfigMapKey: true, + gatewayapi.CACertKey: true, + gatewayapi.CRLKey: true, +} type ObjectKindNamespacedName struct { kind string @@ -230,17 +212,6 @@ func transformConfigMapData(obj interface{}) (interface{}, error) { return cm, nil } -// transformSecretData filters Secret data to only keep needed keys to reduce memory usage. -func transformSecretData(obj interface{}) (interface{}, error) { - secret, ok := obj.(*corev1.Secret) - if !ok || len(secret.Data) <= 1 { - return obj, nil - } - - secret.Data = expectedAndFirstFallbackFilter(secret.Data, cachedSecretKeys) - return secret, nil -} - // composeTransforms chains multiple transform functions together. func composeTransforms(transforms ...toolscache.TransformFunc) toolscache.TransformFunc { return func(obj interface{}) (interface{}, error) { diff --git a/internal/provider/kubernetes/helpers_test.go b/internal/provider/kubernetes/helpers_test.go index 7fb645e634..4c8fd4cb87 100644 --- a/internal/provider/kubernetes/helpers_test.go +++ b/internal/provider/kubernetes/helpers_test.go @@ -592,214 +592,3 @@ func TestTransformConfigMapData(t *testing.T) { }) } } - -func TestTransformSecretData(t *testing.T) { - testCases := []struct { - name string - input interface{} - expected map[string][]byte - }{ - { - name: "nil secret", - input: nil, - expected: nil, - }, - { - name: "non-secret object", - input: &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - }, - }, - expected: nil, - }, - { - name: "secret with single key - no filtering", - input: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - }, - Data: map[string][]byte{ - "key1": []byte("value1"), - }, - }, - expected: map[string][]byte{ - "key1": []byte("value1"), - }, - }, - { - name: "secret with cached keys only", - input: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - }, - Data: map[string][]byte{ - corev1.TLSCertKey: []byte("cert-data"), - corev1.TLSPrivateKeyKey: []byte("key-data"), - egv1a1.TLSOCSPKey: []byte("ocsp-data"), - }, - }, - expected: map[string][]byte{ - corev1.TLSCertKey: []byte("cert-data"), - corev1.TLSPrivateKeyKey: []byte("key-data"), - egv1a1.TLSOCSPKey: []byte("ocsp-data"), - }, - }, - { - name: "secret with cached and non-cached keys", - input: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - }, - Data: map[string][]byte{ - corev1.TLSCertKey: []byte("cert-data"), - corev1.TLSPrivateKeyKey: []byte("key-data"), - "unwanted-key-1": []byte("unwanted-value-1"), - "unwanted-key-2": []byte("unwanted-value-2"), - egv1a1.TLSOCSPKey: []byte("ocsp-data"), - }, - }, - expected: map[string][]byte{ - corev1.TLSCertKey: []byte("cert-data"), - corev1.TLSPrivateKeyKey: []byte("key-data"), - egv1a1.TLSOCSPKey: []byte("ocsp-data"), - // Note: First key "tls.crt" is expected, so no fallback key is added - }, - }, - { - name: "secret with only non-cached keys", - input: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - }, - Data: map[string][]byte{ - "unwanted-key-1": []byte("unwanted-value-1"), - "unwanted-key-2": []byte("unwanted-value-2"), - "unwanted-key-3": []byte("unwanted-value-3"), - }, - }, - expected: map[string][]byte{ - "unwanted-key-1": []byte("unwanted-value-1"), // first key in sorted order - }, - }, - { - name: "secret with OIDC keys", - input: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - }, - Data: map[string][]byte{ - egv1a1.OIDCClientIDKey: []byte("client-id"), - egv1a1.OIDCClientSecretKey: []byte("client-secret"), - "other-key": []byte("other-value"), - }, - }, - expected: map[string][]byte{ - egv1a1.OIDCClientIDKey: []byte("client-id"), - egv1a1.OIDCClientSecretKey: []byte("client-secret"), - // Note: First key "client-id" is expected, so no fallback key is added - }, - }, - { - name: "secret with BasicAuth and APIKeys keys", - input: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - }, - Data: map[string][]byte{ - egv1a1.BasicAuthUsersSecretKey: []byte("basic-auth-data"), - egv1a1.APIKeysSecretKey: []byte("api-keys-data"), - "unwanted-key": []byte("unwanted-value"), - }, - }, - expected: map[string][]byte{ - egv1a1.BasicAuthUsersSecretKey: []byte("basic-auth-data"), - egv1a1.APIKeysSecretKey: []byte("api-keys-data"), - // Note: First key ".htpasswd" is expected, so no fallback key is added - }, - }, - { - name: "secret with DockerConfigJsonKey", - input: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - }, - Data: map[string][]byte{ - corev1.DockerConfigJsonKey: []byte("docker-config"), - "other-key": []byte("other-value"), - }, - }, - expected: map[string][]byte{ - corev1.DockerConfigJsonKey: []byte("docker-config"), - // Note: First key ".dockerconfigjson" is expected, so no fallback key is added - }, - }, - { - name: "secret with CACertKey and CRLKey", - input: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - }, - Data: map[string][]byte{ - gatewayapi.CACertKey: []byte("ca-cert-data"), - gatewayapi.CRLKey: []byte("crl-data"), - "other-key": []byte("other-value"), - }, - }, - expected: map[string][]byte{ - gatewayapi.CACertKey: []byte("ca-cert-data"), - gatewayapi.CRLKey: []byte("crl-data"), - // Note: First key "ca.crl" is expected, so no fallback key is added - }, - }, - { - name: "secret with non-expected key first", - input: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - }, - Data: map[string][]byte{ - "unwanted-key": []byte("unwanted-value"), - corev1.TLSCertKey: []byte("cert-data"), - corev1.TLSPrivateKeyKey: []byte("key-data"), - "another-unwanted-key": []byte("another-value"), - }, - }, - expected: map[string][]byte{ - "another-unwanted-key": []byte("another-value"), // first key in sorted order, added as fallback - corev1.TLSCertKey: []byte("cert-data"), - corev1.TLSPrivateKeyKey: []byte("key-data"), - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - transform := composeTransforms(cache.TransformStripManagedFields(), transformSecretData) - - result, err := transform(tc.input) - require.NoError(t, err) - - if tc.expected == nil { - require.Equal(t, tc.input, result) - return - } - - secret, ok := result.(*corev1.Secret) - require.True(t, ok, "result should be a Secret") - if tc.expected != nil { - require.Equal(t, tc.expected, secret.Data) - } - }) - } -} diff --git a/internal/provider/kubernetes/kubernetes.go b/internal/provider/kubernetes/kubernetes.go index 9ace673e90..ac63b398c5 100644 --- a/internal/provider/kubernetes/kubernetes.go +++ b/internal/provider/kubernetes/kubernetes.go @@ -163,7 +163,6 @@ func newProvider(ctx context.Context, restCfg *rest.Config, svrCfg *ec.Server, // Disable deepcopy for read only resources &corev1.Secret{}: { UnsafeDisableDeepCopy: ptr.To(true), - Transform: composeTransforms(cache.TransformStripManagedFields(), transformSecretData), }, &corev1.ConfigMap{}: { UnsafeDisableDeepCopy: ptr.To(true), diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 67b66c6ada..e16ca349e1 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -16,6 +16,7 @@ bug fixes: | Rejected ClientTrafficPolicy if invalid TLS cipher suites are configured. Fixed validation of XListenerSet certificateRefs Fixed XListenerSet not allowing xRoutes from the same namespace when configured to allow them + Fixed API key authentication dropping non-first client IDs when credential Secrets contain multiple keys. # Enhancements that improve performance. performance improvements: |