fix: classification banner working for SPAs#2710
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the Istio classification-banner EnvoyFilter to avoid hanging on streamed HTML responses (notably Grafana 13’s streamed shell), standardizes banner font styling, reserves viewport space to prevent overlap in SPAs, and enables/validates the banner in the k3d-standard demo bundle via a new Playwright test.
Changes:
- Update the classification-banner Lua filter to inject safely for both bounded (Content-Length) and streamed (chunked/no Content-Length) HTML responses, and skip non-200 responses.
- Adjust injected banner styling and add client-side logic to reserve space so fixed-position SPA chrome doesn’t overlap the banner.
- Enable the banner on demo bundle hosts (sso/portal/grafana) and add a Playwright test to verify banner injection.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| test/playwright/classification-banner.test.ts | Adds an end-to-end Playwright check that the banner is injected on sso/grafana (and portal when present). |
| src/istio/common/chart/templates/classification-banner.yaml | Updates Envoy Lua injection logic to avoid blocking on streamed responses and improves banner layout/styling behavior. |
| bundles/k3d-standard/uds-bundle.yaml | Enables the banner on selected demo hosts and sets demo text to UNKNOWN. |
Greptile SummaryThis PR fixes the classification banner EnvoyFilter so it no longer hangs the gateway on streamed HTML responses (e.g. Grafana 13) by dispatching to either a buffered path (Content-Length present) or a chunk-by-chunk path (
Confidence Score: 5/5Safe to merge. The buffered and streamed injection paths are logically correct for all valid HTML responses, and the 200-only guard eliminates the original hang on bodyless redirects. All changed paths are well-tested (Playwright covers SSO, Grafana, and portal) and the streaming logic correctly handles cross-chunk tag boundaries. The one edge case — a malformed response that ends mid-tag without ever emitting a body tag — would silently drop a few bytes of already-broken content, but cannot affect valid production responses. No files require special attention; the streaming Lua in classification-banner.yaml is the most complex change but its logic is sound. Important Files Changed
Sequence DiagramsequenceDiagram
participant GW as Envoy Gateway
participant LUA as Lua Filter
participant UP as Upstream App
GW->>UP: Forward request
UP->>GW: Response headers
GW->>LUA: envoy_on_response
LUA->>LUA: "Check status=200, text/html, enabled host"
alt Not a target response
LUA-->>GW: Return - pass through
else Content-Length present
LUA->>LUA: buffer full body
LUA->>LUA: inject_banner on full body
LUA-->>GW: setBytes with banner injected
else No Content-Length - streaming
loop each bodyChunk
LUA->>LUA: inject_banner on pending+chunk
alt Banner found in result
LUA-->>GW: "setBytes - injected=true"
else Trailing partial tag
LUA-->>GW: Emit safe prefix - carry partial in pending
else No partial tag
LUA-->>GW: Emit full chunk - clear pending
end
end
end
Reviews (2): Last reviewed commit: "chore: swap to sample banner" | Re-trigger Greptile |
|
If I'm tracking correctly, some apps that more aggressively rebuild pages (e.g. React, Vue) might appear behind the banner as the
Makes me think as the Classification banner use cases/needs become more complex, maybe adding a playwright test with a more complex or tailored app that offers more coverage in this sense would be helpful if we don't have one already. |
|
Could we change 'UNKNOWN' to either 'SAMPLE BANNER' or 'NOTIONAL'? Seeing 'UNKNOWN' implies that the environment's actual classification hasn't been determined, which can look like a compliance issue. 'SAMPLE BANNER' or 'NOTIONAL' makes it clear that it's a deliberate placeholder for the demo. |
|
@greptileai review |
Description
The classification banner was noticed to block traffic to Grafana in uds-identity-config. See: defenseunicorns/uds-identity-config#870. In our envoy filter
response_handle:body()buffers the entire body and blocks until end-of-stream and on Grafana 13's streamed shell (no Content-Length) this hung.This PR:
NOTE: People will see the SAMPLE BANNER classification banner for demo bundle deploys. If we feel this is weird I would be open to changing this. I also set this to SAMPLE BANNER instead of UNCLASSIFIED as I didn't want to assume classification for where people are deploying the demo bundle locally.
Related Issue
Fixes CORE-590
Type of change
Steps to Validate
uds runand check the banners on portal, sso, and grafanaChecklist before merging