Skip to content

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

Open
shreemaan-abhishek wants to merge 2 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 2 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

When the enable-csrf: "true" annotation is set but csrf-key is missing or empty, csrf.Handle returned (nil, nil) - indistinguishable from the annotation being absent. The route was programmed without the csrf plugin, the Ingress reconciled cleanly, and an endpoint the operator believed was CSRF-protected ran unprotected.

Fix

Returning an error from csrf.Handle alone was not enough, because the error was swallowed at three separate points:

  1. plugins.Parse logged the handler error, continued, and 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.

All three now propagate. TranslateIngress 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 rather than in a log line.

Fixing only point 1 would have been worse than the bug: 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 ship, now stripped of authentication too.

The csrf error message does not echo the key value.

Behavior change worth reviewing

Any annotation handler error now fails translation, not just csrf. That includes the upstream parser, so an invalid upstream-scheme/retry/timeout annotation now fails the Ingress instead of being silently ignored. This is 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. Can be narrowed to plugin handlers only if preferred.

Test

  • TestCSRFHandler: missing-key and empty-key now return an error.
  • TestTranslateIngressAnnotations: added parser-level cases for both, proving translation fails end to end. Updated the existing invalid scheme case to expect an error.
go test ./internal/...

Fixes FINDING-031 (api7/rfcs#165). Synced with apache/apisix-ingress-controller#2813.

Summary by CodeRabbit

  • Bug Fixes
    • CSRF configuration now fails with a clear error when CSRF protection is enabled but the CSRF key is missing or empty.
    • Annotation translation and plugin handling now fail closed: translation aborts and returns an error on invalid/failed annotation processing instead of logging and continuing with incomplete results.

Previously, when the enable-csrf annotation was set to true but the
csrf-key annotation was missing or empty, csrf.Handle returned
(nil, nil), which is indistinguishable from the annotation being
absent. The route was then programmed without the csrf plugin and the
Ingress reconciled cleanly, so an endpoint the operator believed was
protected ran unprotected with no error, event, or log.

Return an error in this case so the parser surfaces it instead of
silently dropping the security-relevant plugin. The error message does
not echo the key value.
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Annotation error handling

Layer / File(s) Summary
Fail-loud CSRF key validation
internal/adc/translator/annotations/plugins/csrf.go, internal/adc/translator/annotations/plugins/csrf_test.go
CSRF handling now returns an error when enabled without a non-empty key, with tests covering both missing and empty annotations.
Translation failure propagation
internal/adc/translator/annotations/plugins/plugins.go, internal/adc/translator/annotations.go, internal/adc/translator/ingress.go, internal/adc/translator/annotations_test.go
Plugin and ingress annotation translation now returns handler and parser errors immediately, with tests asserting nil results for failure cases.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

  • api7/rfcs#165: Missing or empty csrf-key annotations now fail translation instead of silently omitting CSRF configuration.

Possibly related PRs

  • api7/api7-ingress-controller#441: Both changes propagate invalid plugin configuration errors instead of continuing with empty or partial plugin configurations.
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
E2e Test Quality Review ⚠️ Warning Tests are unit-level only; the fail-closed CSRF flow isn’t exercised end-to-end. Existing e2e CSRF coverage only verifies the happy path. Add an e2e case with enable-csrf=true and missing/empty csrf-key, then verify reconciliation fails and no CSRF route is programmed.
✅ Passed checks (5 passed)
Check name Status Explanation
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 new sensitive-data, auth, or crypto issues in the changed code; CSRF failures are fail-closed and error messages omit key values.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: CSRF now fails loudly when enabled without a csrf-key.
✨ Finishing Touches
🧪 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-24T10:27:11Z"
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.

@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-24T10:27:13Z"
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.

@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-24T10:44: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
    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.

@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).

Returning an error from csrf.Handle was not enough to stop an
unprotected route from being programmed: the error was swallowed at
three separate points.

  1. plugins.Parse logged the handler error and continued, returning 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.

Propagate the error through all three so TranslateIngress fails. The
provider aborts before building the sync task, so the route is no
longer programmed and the failure surfaces on the reconcile instead of
in a log line nobody reads.

This also stops an invalid upstream-scheme/retry/timeout annotation
from being silently ignored, which had the same silent-downgrade shape
(a typo'd "https" fell back to plaintext http).
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.

2 participants