Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 8 additions & 37 deletions internal/provider/kubernetes/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
211 changes: 0 additions & 211 deletions internal/provider/kubernetes/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
}
1 change: 0 additions & 1 deletion internal/provider/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions release-notes/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
25 changes: 25 additions & 0 deletions test/e2e/tests/api_key_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down