fix: suppress cross-namespace Secret existence oracle in Consumer webhook#433
fix: suppress cross-namespace Secret existence oracle in Consumer webhook#433shreemaan-abhishek wants to merge 1 commit into
Conversation
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 6 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughChangesConsumer Secret access validation
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant ConsumerWebhook
participant CheckConsumerSecretRef
participant ReferenceGrant
participant SecretLookup
ConsumerWebhook->>CheckConsumerSecretRef: Check cross-namespace Secret access
CheckConsumerSecretRef->>ReferenceGrant: Evaluate Consumer-to-Secret permission
alt Access denied
ConsumerWebhook-->>ConsumerWebhook: Emit inaccessible warning
else Access granted
ConsumerWebhook->>SecretLookup: Check referenced Secret
SecretLookup-->>ConsumerWebhook: Return existence result
end
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/controller/utils.go`:
- Around line 1350-1364: Update CheckConsumerSecretRef and the underlying
checkReferenceGrant flow so ReferenceGrant list failures remain distinguishable
from an explicit authorization denial. Preserve fail-closed behavior and avoid
probing the Secret, but propagate or log the lookup error and have the webhook
emit a neutral validation warning when evaluation fails instead of reporting a
missing ReferenceGrant; ensure the error is not silently swallowed.
In `@internal/webhook/v1/consumer_webhook_test.go`:
- Around line 96-140: Update both cross-namespace tests, including
TestConsumerValidator_CrossNamespaceSecretOracleSuppressed and
TestConsumerValidator_CrossNamespaceSecretWithGrant, to save the existing
ReferenceGrant enablement state, enable it during each test, and restore the
saved value via cleanup. Replace the unconditional reset to false so global
controller state is preserved for subsequent tests.
🪄 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: 080b3cb6-6637-4698-87fd-42b481bd90cf
📒 Files selected for processing (3)
internal/controller/utils.gointernal/webhook/v1/consumer_webhook.gointernal/webhook/v1/consumer_webhook_test.go
| func CheckConsumerSecretRef(ctx context.Context, cli client.Client, fromNamespace string, secretNN k8stypes.NamespacedName) bool { | ||
| secretNS := secretNN.Namespace | ||
| return checkReferenceGrant(ctx, cli, | ||
| v1beta1.ReferenceGrantFrom{ | ||
| Group: v1beta1.Group(v1alpha1.GroupVersion.Group), | ||
| Kind: types.KindConsumer, | ||
| Namespace: v1beta1.Namespace(fromNamespace), | ||
| }, | ||
| gatewayv1.ObjectReference{ | ||
| Group: corev1.GroupName, | ||
| Kind: types.KindSecret, | ||
| Name: gatewayv1.ObjectName(secretNN.Name), | ||
| Namespace: (*gatewayv1.Namespace)(&secretNS), | ||
| }, | ||
| ) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Preserve ReferenceGrant lookup failures separately from denial.
checkReferenceGrant maps cli.List errors to false, so this helper makes the webhook report a missing ReferenceGrant when authorization could not be evaluated. Keep the fail-closed/no-Secret-probe behavior, but propagate or log the lookup error and emit a neutral validation warning instead.
As per coding guidelines, errors must be properly handled and not silently swallowed.
🤖 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/controller/utils.go` around lines 1350 - 1364, Update
CheckConsumerSecretRef and the underlying checkReferenceGrant flow so
ReferenceGrant list failures remain distinguishable from an explicit
authorization denial. Preserve fail-closed behavior and avoid probing the
Secret, but propagate or log the lookup error and have the webhook emit a
neutral validation warning when evaluation fails instead of reporting a missing
ReferenceGrant; ensure the error is not silently swallowed.
Source: Coding guidelines
| // Cross-namespace refs without a permitting ReferenceGrant must not reveal whether | ||
| // the Secret exists: found and not-found both yield the same uniform warning. | ||
| func TestConsumerValidator_CrossNamespaceSecretOracleSuppressed(t *testing.T) { | ||
| ns := "auth" | ||
| newConsumer := func() *apisixv1alpha1.Consumer { | ||
| return &apisixv1alpha1.Consumer{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "demo", | ||
| Namespace: "default", | ||
| }, | ||
| Spec: apisixv1alpha1.ConsumerSpec{ | ||
| GatewayRef: apisixv1alpha1.GatewayRef{Name: "test-gateway"}, | ||
| Credentials: []apisixv1alpha1.Credential{{ | ||
| Type: "jwt-auth", | ||
| SecretRef: &apisixv1alpha1.SecretReference{ | ||
| Name: "jwt-secret", | ||
| Namespace: &ns, | ||
| }, | ||
| }}, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // Secret present in the target namespace, but no ReferenceGrant permits the ref. | ||
| present := buildConsumerValidator(t, &corev1.Secret{ | ||
| ObjectMeta: metav1.ObjectMeta{Name: "jwt-secret", Namespace: "auth"}, | ||
| }) | ||
| presentWarnings, err := present.ValidateCreate(context.Background(), newConsumer()) | ||
| require.NoError(t, err) | ||
|
|
||
| // Secret absent. | ||
| absent := buildConsumerValidator(t) | ||
| absentWarnings, err := absent.ValidateCreate(context.Background(), newConsumer()) | ||
| require.NoError(t, err) | ||
|
|
||
| // Identical output either way: no existence oracle. | ||
| require.Equal(t, absentWarnings, presentWarnings) | ||
| require.Len(t, presentWarnings, 1) | ||
| require.Contains(t, presentWarnings[0], "Referenced Secret 'auth/jwt-secret' is not accessible from this Consumer without a ReferenceGrant") | ||
| } | ||
|
|
||
| // A ReferenceGrant re-enables the normal existence check for the permitted namespace. | ||
| func TestConsumerValidator_CrossNamespaceSecretWithGrant(t *testing.T) { | ||
| controller.SetEnableReferenceGrant(true) | ||
| defer controller.SetEnableReferenceGrant(false) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Exercise denial with ReferenceGrant evaluation enabled and restore prior state.
The oracle test currently passes when ReferenceGrant support is disabled, before grant lookup is exercised. Save the existing flag, enable it for both cross-namespace tests, and restore the saved value with cleanup; the current unconditional reset to false can leak global state into other tests.
🤖 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 96 - 140, Update
both cross-namespace tests, including
TestConsumerValidator_CrossNamespaceSecretOracleSuppressed and
TestConsumerValidator_CrossNamespaceSecretWithGrant, to save the existing
ReferenceGrant enablement state, enable it during each test, and restore the
saved value via cleanup. Replace the unconditional reset to false so global
controller state is preserved for subsequent tests.
conformance test report - apisix-standalone modeapiVersion: gateway.networking.k8s.io/v1
date: "2026-07-24T09:09:25Z"
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 modeapiVersion: gateway.networking.k8s.io/v1
date: "2026-07-24T09:09:28Z"
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:
- 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.
- 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-17T09:53:46Z"
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
skippedTests:
- HTTPRouteHTTPSListener
statistics:
Failed: 1
Passed: 31
Skipped: 1
extended:
failedTests:
- HTTPRouteBackendProtocolWebSocket
result: failure
skippedTests:
- HTTPRouteRedirectPortAndScheme
statistics:
Failed: 1
Passed: 10
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 failed with 1 test
failures.
- core:
failedTests:
- GatewayModifyListeners
- TLSRouteSimpleSameNamespace
result: failure
statistics:
Failed: 2
Passed: 9
Skipped: 0
name: GATEWAY-TLS
summary: Core tests failed with 2 test failures.
- core:
failedTests:
- GatewayModifyListeners
result: failure
statistics:
Failed: 1
Passed: 11
Skipped: 0
name: GATEWAY-GRPC
summary: Core tests failed with 1 test failures. |
…hook Fixes Sec Review FINDING-051 (api7/rfcs#185). The Consumer validating webhook's warning pass emitted a "Referenced Secret not found" warning only when a referenced Secret was absent; a resolvable Secret produced no warning. Warnings are returned to the caller even when admission is denied, so for cross-namespace secretRefs that response difference remained an existence oracle: a user with only Consumer create/update permission could probe arbitrary namespace/name pairs and learn which Secrets exist in namespaces they cannot read. Gate the warning pass on a ReferenceGrant: when no grant permits the reference, emit a uniform "not accessible without a ReferenceGrant" warning and skip the existence check entirely, so presence and absence are indistinguishable. Same-namespace and grant-permitted references keep the normal not-found warning. Extract the Consumer -> Secret grant check added in #432 into a shared CheckConsumerSecretRef helper so the reconciler and the webhook use one implementation instead of two copies. Signed-off-by: Abhishek Choudhary <shreemaan.abhishek@gmail.com>
40fb67e to
b41c436
Compare
Fixes Sec Review FINDING-051 (api7/rfcs#185).
Problem
The
Consumervalidating webhook checks referenced Secrets and emits aReferenced Secret '<ns>/<name>' not foundwarning only when the Secret is absent. A resolvable Secret produces no warning.For cross-namespace
secretRefs (credential.secretRef.namespaceis caller-settable), that presence/absence difference is an existence oracle: a user with onlyConsumercreate/update permission can point a credential attarget-ns/guessed-name, observe whether the admission warning appears, and enumerate which Secrets exist in namespaces they cannot read, using the controller's cluster-wide Secret read privilege. Same missing-ReferenceGrant root cause as FINDING-004/006.Fix
Gate the cross-namespace lookup on a
ReferenceGrant(reusingcheckReferenceGrant, as the route controllers already do for cross-namespace backend refs):Referenced Secret '<ns>/<name>' is not accessible from this Consumer without a ReferenceGrantwarning and skip the existence check entirely, so found and not-found are indistinguishable.Tests
TestConsumerValidator_CrossNamespaceSecretOracleSuppressedasserts identical output whether or not the cross-namespace Secret exists (no oracle).TestConsumerValidator_CrossNamespaceSecretWithGrantasserts aReferenceGrantrestores normal probing.Sync
Paired with the upstream OSS fix apache/apisix-ingress-controller#2806 (identical code); the sync is complete only when both merge.
Summary by CodeRabbit
Bug Fixes
Tests