Skip to content

fix: flag inverted TrafficProtectionPolicy paranoia levels via status condition#252

Merged
ecv merged 1 commit into
mainfrom
fix/250-paranoia-status-condition
Jul 13, 2026
Merged

fix: flag inverted TrafficProtectionPolicy paranoia levels via status condition#252
ecv merged 1 commit into
mainfrom
fix/250-paranoia-status-condition

Conversation

@ecv

@ecv ecv commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Addresses #250 via option 3 (controller status condition, defense-in-depth) from the issue's root-cause comment.

A TrafficProtectionPolicy whose OWASP CRS detection paranoia level is below its blocking paranoia level is illegal: CRS rule 901500 fails closed and denies every request at phase 1 with HTTP 500 before any attack evaluation. Such a policy silently 500s 100% of traffic with no signal to the tenant about why.

Change

Reject the policy during reconciliation:

  • Set Accepted=False with reason Invalid on the affected ancestor.
  • Skip attachment so the broken directives are never emitted to the data plane.
  • Also surfaces already-persisted policies carrying the inverted configuration.

Test

  • TestParanoiaLevelsResolveError — guard, 4 cases (equal / higher detection / inverted / defaulted inverted).
  • inverted-paranoia case added to both TestProcessTrafficProtectionPolicyForHTTPRoute and TestProcessTrafficProtectionPolicyForGateway, asserting Accepted=False/Invalid and no attachment.

Companion PR

Companion to #251, which implements option 1 (CEL admission validation, the preferred fail-closed fix). CEL only validates on write; this guard catches policies already stored with the bad config and gives tenants an actionable Accepted=False reason. The two are complementary and can merge independently.

A TrafficProtectionPolicy whose OWASP CRS detection paranoia level is
below its blocking paranoia level is an illegal configuration: CRS rule
901500 fails closed and denies every request at phase 1 with HTTP 500
before any attack evaluation. Such a policy silently 500s 100% of traffic
to the protected route with no signal to the tenant about why.

Reject the policy during reconciliation: set Accepted=False with reason
Invalid on the affected ancestor and skip attachment so the broken
directives are never emitted to the data plane. This also surfaces
already-persisted policies that carry the inverted configuration.

Key changes:
- add paranoiaLevelsResolveError guard in the TPP reconciler
- set Accepted=False/Invalid and skip attach for HTTPRoute and Gateway
  targets when detection < blocking
- add unit coverage for the guard and both process paths
@ecv
ecv force-pushed the fix/250-paranoia-status-condition branch from d17ce22 to 1b06e9c Compare July 13, 2026 16:32
@ecv
ecv enabled auto-merge July 13, 2026 16:37
@ecv
ecv merged commit 411d117 into main Jul 13, 2026
10 of 11 checks passed
@ecv
ecv deleted the fix/250-paranoia-status-condition branch July 13, 2026 16:41
ecv added a commit that referenced this pull request Jul 13, 2026
Add a gated e2e case on the downstream WAF data plane exercising a valid
Enforce TrafficProtectionPolicy end to end: a benign GET returns 200 with
the backend body intact, and a CRS-tripping attack payload returns 403 with
no backend leakage. Assertions check body integrity, not status alone, per
datum-cloud/infra#3321.

Neutralization of already-persisted inverted policies is tracked separately
as follow-up to #252, where the controller-side fix and its test belong.
ecv added a commit that referenced this pull request Jul 13, 2026
An inverted-paranoia TrafficProtectionPolicy (OWASP CRS detection below
blocking) makes CRS rule 901500 fail closed and 500 every request. #252
set Accepted=False/Invalid and skipped attachment on the controller's
EnvoyPatchPolicy path, but the live edge enforces coraza through the
extension server, which builds per-route directives in a separate path
(internal/extensionserver/cache) that #252 never touched. A grandfathered
inverted policy therefore still 500'd benign traffic on the downstream
gateway.

Gate the extension server's directive emission on the same inverted-level
check the controller uses, so an inverted policy produces no directives
and the mutation layer withholds it from the data plane. A benign request
is then unaffected instead of 500'ing.

Key changes:
- add shared TrafficProtectionPolicySpec.InvertedParanoiaLevels predicate
  and route both the controller resolve-error guard and the extension
  server emission through it
- return nil directives for an inverted policy in computeCorazaDirectives
  so ApplyTPPRouteConfig skips the route
- add API and emission-layer unit tests: inverted not emitted, valid and
  equal levels emitted
- promote the neutralization e2e into the gating test/e2e suite and drop
  the now-empty test-e2e-nonblocking target

Fixes #273. Refs #252, #242, #268.
ecv added a commit that referenced this pull request Jul 13, 2026
Add a quarantined, expected-red e2e case asserting the controller withholds
an already-persisted inverted-paranoia TrafficProtectionPolicy from the
coraza edge data plane, so benign traffic still serves 200 instead of the
CRS 901500 fail-closed 500.

The test injects a grandfathered inverted policy by bypassing the #251 CEL
admission rule (patch the paranoiaLevels validation off the CRD, create the
policy, restore the rule), then asserts a benign GET returns 200. It fails
until #252's skip-attachment is enforced on the downstream-gateway path, so
it lives under test/e2e-nonblocking and is excluded from the gating test-e2e
run via the separate test-e2e-nonblocking target.

The controller-side fix that makes this pass is to follow on this branch.
ecv added a commit that referenced this pull request Jul 13, 2026
An inverted-paranoia TrafficProtectionPolicy (OWASP CRS detection below
blocking) makes CRS rule 901500 fail closed and 500 every request. #252
set Accepted=False/Invalid and skipped attachment on the controller's
EnvoyPatchPolicy path, but the live edge enforces coraza through the
extension server, which builds per-route directives in a separate path
(internal/extensionserver/cache) that #252 never touched. A grandfathered
inverted policy therefore still 500'd benign traffic on the downstream
gateway.

Gate the extension server's directive emission on the same inverted-level
check the controller uses, so an inverted policy produces no directives
and the mutation layer withholds it from the data plane. A benign request
is then unaffected instead of 500'ing.

Key changes:
- add shared TrafficProtectionPolicySpec.InvertedParanoiaLevels predicate
  and route both the controller resolve-error guard and the extension
  server emission through it
- return nil directives for an inverted policy in computeCorazaDirectives
  so ApplyTPPRouteConfig skips the route
- add API and emission-layer unit tests: inverted not emitted, valid and
  equal levels emitted
- promote the neutralization e2e into the gating test/e2e suite and drop
  the now-empty test-e2e-nonblocking target

Fixes #273. Refs #252, #242, #268.
ecv added a commit that referenced this pull request Jul 13, 2026
…edge

fix: neutralize persisted inverted TPP on the coraza edge path (#252 follow-up)
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