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
…hook Fixes Sec Review FINDING-051 (api7/rfcs#185). The Consumer validating webhook emitted a "Referenced Secret not found" warning only when a referenced Secret was absent; a resolvable Secret produced no warning. For cross-namespace secretRefs this response difference let a user with only Consumer create/update permission probe arbitrary namespace/name pairs and learn which Secrets exist in namespaces they cannot read, using the controller's cluster-wide Secret read privilege. Gate the cross-namespace lookup 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. Signed-off-by: Abhishek Choudhary <shreemaan.abhishek@gmail.com>
📝 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 docstrings
🧪 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-17T09:34:31Z"
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:
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. |
conformance test report - apisix modeapiVersion: gateway.networking.k8s.io/v1
date: "2026-07-17T09:35: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: 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.
- 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. |
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. |
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