Skip to content

fix(cost-management): enable cluster/project RBAC filtering by registering dynamic permissions with RHDH (FLPATH-4207)#3193

Merged
pkliczewski merged 3 commits into
redhat-developer:mainfrom
hardengl:fix/rbac-cluster-permissions
Jun 3, 2026
Merged

fix(cost-management): enable cluster/project RBAC filtering by registering dynamic permissions with RHDH (FLPATH-4207)#3193
pkliczewski merged 3 commits into
redhat-developer:mainfrom
hardengl:fix/rbac-cluster-permissions

Conversation

@hardengl
Copy link
Copy Markdown
Contributor

@hardengl hardengl commented May 20, 2026

Summary

Cluster-specific RBAC permissions (ros/<cluster>, ros/<cluster>/<project>, and their cost/ equivalents) are created dynamically at runtime by fetchDynamicPermissions() but were never registered with createPermissionIntegrationRouter. RHDH's RBAC backend uses PluginPermissionMetadataCollector to validate permission names before evaluating them against Casbin policies — unregistered permissions default to DENY. This broke the entire 3-tier RBAC model: only ros.plugin (tier 1) ever worked.

Note — community Backstage vs RHDH: The upstream community RBAC backend may evaluate Casbin policies by name matching alone. However, RHDH's distribution (@red-hat-developer-hub/backstage-plugin-rbac-backend) requires permissions to be registered via createPermissionIntegrationRouter for them to be recognized by the authorization flow. This is confirmed by the A/B test evidence below.

Changes

router.ts — At router init, fetch cluster/project data from the upstream Cost Management APIs and register all ros/<cluster>, ros/<cluster>/<project>, cost/<cluster>, and cost/<cluster>/<project> permissions with the integration router. Uses Promise.allSettled so a partial failure (e.g. one API down) doesn't block other permissions from registering.

secureProxy.ts — Improved error logging and response messages. Previously returned opaque "Internal proxy error" for all non-SSO exceptions; now includes the request path and error details for easier debugging.

A/B Test Evidence — Permission Registration

Tested on live RHDH 1.9 cluster (ocp-edge73-0, OCP 4.19). Queried /api/permission/plugins/policies with identical configuration, only swapping the plugin image:

WITHOUT fix (stock 2.2.0): cost-management plugin registers 3 permissionsros.plugin, ros.apply, cost.plugin. Zero dynamic permissions. Any permissionsSvc.authorize() call for ros/ocp-edge73-0-xfvkt returns DENY.

WITH fix (2.2.0-rc.1): cost-management plugin registers 174 permissions — the same 3 static plus 171 dynamic cluster/project permissions across 57 unique clusters. Examples: ros/ocp-edge73-0-xfvkt, ros/ocp-edge73-0-xfvkt/rhdh-operator, ros/ocp-test-mc8h6, etc.

This confirms that createPermissionIntegrationRouter registration is required for RHDH's RBAC to evaluate these permissions. PRs #1996/#2102 correctly implemented the authorization logic (filterAuthorizedClustersAndProjects calling authorize() per cluster), but those calls always returned DENY because the permission names were not registered. This PR completes the circuit.

Proof the fix works

Verified on a live RHDH instance (ocp-edge73) with OCI image quay.io/gharden/cost-management-dynamic-plugins:2.2.0-rc.1.

Tier 1: ros.plugin — full access (396 containers, no filters)

costmgmt-full-access-optimizations

No permissions — access denied

costmgmt-no-access-optimizations

Tier 2: ros/<cluster> only — cluster-filtered (10 containers, down from 396)

ro-cluster-only-optimizations

Tier 3: ros/<cluster>/<project> only — cluster+project filtered

ro-project-only-optimizations

Backend audit logs

costmgmt-full-access: {"decision":"ALLOW","filters":{"clusters":[],"projects":[]}}            ← no filters
ro-cluster-only:      {"decision":"ALLOW","filters":{"clusters":["696794b9-..."],"projects":[]}}  ← cluster filter
ro-project-only:      {"decision":"ALLOW","filters":{"clusters":["696794b9-..."],"projects":["rhdh-operator"]}}
costmgmt-no-access:   {"decision":"DENY"}

CI

flightpath-ros #95 — 84 passed, 2 failed (pre-existing), 4 skipped. 20 of 21 FLPATH-4207 RBAC tests passed; 1 failure ("full-access user should see data across clusters") is under investigation.

Limitation

Dynamic permissions are registered once at startup. New clusters added after RHDH starts won't be recognized until the pod restarts. A periodic refresh mechanism would be a more complete solution.

Related

…roject tiers (FLPATH-4207)

Cluster-specific permissions (ros/<cluster>, ros/<cluster>/<project>) were
created at runtime but never registered with createPermissionIntegrationRouter.
The RHDH RBAC backend only evaluates registered permissions — unregistered ones
get DENY by default, breaking the entire 3-tier RBAC model.

At router init, fetch cluster/project data from upstream APIs and register all
dynamic permissions. Also improve secureProxy.ts error messages (include path
and error details instead of opaque "Internal proxy error").

Co-authored-by: Cursor <cursoragent@cursor.com>
@rhdh-gh-app
Copy link
Copy Markdown

