Skip to content

fix: suppress cross-namespace Secret existence oracle in Consumer webhook#433

Open
shreemaan-abhishek wants to merge 1 commit into
masterfrom
sec/finding-051-secret-oracle
Open

fix: suppress cross-namespace Secret existence oracle in Consumer webhook#433
shreemaan-abhishek wants to merge 1 commit into
masterfrom
sec/finding-051-secret-oracle

Conversation

@shreemaan-abhishek

@shreemaan-abhishek shreemaan-abhishek commented Jul 17, 2026

Copy link
Copy Markdown

Fixes Sec Review FINDING-051 (api7/rfcs#185).

Problem

The Consumer validating webhook checks referenced Secrets and emits a Referenced Secret '<ns>/<name>' not found warning only when the Secret is absent. A resolvable Secret produces no warning.

For cross-namespace secretRefs (credential.secretRef.namespace is caller-settable), that presence/absence difference is an existence oracle: a user with only Consumer create/update permission can point a credential at target-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 (reusing checkReferenceGrant, as the route controllers already do for cross-namespace backend refs):

  • No permitting grant → emit a uniform Referenced Secret '<ns>/<name>' is not accessible from this Consumer without a ReferenceGrant warning and skip the existence check entirely, so found and not-found are indistinguishable.
  • Same-namespace or grant-permitted → unchanged; the normal not-found warning still fires.

Tests

  • TestConsumerValidator_CrossNamespaceSecretOracleSuppressed asserts identical output whether or not the cross-namespace Secret exists (no oracle).
  • TestConsumerValidator_CrossNamespaceSecretWithGrant asserts a ReferenceGrant restores normal probing.
  • Existing webhook tests updated for the new behavior.

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

    • Improved validation of credentials that reference Secrets in another namespace.
    • Prevented validation warnings from revealing whether an inaccessible Secret exists.
    • Cross-namespace Secrets now produce a consistent access warning unless authorized by a ReferenceGrant.
    • Authorized references continue to validate normally, including appropriate warnings when a permitted Secret is missing.
  • Tests

    • Added coverage for cross-namespace access restrictions and ReferenceGrant behavior.

…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>
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Consumer Secret access validation

Layer / File(s) Summary
ReferenceGrant access gate
internal/controller/utils.go, internal/webhook/v1/consumer_webhook.go
Adds CheckConsumerSecretRef and prevents denied cross-namespace Secret lookups, emitting a uniform accessibility warning.
Cross-namespace validation coverage
internal/webhook/v1/consumer_webhook_test.go
Adds tests for suppressed Secret existence warnings, permitted references, missing granted Secrets, and same-namespace Secret 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
Loading

Possibly related PRs

Suggested reviewers: alinsran

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
E2e Test Quality Review ⚠️ Warning The added coverage is unit/fake-client only, not true E2E, and CheckConsumerSecretRef still swallows ReferenceGrant list errors. Add real webhook E2E coverage for cross-namespace SecretRef/ReferenceGrant behavior, and handle/propgate list failures instead of silently denying.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main change: suppressing cross-namespace Secret existence disclosure in the Consumer webhook.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Check ✅ Passed No sensitive-data, authz, ownership, TLS, or secret-storage issues found in the changed paths; warnings only expose secret names, not values.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sec/finding-051-secret-oracle

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5f84a00 and 40fb67e.

📒 Files selected for processing (3)
  • internal/controller/utils.go
  • internal/webhook/v1/consumer_webhook.go
  • internal/webhook/v1/consumer_webhook_test.go

Comment on lines +1350 to +1364
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),
},
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 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

Comment on lines +96 to +140
// 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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.

@github-actions

Copy link
Copy Markdown
Contributor

conformance test report - apisix-standalone mode

apiVersion: 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.

@github-actions

Copy link
Copy Markdown
Contributor

conformance test report - apisix mode

apiVersion: 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.

@github-actions

Copy link
Copy Markdown
Contributor

conformance test report

apiVersion: 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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant