Skip to content

fix: fail loud when enable-csrf is set but csrf-key annotation is missing - #440

Open
shreemaan-abhishek wants to merge 3 commits into
masterfrom
fix/csrf-fail-loud
Open

fix: fail loud when enable-csrf is set but csrf-key annotation is missing#440
shreemaan-abhishek wants to merge 3 commits into
masterfrom
fix/csrf-fail-loud

Conversation

@shreemaan-abhishek

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

Copy link
Copy Markdown
Contributor

What this PR does

Fixes FINDING-031. When enable-csrf: "true" is set on an Ingress but csrf-key is missing or empty, the csrf plugin was silently dropped: the route was programmed without CSRF protection and the Ingress reconciled cleanly, so an endpoint the operator believed was protected ran unprotected with no error surfaced.

Approach (changed after review)

Enforce the rule in the Ingress validating admission webhook (ingress_webhook.go), for both ValidateCreate and ValidateUpdate. enable-csrf: true with an empty/missing csrf-key is now a hard error, so kubectl apply is rejected loudly and synchronously, before the object is ever stored, for both new and updated Ingresses.

This replaces the earlier translation-failure approach, per review feedback from @AlinsRan and @nic-6443. That approach was reverted because:

  1. It did not cover the update path. If an Ingress already served a route without CSRF and was then edited to add enable-csrf with no key, Provider.Update returned on the translation error before UpdateConfig, so the old unprotected route stayed live in the data plane (raised by @nic-6443).
  2. It over-reached. An Ingress syncs {Service, SSL} together, so failing the whole translation on any annotation error meant a typo in a non-security annotation (e.g. upstream-scheme) took down the Ingress including its TLS (raised by @AlinsRan).
  3. Dropping a route does not stop traffic anyway: requests fall through to any broader wildcard route, which may have no CSRF (raised by @AlinsRan).

The webhook can enforce severity properly (hard error vs warning) and blocks the bad intent at the source, which the translation layer structurally cannot.

Known limitation

The Ingress webhook is deployed with failurePolicy: Ignore, so if the webhook is unavailable the invalid Ingress is admitted. This is a best-effort gate, not an unbreakable boundary; hardening that bypass is a separate concern (same class as the webhook-availability finding).

Test

Added to ingress_webhook_test.go:

  • enable-csrf without a key, and with an empty key, are rejected on create.
  • the same invalid combination is rejected on update (covers the already-served case).
  • enable-csrf with a key, and csrf-not-enabled, are allowed.
go test ./internal/webhook/... ./internal/adc/...

Synced with apache/apisix-ingress-controller#2813.

Summary by CodeRabbit

  • Bug Fixes
    • Ingress configurations that enable CSRF must now include a non-empty CSRF key.
    • Invalid CSRF annotation combinations are rejected during both create and update.
    • Configurations without CSRF enabled remain valid even if the CSRF key is absent.
  • Tests
    • Added and expanded coverage for CSRF annotation validation, including missing-key and empty-key failure cases, plus the valid case.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b20bd073-94ec-406b-af3a-0b444fa13c5f

📥 Commits

Reviewing files that changed from the base of the PR and between df885cd and 167aed9.

📒 Files selected for processing (1)
  • internal/webhook/v1/ingress_webhook.go

📝 Walkthrough

Walkthrough

Ingress webhook create and update validation now reject enabled CSRF without a non-empty key. The CSRF translator plugin applies the same error behavior, with tests covering missing, empty, valid, and disabled-CSRF configurations.

Changes

Ingress CSRF validation

Layer / File(s) Summary
Webhook annotation validation and coverage
internal/webhook/v1/ingress_webhook.go, internal/webhook/v1/ingress_webhook_test.go
Create and update validation invoke shared CSRF annotation checks before conflict detection and reference-warning collection. Tests cover missing or empty keys, valid keys, and disabled CSRF.
CSRF plugin error handling and coverage
internal/adc/translator/annotations/plugins/csrf.go, internal/adc/translator/annotations/plugins/csrf_test.go
The CSRF handler returns an error with nil output when the enabled feature lacks a non-empty key, with tests for missing and empty key annotations.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
E2e Test Quality Review ⚠️ Warning Unit-only fake-client tests were added; they never exercise the admission API or dataplane, and the real ingress E2E suite wasn’t updated. Add an ingress E2E case that creates/updates a CSRF-ingress without csrf-key, asserts admission rejection, and verifies no route is programmed.
✅ 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 matches the main change: rejecting enable-csrf when csrf-key is missing.
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 security issues found: the new webhook rejects invalid CSRF annotations, and the code doesn't log or persist secret material.
✨ 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 fix/csrf-fail-loud

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

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