rhdh-gh-app Bot commented May 20, 2026

Changed Packages

Package Name Package Path Changeset Bump Current Version
@red-hat-developer-hub/plugin-cost-management-backend workspaces/cost-management/plugins/cost-management-backend patch v2.2.0

@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

❌ Patch coverage is 60.46512% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.99%. Comparing base (815580b) to head (43b3547).
⚠️ Report is 60 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3193      +/-   ##
==========================================
+ Coverage   60.97%   60.99%   +0.01%     
==========================================
  Files        2098     2098              
  Lines       65140    65181      +41     
  Branches    17029    17031       +2     
==========================================
+ Hits        39721    39756      +35     
- Misses      25180    25190      +10     
+ Partials      239      235       -4     
Flag Coverage Δ *Carryforward flag
adoption-insights 83.58% <ø> (ø) Carriedforward from e0b1627
ai-integrations 70.03% <ø> (ø) Carriedforward from e0b1627
app-defaults 69.60% <ø> (ø) Carriedforward from e0b1627
augment 69.36% <ø> (ø) Carriedforward from e0b1627
bulk-import 72.86% <ø> (ø) Carriedforward from e0b1627
cost-management 17.48% <60.46%> (+0.99%) ⬆️
dcm 32.85% <ø> (ø) Carriedforward from e0b1627
extensions 61.79% <ø> (ø) Carriedforward from e0b1627
global-floating-action-button 74.30% <ø> (ø) Carriedforward from e0b1627
global-header 61.68% <ø> (ø) Carriedforward from e0b1627
homepage 50.95% <ø> (ø) Carriedforward from e0b1627
konflux 91.01% <ø> (ø) Carriedforward from e0b1627
lightspeed 68.33% <ø> (ø) Carriedforward from e0b1627
mcp-integrations 81.59% <ø> (ø) Carriedforward from e0b1627
orchestrator 36.36% <ø> (ø) Carriedforward from e0b1627
quickstart 62.88% <ø> (ø) Carriedforward from e0b1627
sandbox 79.42% <ø> (ø) Carriedforward from e0b1627
scorecard 83.72% <ø> (ø) Carriedforward from e0b1627
theme 64.54% <ø> (ø) Carriedforward from e0b1627
translations 8.49% <ø> (ø) Carriedforward from e0b1627
x2a 78.28% <ø> (ø) Carriedforward from e0b1627

*This pull request uses carry forward flags. Click here to find out more.


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 815580b...43b3547. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- Extract extractStrings() and buildClusterProjectPermissions() helpers
  to flatten nested if/for blocks (SonarCloud cognitive complexity 33→8)
- Add 8 unit tests covering both helpers (fulfilled/rejected/empty/dedup)
- Add changeset for cost-management-backend patch release

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

@asmasarw asmasarw left a comment

Choose a reason for hiding this comment

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

LGTM - ran it locally and it works fine.

Copy link
Copy Markdown
Contributor

@PreetiW PreetiW left a comment

Choose a reason for hiding this comment

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

The RBAC changes here aren't working as expected — registering permissions in createPermissionIntegrationRouter only affects the metadata endpoint, not the authorization flow. Please refer to the Backstage Permission concepts and PRs #1996 / #2102 for the correct approach.

@hardengl hardengl changed the title fix(cost-management): register dynamic RBAC permissions for cluster/project tiers (FLPATH-4207) fix(cost-management): enable cluster/project RBAC filtering by registering dynamic permissions with RHDH (FLPATH-4207) May 26, 2026
Copy link
Copy Markdown
Contributor

@PreetiW PreetiW left a comment

Choose a reason for hiding this comment

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

Hello @hardengl as discussed previously the RBAC changes were not working on my end due to spelling mistake in RBAC policy and URL in app-config. After fixing those everything works 💯

If you can add note for limit:1000 for

      options.costManagementApi
        .searchOpenShiftClusters('', { token, limit: 1000 })
        .then(r => r.json()),
      options.costManagementApi
        .searchOpenShiftProjects('', { token, limit: 1000 })
        .then(r => r.json()),

and let me know when the Clusters and project data will be refreshed for cost and optimization page which is used for RBAC that will be great 🙌🏻

Rest everything looks good 👍🏻

Copy link
Copy Markdown
Contributor

@PreetiW PreetiW left a comment

Choose a reason for hiding this comment

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

Left a comment based on our discussion.

Clarifies that cluster/project data for RBAC permission registration
is fetched once at plugin startup. New clusters or projects added
afterwards require a Backstage restart to be recognised.

Requested-by: PreetiW
Co-authored-by: Cursor <cursoragent@cursor.com>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 3, 2026

@hardengl
Copy link
Copy Markdown
Contributor Author

hardengl commented Jun 3, 2026

@PreetiW added the comment you suggested

Copy link
Copy Markdown
Contributor

@PreetiW PreetiW left a comment

Choose a reason for hiding this comment

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

Thank you for making the changes @hardengl PR looks good to me 🚀

@pkliczewski pkliczewski merged commit f32b8a8 into redhat-developer:main Jun 3, 2026
73 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants