-
Notifications
You must be signed in to change notification settings - Fork 612
Add support for API key authN in TrafficPolicy #12962
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: Yossi Mesika <[email protected]>
Signed-off-by: Yossi Mesika <[email protected]>
There was a problem hiding this 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
APIKeyAuthenticationfield 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.
install/helm/kgateway-crds/templates/gateway.kgateway.dev_trafficpolicies.yaml
Outdated
Show resolved
Hide resolved
install/helm/kgateway-crds/templates/gateway.kgateway.dev_trafficpolicies.yaml
Outdated
Show resolved
Hide resolved
Signed-off-by: Yossi Mesika <[email protected]>
Signed-off-by: Yossi Mesika <[email protected]>
Signed-off-by: Yossi Mesika <[email protected]>
Signed-off-by: Yossi Mesika <[email protected]>
Signed-off-by: Yossi Mesika <[email protected]>
Signed-off-by: Yossi Mesika <[email protected]>
Signed-off-by: Yossi Mesika <[email protected]>
lgadban
left a comment
There was a problem hiding this 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
| // 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) | ||
| } | ||
| } |
There was a problem hiding this comment.
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.).
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Signed-off-by: Yossi Mesika <[email protected]>
Signed-off-by: Yossi Mesika <[email protected]>
Signed-off-by: Yossi Mesika <[email protected]>
lgadban
left a comment
There was a problem hiding this 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.
| // APIKeyAuthentication authenticates users based on a configured API Key. | ||
| // +optional | ||
| APIKeyAuthentication *APIKeyAuthentication `json:"apiKeyAuthentication,omitempty"` |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
pkg/krtcollections/secrets.go
Outdated
|
|
||
| // 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] { |
There was a problem hiding this comment.
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:
kgateway/pkg/krtcollections/policy.go
Lines 322 to 324 in 8419f0a
| 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?
There was a problem hiding this comment.
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.
Signed-off-by: Yossi Mesika <[email protected]>
Signed-off-by: Yossi Mesika <[email protected]>
| // client2: "k-456" | ||
| // | ||
| // +optional | ||
| SecretRef *gwv1.SecretObjectReference `json:"secretRef,omitempty"` |
There was a problem hiding this comment.
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.
Signed-off-by: Yossi Mesika <[email protected]>
| if !s.refgrants.ReferenceAllowed(kctx, from.GroupKind, from.Namespace, to) { | ||
| return nil, ErrMissingReferenceGrant | ||
| } |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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:
SecretReforSecretSelectorenvoy.filters.http.api_key_authfilterTesting:
CI: Added e2e to
cluster-fivein CI workflowFixes #12909
Change Type
/kind feature
Changelog
Additional Notes