conformance test report - apisix mode

apiVersion: gateway.networking.k8s.io/v1
date: "2026-07-30T07:11:39Z"
gatewayAPIChannel: experimental
gatewayAPIVersion: v1.6.0
implementation:
  contact:
  - https://github.com/apache/apisix-ingress-controller/issues
  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
    - HTTPRouteInvalidBackendRefUnknownKind
    - HTTPRouteInvalidCrossNamespaceBackendRef
    - HTTPRouteInvalidNonExistentBackendRef
    - HTTPRouteListenerHostnameMatching
    - HTTPRouteMultipleGateways
    - HTTPRouteNoBackendRefs
    statistics:
      Failed: 0
      Passed: 30
      Skipped: 7
  extended:
    result: partial
    skippedTests:
    - HTTPRouteRedirectPortAndScheme
    statistics:
      Failed: 0
      Passed: 12
      Skipped: 1
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    - HTTPRouteBackendProtocolWebSocket
    - HTTPRouteDestinationPortMatching
    - HTTPRouteHostRewrite
    - HTTPRouteMethodMatching
    - HTTPRoutePathRewrite
    - HTTPRoutePortRedirect
    - HTTPRouteQueryParamMatching
    - HTTPRouteRequestMirror
    - HTTPRouteResponseHeaderModification
    - HTTPRouteSchemeRedirect
    unsupportedFeatures:
    - BackendTLSPolicy
    - BackendTLSPolicySANValidation
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - HTTPRoute303RedirectStatusCode
    - HTTPRoute307RedirectStatusCode
    - HTTPRoute308RedirectStatusCode
    - HTTPRouteBackendProtocolH2C
    - HTTPRouteBackendRequestHeaderModification
    - HTTPRouteBackendTimeout
    - HTTPRouteCORS
    - HTTPRouteNamedRouteRule
    - HTTPRouteParentRefPort
    - HTTPRoutePathRedirect
    - HTTPRouteRequestMultipleMirrors
    - HTTPRouteRequestPercentageMirror
    - HTTPRouteRequestTimeout
    - HTTPRouteRetry
    - HTTPRouteRetryBackendTimeout
    - HTTPRouteRetryConnectionError
    - ListenerSet
  name: GATEWAY-HTTP
  summary: Core tests partially succeeded with 7 test skips. Extended tests partially
    succeeded with 1 test skips.
- core:
    result: partial
    skippedTests:
    - GRPCRouteListenerHostnameMatching
    statistics:
      Failed: 0
      Passed: 14
      Skipped: 1
  extended:
    result: success
    statistics:
      Failed: 0
      Passed: 1
      Skipped: 0
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    unsupportedFeatures:
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - ListenerSet
  name: GATEWAY-GRPC
  summary: Core tests partially succeeded with 1 test skips. Extended tests succeeded.
- core:
    result: partial
    skippedTests:
    - TLSRouteHostnameIntersection
    - TLSRouteInvalidBackendRefNonexistent
    - TLSRouteInvalidBackendRefUnknownKind
    - TLSRouteSimpleSameNamespace
    statistics:
      Failed: 0
      Passed: 16
      Skipped: 4
  extended:
    result: partial
    skippedTests:
    - TLSRouteTerminateSimpleSameNamespace
    statistics:
      Failed: 0
      Passed: 3
      Skipped: 1
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    - TLSRouteModeTerminate
    unsupportedFeatures:
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - ListenerSet
    - TLSRouteModeMixed
  name: GATEWAY-TLS
  summary: Core tests partially succeeded with 4 test skips. Extended tests partially
    succeeded with 1 test skips.
succeededProvisionalTests:
- GatewayOptionalAddressValue

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

conformance test report - apisix-standalone mode

apiVersion: gateway.networking.k8s.io/v1
date: "2026-07-30T07:11:41Z"
gatewayAPIChannel: experimental
gatewayAPIVersion: v1.6.0
implementation:
  contact:
  - https://github.com/apache/apisix-ingress-controller/issues
  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
    - HTTPRouteInvalidBackendRefUnknownKind
    - HTTPRouteInvalidCrossNamespaceBackendRef
    - HTTPRouteInvalidNonExistentBackendRef
    - HTTPRouteListenerHostnameMatching
    - HTTPRouteMultipleGateways
    - HTTPRouteNoBackendRefs
    statistics:
      Failed: 0
      Passed: 30
      Skipped: 7
  extended:
    result: partial
    skippedTests:
    - HTTPRouteRedirectPortAndScheme
    statistics:
      Failed: 0
      Passed: 12
      Skipped: 1
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    - HTTPRouteBackendProtocolWebSocket
    - HTTPRouteDestinationPortMatching
    - HTTPRouteHostRewrite
    - HTTPRouteMethodMatching
    - HTTPRoutePathRewrite
    - HTTPRoutePortRedirect
    - HTTPRouteQueryParamMatching
    - HTTPRouteRequestMirror
    - HTTPRouteResponseHeaderModification
    - HTTPRouteSchemeRedirect
    unsupportedFeatures:
    - BackendTLSPolicy
    - BackendTLSPolicySANValidation
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - HTTPRoute303RedirectStatusCode
    - HTTPRoute307RedirectStatusCode
    - HTTPRoute308RedirectStatusCode
    - HTTPRouteBackendProtocolH2C
    - HTTPRouteBackendRequestHeaderModification
    - HTTPRouteBackendTimeout
    - HTTPRouteCORS
    - HTTPRouteNamedRouteRule
    - HTTPRouteParentRefPort
    - HTTPRoutePathRedirect
    - HTTPRouteRequestMultipleMirrors
    - HTTPRouteRequestPercentageMirror
    - HTTPRouteRequestTimeout
    - HTTPRouteRetry
    - HTTPRouteRetryBackendTimeout
    - HTTPRouteRetryConnectionError
    - ListenerSet
  name: GATEWAY-HTTP
  summary: Core tests partially succeeded with 7 test skips. Extended tests partially
    succeeded with 1 test skips.
- core:
    result: partial
    skippedTests:
    - GRPCRouteListenerHostnameMatching
    statistics:
      Failed: 0
      Passed: 14
      Skipped: 1
  extended:
    result: success
    statistics:
      Failed: 0
      Passed: 1
      Skipped: 0
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    unsupportedFeatures:
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - ListenerSet
  name: GATEWAY-GRPC
  summary: Core tests partially succeeded with 1 test skips. Extended tests succeeded.
- core:
    result: partial
    skippedTests:
    - TLSRouteHostnameIntersection
    - TLSRouteInvalidBackendRefNonexistent
    - TLSRouteInvalidBackendRefUnknownKind
    - TLSRouteSimpleSameNamespace
    statistics:
      Failed: 0
      Passed: 16
      Skipped: 4
  extended:
    result: partial
    skippedTests:
    - TLSRouteTerminateSimpleSameNamespace
    statistics:
      Failed: 0
      Passed: 3
      Skipped: 1
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    - TLSRouteModeTerminate
    unsupportedFeatures:
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - ListenerSet
    - TLSRouteModeMixed
  name: GATEWAY-TLS
  summary: Core tests partially succeeded with 4 test skips. Extended tests partially
    succeeded with 1 test skips.
succeededProvisionalTests:
- GatewayOptionalAddressValue

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

conformance test report

