fix: enforce ReferenceGrant on cross-namespace Consumer SecretRef#432
fix: enforce ReferenceGrant on cross-namespace Consumer SecretRef#432shreemaan-abhishek wants to merge 2 commits into
Conversation
A v1alpha1 Consumer credential secretRef carries an attacker-settable namespace. The reconciler honored it and read the Secret with the controller's cluster-wide RBAC, no ReferenceGrant check, binding a foreign namespace's auth material to the consumer identity (confused deputy). The route controllers already gate cross-namespace refs via checkReferenceGrant; apply the same gate in the Consumer reconciler and refuse the reference when no grant permits it.
📝 WalkthroughWalkthroughConsumer reconciliation and webhook validation now require a matching ChangesConsumer Secret Reference Authorization
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 error, 1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
conformance test report - apisix modeapiVersion: gateway.networking.k8s.io/v1
date: "2026-07-17T11:01:49Z"
gatewayAPIChannel: experimental
gatewayAPIVersion: v1.3.0
implementation:
contact: null
organization: APISIX
project: apisix-ingress-controller
url: https://github.com/apache/apisix-ingress-controller.git
version: v2.0.0
kind: ConformanceReport
mode: default
profiles:
- core:
result: success
statistics:
Failed: 0
Passed: 12
Skipped: 0
name: GATEWAY-GRPC
summary: Core tests succeeded.
- core:
failedTests:
- HTTPRouteInvalidBackendRefUnknownKind
result: failure
skippedTests:
- HTTPRouteHTTPSListener
statistics:
Failed: 1
Passed: 31
Skipped: 1
extended:
result: partial
skippedTests:
- HTTPRouteRedirectPortAndScheme
statistics:
Failed: 0
Passed: 11
Skipped: 1
supportedFeatures:
- GatewayAddressEmpty
- GatewayPort8080
- HTTPRouteBackendProtocolWebSocket
- HTTPRouteDestinationPortMatching
- HTTPRouteHostRewrite
- HTTPRouteMethodMatching
- HTTPRoutePathRewrite
- HTTPRoutePortRedirect
- HTTPRouteQueryParamMatching
- HTTPRouteRequestMirror
- HTTPRouteResponseHeaderModification
- HTTPRouteSchemeRedirect
unsupportedFeatures:
- GatewayHTTPListenerIsolation
- GatewayInfrastructurePropagation
- GatewayStaticAddresses
- HTTPRouteBackendProtocolH2C
- HTTPRouteBackendRequestHeaderModification
- HTTPRouteBackendTimeout
- HTTPRouteParentRefPort
- HTTPRoutePathRedirect
- HTTPRouteRequestMultipleMirrors
- HTTPRouteRequestPercentageMirror
- HTTPRouteRequestTimeout
name: GATEWAY-HTTP
summary: Core tests failed with 1 test failures. Extended tests partially succeeded
with 1 test skips.
- core:
result: partial
skippedTests:
- TLSRouteSimpleSameNamespace
statistics:
Failed: 0
Passed: 10
Skipped: 1
name: GATEWAY-TLS
summary: Core tests partially succeeded with 1 test skips. |
conformance test report - apisix-standalone modeapiVersion: gateway.networking.k8s.io/v1
date: "2026-07-17T11:00:53Z"
gatewayAPIChannel: experimental
gatewayAPIVersion: v1.3.0
implementation:
contact: null
organization: APISIX
project: apisix-ingress-controller
url: https://github.com/apache/apisix-ingress-controller.git
version: v2.0.0
kind: ConformanceReport
mode: default
profiles:
- core:
result: partial
skippedTests:
- HTTPRouteHTTPSListener
statistics:
Failed: 0
Passed: 32
Skipped: 1
extended:
result: partial
skippedTests:
- HTTPRouteRedirectPortAndScheme
statistics:
Failed: 0
Passed: 11
Skipped: 1
supportedFeatures:
- GatewayAddressEmpty
- GatewayPort8080
- HTTPRouteBackendProtocolWebSocket
- HTTPRouteDestinationPortMatching
- HTTPRouteHostRewrite
- HTTPRouteMethodMatching
- HTTPRoutePathRewrite
- HTTPRoutePortRedirect
- HTTPRouteQueryParamMatching
- HTTPRouteRequestMirror
- HTTPRouteResponseHeaderModification
- HTTPRouteSchemeRedirect
unsupportedFeatures:
- GatewayHTTPListenerIsolation
- GatewayInfrastructurePropagation
- GatewayStaticAddresses
- HTTPRouteBackendProtocolH2C
- HTTPRouteBackendRequestHeaderModification
- HTTPRouteBackendTimeout
- HTTPRouteParentRefPort
- HTTPRoutePathRedirect
- HTTPRouteRequestMultipleMirrors
- HTTPRouteRequestPercentageMirror
- HTTPRouteRequestTimeout
name: GATEWAY-HTTP
summary: Core tests partially succeeded with 1 test skips. Extended tests partially
succeeded with 1 test skips.
- core:
result: partial
skippedTests:
- TLSRouteSimpleSameNamespace
statistics:
Failed: 0
Passed: 10
Skipped: 1
name: GATEWAY-TLS
summary: Core tests partially succeeded with 1 test skips.
- core:
result: success
statistics:
Failed: 0
Passed: 12
Skipped: 0
name: GATEWAY-GRPC
summary: Core tests succeeded. |
conformance test reportapiVersion: gateway.networking.k8s.io/v1
date: "2026-07-17T11:18:27Z"
gatewayAPIChannel: experimental
gatewayAPIVersion: v1.3.0
implementation:
contact: null
organization: APISIX
project: apisix-ingress-controller
url: https://github.com/apache/apisix-ingress-controller.git
version: v2.0.0
kind: ConformanceReport
mode: default
profiles:
- core:
failedTests:
- GatewayModifyListeners
result: failure
statistics:
Failed: 1
Passed: 11
Skipped: 0
name: GATEWAY-GRPC
summary: Core tests failed with 1 test failures.
- core:
failedTests:
- GatewayModifyListeners
result: failure
skippedTests:
- HTTPRouteHTTPSListener
statistics:
Failed: 1
Passed: 31
Skipped: 1
extended:
result: partial
skippedTests:
- HTTPRouteRedirectPortAndScheme
statistics:
Failed: 0
Passed: 11
Skipped: 1
supportedFeatures:
- GatewayAddressEmpty
- GatewayPort8080
- HTTPRouteBackendProtocolWebSocket
- HTTPRouteDestinationPortMatching
- HTTPRouteHostRewrite
- HTTPRouteMethodMatching
- HTTPRoutePathRewrite
- HTTPRoutePortRedirect
- HTTPRouteQueryParamMatching
- HTTPRouteRequestMirror
- HTTPRouteResponseHeaderModification
- HTTPRouteSchemeRedirect
unsupportedFeatures:
- GatewayHTTPListenerIsolation
- GatewayInfrastructurePropagation
- GatewayStaticAddresses
- HTTPRouteBackendProtocolH2C
- HTTPRouteBackendRequestHeaderModification
- HTTPRouteBackendTimeout
- HTTPRouteParentRefPort
- HTTPRoutePathRedirect
- HTTPRouteRequestMultipleMirrors
- HTTPRouteRequestPercentageMirror
- HTTPRouteRequestTimeout
name: GATEWAY-HTTP
summary: Core tests failed with 1 test failures. Extended tests partially succeeded
with 1 test skips.
- core:
failedTests:
- GatewayModifyListeners
- TLSRouteSimpleSameNamespace
result: failure
statistics:
Failed: 2
Passed: 9
Skipped: 0
name: GATEWAY-TLS
summary: Core tests failed with 2 test failures. |
…ment The Consumer admission validator shares processSpec via PrepareConsumerForValidation, so the ReferenceGrant gate now also fail-closes at admission. Update the two cross-namespace webhook tests to permit the reference via a ReferenceGrant, and add a test asserting a cross-namespace secretRef without a grant is denied at admission.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/webhook/v1/consumer_webhook_test.go`:
- Around line 167-172: Update the ValidateCreate test around
buildConsumerValidator so its fake client fails when Get requests
auth/jwt-secret, preventing the foreign Secret from being served before
ReferenceGrant validation. Preserve the existing error assertion containing “not
permitted by any ReferenceGrant”.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 86667401-87d4-4f82-9437-00651858b187
📒 Files selected for processing (1)
internal/webhook/v1/consumer_webhook_test.go
| secret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "jwt-secret", Namespace: "auth"}} | ||
| validator := buildConsumerValidator(t, secret) | ||
|
|
||
| _, err := validator.ValidateCreate(context.Background(), consumer) | ||
| require.Error(t, err) | ||
| require.Contains(t, err.Error(), "not permitted by any ReferenceGrant") |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
ast-grep outline internal/webhook/v1/consumer_webhook.go --match ConsumerCustomValidator --view expanded
rg -n -C4 'func \(v \*ConsumerCustomValidator\) collectWarnings|checker\.Secret|adcValidator\.Validate|processSpec' \
internal/webhook/v1/consumer_webhook.go internal/webhook/v1/reference/checker.goRepository: api7/api7-ingress-controller
Length of output: 4073
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the test around the cited lines
sed -n '1,260p' internal/webhook/v1/consumer_webhook_test.go
echo
echo '--- consumer_webhook.go (relevant slice) ---'
sed -n '115,150p' internal/webhook/v1/consumer_webhook.go
echo
echo '--- reference/checker.go (relevant slice) ---'
sed -n '1,240p' internal/webhook/v1/reference/checker.goRepository: api7/api7-ingress-controller
Length of output: 13339
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '60,95p' internal/webhook/v1/consumer_webhook.go
echo
echo '--- adc admission validator references ---'
rg -n -C3 'type adcAdmissionValidator|func \(v \*adcAdmissionValidator\) Validate|ReferenceGrant|Check.*Grant|Secret\(' internal/webhook/v1 -g '*.go'Repository: api7/api7-ingress-controller
Length of output: 18985
Block the foreign Secret lookup (internal/webhook/v1/consumer_webhook_test.go:167-172)
Because ValidateCreate collects warnings first, the fake client can still serve auth/jwt-secret before the ReferenceGrant denial. Use a client that fails on Get for that Secret and keep the not permitted by any ReferenceGrant assertion.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/webhook/v1/consumer_webhook_test.go` around lines 167 - 172, Update
the ValidateCreate test around buildConsumerValidator so its fake client fails
when Get requests auth/jwt-secret, preventing the foreign Secret from being
served before ReferenceGrant validation. Preserve the existing error assertion
containing “not permitted by any ReferenceGrant”.
Source: Coding guidelines
Fixes api7/rfcs#138 (Sec Review FINDING-004).
What this fixes
A
v1alpha1Consumercredential'ssecretRefcarries a caller-settablenamespace.ConsumerReconciler.processSpechonored it and read the Secret with the controller's cluster-widesecretsRBAC, with noReferenceGrantcheck, thenTranslateConsumerV1alpha1copied the entire foreign Secret's data into the ADC consumer credential and pushed it to the data plane.A user who can only
create consumersin namespace A could namenamespace-B/victim-secretand bind that namespace's auth material (key-auth keys, basic-auth passwords, JWT secrets, HMAC keys) to a Consumer identity they own. The caller never hadget secretsin namespace B; the controller does. Classic cross-namespace confused deputy.The route controllers already gate cross-namespace refs via
checkReferenceGrant; the Consumer path was the one auth pathway that skipped it. Thev2ApisixConsumerAPI is namespace-locked (LocalObjectReference), so onlyv1alpha1was affected.The fix
Enforce
checkReferenceGrantbefore dereferencing a cross-namespace ConsumersecretRef, matching the route controllers. Same-namespace refs are unaffected (the check short-circuits). Without a permitting grant the reconcile refuses the reference and surfaces the error on the Consumer status, so the foreign Secret is never loaded or bound.Tests
internal/controller/consumer_controller_test.gocovers cross-namespace denied (no grant), cross-namespace allowed (matching grant), and same-namespace allowed.Sync
Paired open-source fix: apache/apisix-ingress-controller#2805 (identical change). Per the two-PR sync rule, the sync is complete when both merge.
Summary by CodeRabbit
ReferenceGrantis present.