Skip to content

Conversation

@ymesika
Copy link
Contributor

@ymesika ymesika commented Nov 24, 2025

Description

Design for this is available in: #12919 (pending approval)

This PR adds API key authentication support to TrafficPolicies, allowing API keys to be provided via HTTP headers, query parameters, or cookies.

Features:

  • API key sources: header, query parameter, and cookie
  • Secret management via SecretRef or SecretSelector
  • Policy attachment at gateway, HTTPRoute, and route levels
  • Uses Envoy's native envoy.filters.http.api_key_auth filter

Testing:

  • Unit, translator (golden files), and e2e tests
  • E2e tests cover all attachment levels, key sources, and secret update propagation

CI: Added e2e to cluster-five in CI workflow

Fixes #12909

Change Type

/kind feature

Changelog

Add support for configuring an API key authentication in TrafficPolicy with keys defined in secret(s)

Additional Notes

Copilot AI review requested due to automatic review settings November 24, 2025 16:35
@gateway-bot gateway-bot added kind/feature Categorizes issue or PR as related to a new feature. release-note labels Nov 24, 2025
Copilot finished reviewing on behalf of ymesika November 24, 2025 16:38
Signed-off-by: Yossi Mesika <[email protected]>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds API key authentication support to TrafficPolicy, enabling users to authenticate requests using API keys provided via HTTP headers, query parameters, or cookies. The implementation uses Envoy's native envoy.filters.http.api_key_auth filter and integrates with Kubernetes secrets for key management.

Key Changes:

  • Added APIKeyAuthentication field to TrafficPolicy CRD with support for multiple key sources (header/query/cookie) and flexible secret management
  • Implemented API key auth filter handling in the trafficpolicy plugin following the same pattern as RBAC (disabled by default, configured per-route)
  • Added comprehensive test coverage including unit tests, translator tests (golden files), and e2e tests covering all attachment levels and secret update propagation

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
api/v1alpha1/traffic_policy_types.go Defines APIKeyAuthentication and APIKeySource types with validation constraints
api/v1alpha1/zz_generated.deepcopy.go Auto-generated deepcopy methods for new API types
install/helm/kgateway-crds/templates/gateway.kgateway.dev_trafficpolicies.yaml CRD schema definition for API key authentication
internal/kgateway/krtcollections/secrets.go Adds GetSecretCollection method to access secrets without reference grant checks
internal/kgateway/extensions2/plugins/trafficpolicy/api_key_auth.go Core implementation of API key auth translation to Envoy configuration
internal/kgateway/extensions2/plugins/trafficpolicy/api_key_auth_test.go Unit tests for API key auth IR, validation, and filter handling
internal/kgateway/extensions2/plugins/trafficpolicy/traffic_policy_plugin.go Integration of API key auth into trafficpolicy plugin filter chain
internal/kgateway/extensions2/plugins/trafficpolicy/merge.go Merge logic for API key auth policies
internal/kgateway/extensions2/plugins/trafficpolicy/constructor.go Constructor integration for API key auth IR
internal/kgateway/translator/gateway/gateway_translator_test.go Translator tests for API key auth at gateway, HTTPRoute, and route levels
internal/kgateway/translator/gateway/testutils/inputs/traffic-policy/ Golden file test inputs for various API key auth configurations
internal/kgateway/translator/gateway/testutils/outputs/traffic-policy/ Expected golden file outputs for API key auth translation
test/e2e/features/apikeyauth/types.go E2e test suite type definitions and manifest paths
test/e2e/features/apikeyauth/suite.go E2e test implementations covering all key sources and secret updates
test/e2e/features/apikeyauth/testdata/ Test manifests for e2e tests
test/e2e/tests/kgateway_tests.go Registration of APIKeyAuth test suite
.github/workflows/e2e.yaml Added APIKeyAuth tests to cluster-five CI workflow

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Signed-off-by: Yossi Mesika <[email protected]>
@lgadban lgadban self-requested a review November 25, 2025 04:06
Copy link
Contributor

@lgadban lgadban left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add translator tests (and maybe augment the e2e test as well) to have a scenario with APIKey policy attached to both Gateway and Route?

IIUC the behavior should be that the route-specific policy is the only one applied

Comment on lines 89 to 96
// Fetch secrets matching labels, then filter by namespace
allSecrets := krt.Fetch(krtctx, secretCol, krt.FilterLabel(ak.SecretSelector.MatchLabels))
for i := range allSecrets {
secret := &allSecrets[i]
if secret.Namespace == policy.Namespace {
secrets = append(secrets, secret)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not filter by namespace as part of the initial krt.Fetch?
Something like:

krt.Fetch(krtctx, secretCol, krt.FilterGeneric(func(obj any) bool {...}, krt.FilterLabel(ak.SecretSelector.MatchLabels))

We still may need to iterate on the specifics here to get performance to a reasonable place. (adding a specific Index, etc.)

A very similar concept was added in #11163.

Either way, we should make sure and track some basic performance numbers (i.e. resource impact vs. number of secrets, etc.).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can also be a follow up

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modified to have a FilterGeneric

}

// +kubebuilder:validation:ExactlyOneOf=secretRef;secretSelector
type APIKeyAuthentication struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized this after the EP has merged, but we don't expose a disable option for APIKey.

We may need to in order to be consistent with our other policies.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately the envoy api doesn't have a disabled field like other filter configs.
I think we can still support this by adding config to all routes instead of a higher level one. When it's disabled on a route we will avoid adding it.
Is this an approach that makes sense?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do this in a follow up, but we have general pattern we can use to handle global disable for various policies by using Envoy's generic filter disable mechanism and a specifically configured composite filter.

See #12945 for more details on how we did it for JWT.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't manage to add it to this PR. Will open a follow-up an issue for that.

Copy link
Contributor

@lgadban lgadban left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!
Approved with a few nits, but I definitely think we need to make the secret collection getter longer and more explicit in a follow up, possibly when we add global disable.

Comment on lines +126 to +128
// APIKeyAuthentication authenticates users based on a configured API Key.
// +optional
APIKeyAuthentication *APIKeyAuthentication `json:"apiKeyAuthentication,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a nit and would like other opinions, but this could just be apiKey ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm indifferent between the two names. Hoping to get other opinions too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't have consensus so I would just leave as-is and we can address in a follow up


// GetSecretCollection returns the secret collection for a given GroupKind.
// This is useful for accessing secrets without reference grant checks (e.g., same namespace).
func (s *SecretIndex) GetSecretCollection(gk schema.GroupKind) krt.Collection[ir.Secret] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably rename this method to be explicit that refGrants are not being used here, and should be explicit in the godoc that you MUST know what you are doing to ignore refGrant look ups

See below for how we handled this for a similar method:

func (i *BackendIndex) GetBackendFromRefWithoutRefGrantValidation(kctx krt.HandlerContext, src ir.ObjectSource, ref gwv1.BackendObjectReference) (*ir.BackendObjectIR, error) {
return i.getBackendFromRef(kctx, src.Namespace, ref)
}

Either way though, you are exposing this because we need the raw collection to do our own FilterKey right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this important comment.
This led me to totally modify how we handle the Secret ref and selectors and take into account whether there is a reference grant when matching secrets are not in the same namespace.
Also added multiple setup tests for those.

// client2: "k-456"
//
// +optional
SecretRef *gwv1.SecretObjectReference `json:"secretRef,omitempty"`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lgadban notice I changed it to SecretObjectReference from LocalObjectReference.

Comment on lines +122 to +124
if !s.refgrants.ReferenceAllowed(kctx, from.GroupKind, from.Namespace, to) {
return nil, ErrMissingReferenceGrant
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we return here, is there even value in collecting allowedSecrets?

should we just continue but aggregate errors encountered, then return allowedSecrets and the joined errors if any are present?

kctx krt.HandlerContext,
from From,
secretGK schema.GroupKind,
namespace string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is namespace field actually used?

// This is useful for accessing secrets without reference grant checks (e.g., same namespace).
func (s *SecretIndex) GetSecretCollection(gk schema.GroupKind) krt.Collection[ir.Secret] {
return s.secrets[gk]
// GetSecretsBySelector retrieves secrets matching the label selector in the specified namespace,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see comment below, this doc is confusing as namespace doesn't seem to be relevant here.

IIUC, this method will get secrets matching labels in all namespaces and then enforce RefGrants for that set of secrets

apiKeyAuthManifestQuery = filepath.Join(fsutils.MustGetThisDir(), "testdata", "api-key-auth-query.yaml")
apiKeyAuthManifestCookie = filepath.Join(fsutils.MustGetThisDir(), "testdata", "api-key-auth-cookie.yaml")
apiKeyAuthManifestSecretUpdate = filepath.Join(fsutils.MustGetThisDir(), "testdata", "api-key-auth-secret-update.yaml")
apiKeyAuthManifestDisable = filepath.Join(fsutils.MustGetThisDir(), "testdata", "api-key-auth-disable.yaml")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apiKeyAuthManifestDisable is not currently used right? (as it will be implemented in a follow up?)

can we remove it for now and all the related types/files/etc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

according to https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/api_key_auth/v3/api_key_auth.proto#envoy-v3-api-field-extensions-filters-http-api-key-auth-v3-keysource-header

if the header source is used, it supports the prefix of Bearer in the header value.

If it's easy to add, it would be great to add a test case for this to the basic api-key-auth.yaml test.
I imagine an extremely common use case would be the standard Authorization header with a Bearer $APIKEY value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature Categorizes issue or PR as related to a new feature. release-note

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Envoy Dataplane: API Key

3 participants