apiVersion: gateway.networking.k8s.io/v1
date: "2026-07-30T07:31:35Z"
gatewayAPIChannel: experimental
gatewayAPIVersion: v1.6.0
implementation:
  contact:
  - https://github.com/apache/apisix-ingress-controller/issues
  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
    - HTTPRouteInvalidBackendRefUnknownKind
    - HTTPRouteMultipleGateways
    - HTTPRouteNoBackendRefs
    result: failure
    skippedTests:
    - HTTPRouteHTTPSListener
    statistics:
      Failed: 4
      Passed: 32
      Skipped: 1
  extended:
    result: partial
    skippedTests:
    - HTTPRouteRedirectPortAndScheme
    statistics:
      Failed: 0
      Passed: 12
      Skipped: 1
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    - HTTPRouteBackendProtocolWebSocket
    - HTTPRouteDestinationPortMatching
    - HTTPRouteHostRewrite
    - HTTPRouteMethodMatching
    - HTTPRoutePathRewrite
    - HTTPRoutePortRedirect
    - HTTPRouteQueryParamMatching
    - HTTPRouteRequestMirror
    - HTTPRouteResponseHeaderModification
    - HTTPRouteSchemeRedirect
    unsupportedFeatures:
    - BackendTLSPolicy
    - BackendTLSPolicySANValidation
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - HTTPRoute303RedirectStatusCode
    - HTTPRoute307RedirectStatusCode
    - HTTPRoute308RedirectStatusCode
    - HTTPRouteBackendProtocolH2C
    - HTTPRouteBackendRequestHeaderModification
    - HTTPRouteBackendTimeout
    - HTTPRouteCORS
    - HTTPRouteNamedRouteRule
    - HTTPRouteParentRefPort
    - HTTPRoutePathRedirect
    - HTTPRouteRequestMultipleMirrors
    - HTTPRouteRequestPercentageMirror
    - HTTPRouteRequestTimeout
    - HTTPRouteRetry
    - HTTPRouteRetryBackendTimeout
    - HTTPRouteRetryConnectionError
    - ListenerSet
  name: GATEWAY-HTTP
  summary: Core tests failed with 4 test failures. Extended tests partially succeeded
    with 1 test skips.
- core:
    failedTests:
    - GatewayModifyListeners
    result: failure
    statistics:
      Failed: 1
      Passed: 14
      Skipped: 0
  extended:
    result: success
    statistics:
      Failed: 0
      Passed: 1
      Skipped: 0
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    unsupportedFeatures:
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - ListenerSet
  name: GATEWAY-GRPC
  summary: Core tests failed with 1 test failures. Extended tests succeeded.
- core:
    failedTests:
    - GatewayModifyListeners
    - TLSRouteHostnameIntersection
    - TLSRouteInvalidBackendRefNonexistent
    - TLSRouteInvalidBackendRefUnknownKind
    - TLSRouteSimpleSameNamespace
    result: failure
    statistics:
      Failed: 5
      Passed: 15
      Skipped: 0
  extended:
    failedTests:
    - TLSRouteTerminateSimpleSameNamespace
    result: failure
    statistics:
      Failed: 1
      Passed: 3
      Skipped: 0
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    - TLSRouteModeTerminate
    unsupportedFeatures:
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - ListenerSet
    - TLSRouteModeMixed
  name: GATEWAY-TLS
  summary: Core tests failed with 5 test failures. Extended tests failed with 1 test
    failures.
succeededProvisionalTests:
- GatewayOptionalAddressValue

@shreemaan-abhishek shreemaan-abhishek self-assigned this Jul 20, 2026
if key == "" {
return nil, nil
// csrf requested but the key is missing: fail loud instead of
// silently dropping the plugin and programming the route unprotected.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This still fails open at the route level. plugins.Parse logs handler errors and continues, then returns a nil error, so this error never reaches reconciliation and the route is still programmed without the csrf plugin. The linked finding explicitly requires translation to fail rather than only adding a log entry. Please propagate the handler error from plugins.Parse and cover the parser/reconcile path so enable-csrf=true cannot reconcile successfully without a key.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You are right, and I have pushed the full fix.

Confirming your diagnosis: the error was swallowed and never reached reconciliation. I traced it and there were three swallow points, not one:

  1. plugins.Parse - logged the handler error, continue, returned a nil error.
  2. TranslateIngressAnnotations - logged the parser error and returned the config anyway.
  3. TranslateIngress - had no error to inspect, since the function above returned only a config.

One note on scope, because the diff is wider than the single change you asked for. Propagating from plugins.Parse alone would have made things worse: on error Parse returns nil for the entire plugin map, which also holds key-auth, basic-auth, ip-restriction and forward-auth. With points 2 and 3 still swallowing, the route would still have been programmed, now stripped of authentication and IP restrictions as well. A one-plugin gap would have become an all-plugins gap. So points 2 and 3 had to move together with point 1 for point 1 to be safe.

With all three propagating, TranslateIngress now returns the error, and the provider aborts at provider.go:154 before the sync task is built, so the route is never programmed and the failure surfaces on the reconcile.

Two things worth flagging for your review:

  • Behavior change beyond csrf. Any annotation handler error now fails translation. That includes the upstream parser, so an invalid upstream-scheme/retry/timeout annotation now fails the Ingress instead of being silently ignored. I think this is correct and the same bug class (a typo'd https silently fell back to plaintext http), but it will turn previously-"working" typo'd Ingresses red on upgrade. Happy to narrow it to plugin handlers only if you would rather not carry that.
  • I updated the existing invalid scheme case in TestTranslateIngressAnnotations to expect an error for that reason, and added parser-level cases covering both missing and empty csrf-key.

Full unit suite passes. The same change is in the upstream PR (apache/apisix-ingress-controller#2813).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, the error now reaches reconciliation, but the update path still fails open. If an existing Ingress has already programmed a route without CSRF and is then updated with enable-csrf: "true" but no key, Provider.Update returns on this error before calling UpdateConfig; it does not delete or replace the previous resources, so the old unprotected route remains in the ADC cache and data plane. The translator-only tests cannot catch this. Please reject the invalid update in admission or program a fail-closed replacement, and cover an update from an already-served route.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@jarvis9443 you are right about the update path, thanks. Confirmed Provider.Update returns on the translation error before UpdateConfig, so the previously-programmed unprotected route survived in the ADC cache and data plane, and translator-only tests could not catch it.

I have moved enforcement to the Ingress admission webhook (ValidateCreate + ValidateUpdate): enable-csrf: true with no key is now rejected at apply time for updates as well as creates, so the already-served case is covered. The translation-failure change is reverted. Added a webhook test that exercises an update from a route with no csrf annotations into the invalid enable-csrf-without-key state.

Note the Ingress webhook is deployed with failurePolicy: Ignore, so this is a best-effort gate; hardening that bypass is out of scope here. Same change is in the upstream PR apache/apisix-ingress-controller#2813.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in df885cd. csrf.Handle now returns an error when the key is missing, so plugins.Parse logs a breadcrumb for the cases the webhook cannot cover (webhook off, or a pre-upgrade Ingress). Parse skips only the csrf plugin, so the rest of the route is untouched. The webhook stays the enforcement point. Mirrors apache/apisix-ingress-controller#2813.

…hook

FINDING-031: when enable-csrf is set but csrf-key is missing or empty,
the csrf plugin was silently dropped and the route programmed without
CSRF protection while the Ingress reconciled cleanly.

Enforce this in the Ingress validating webhook (create and update) so
the invalid combination is rejected at kubectl apply, loudly and
synchronously, for both new and updated Ingresses.

This replaces the earlier translation-failure approach, which:
- did not cover the update path (an already-served unprotected route
  survived, since Provider.Update returns before UpdateConfig);
- over-reached: any annotation error, e.g. a typo'd upstream-scheme,
  failed the whole Ingress including its TLS;
- could not truly fail closed anyway, since dropping a route lets
  traffic fall through to any broader wildcard route.

The webhook is deployed with failurePolicy: Ignore, so this is a
best-effort gate; hardening that bypass is tracked separately.
Comment thread internal/webhook/v1/ingress_webhook.go Outdated
return nil, fmt.Errorf("%s", sslvalidator.FormatConflicts(conflicts))
}

if err := validateAnnotations(ingress); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] This guard only runs when the admission webhook is enabled, but the shipped configuration defaults webhook.enable to false. In default deployments, ValidateCreate and ValidateUpdate never run and the translator still silently drops csrf, so both create and update remain unprotected. failurePolicy: Ignore is not the only bypass. Please enforce this invariant in a path that always runs, with fail-closed behavior for updates, or make admission mandatory for this feature.

The webhook is the enforcement point, but it cannot cover every case:
webhook disabled, or an Ingress created before the upgrade that never
gets re-admitted. Return an error from csrf.Handle so plugins.Parse
emits a log line for those cases instead of dropping the plugin in
total silence. Parse skips only the csrf plugin, so the rest of the
route is unaffected.
validateAnnotations is a pure in-memory check, so run it before
DetectConflicts, which does List calls and Secret reads. Reject an
invalid annotation combination without the network round-trips.
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.

3 participants