From fe5e77665d8a4c829bf9f05413e754a702b18c71 Mon Sep 17 00:00:00 2001 From: Preeti Wadhwani Date: Thu, 2 Apr 2026 14:42:32 +0530 Subject: [PATCH 1/2] docs(cost-management): update documentation to reflect security PRs #2616-#2620 - Remove outdated proxy config from workspace README (PR #2616 moved data fetching server-side) - Fix broken link typo in frontend plugin README - Update backend README: clarify mixed dot/slash permission format, add missing endpoints (access, apply-recommendation), add audit logging section (PR #2619) - Clarify permission name format in docs/rbac.md intro (dot for plugin-level, slash for cluster/project per PR #2620) - Update ADR 0002 status to Accepted with implementation notes documenting the backend gateway pattern (PRs #2616, #2618, #2619) Made-with: Cursor --- workspaces/cost-management/README.md | 8 ----- ...mizations-using-the-orchestrator-plugin.md | 22 +++++++++++- workspaces/cost-management/docs/rbac.md | 8 ++--- .../plugins/cost-management-backend/README.md | 36 ++++++++++++++++--- .../plugins/cost-management/README.md | 2 +- 5 files changed, 57 insertions(+), 19 deletions(-) diff --git a/workspaces/cost-management/README.md b/workspaces/cost-management/README.md index c1d22186f92..0bb0a245201 100644 --- a/workspaces/cost-management/README.md +++ b/workspaces/cost-management/README.md @@ -72,14 +72,6 @@ This method requires vanilla backstage to be used: ```yaml # app-config.yaml - proxy: - endpoints: - '/cost-management/v1': - target: https://console.redhat.com/api/cost-management/v1 - allowedHeaders: ['Authorization'] - # See: https://backstage.io/docs/releases/v1.28.0/#breaking-proxy-backend-plugin-protected-by-default - credentials: dangerously-allow-unauthenticated - # Replace `${RHHCC_SA_CLIENT_ID}` and `${RHHCC_SA_CLIENT_SECRET}` with the service account credentials. costManagement: clientId: ${RHHCC_SA_CLIENT_ID} diff --git a/workspaces/cost-management/docs/adrs/0002-applying-optimizations-using-the-orchestrator-plugin.md b/workspaces/cost-management/docs/adrs/0002-applying-optimizations-using-the-orchestrator-plugin.md index c5139610c7b..d97fced60a7 100644 --- a/workspaces/cost-management/docs/adrs/0002-applying-optimizations-using-the-orchestrator-plugin.md +++ b/workspaces/cost-management/docs/adrs/0002-applying-optimizations-using-the-orchestrator-plugin.md @@ -2,7 +2,27 @@ ## Status -Proposed +Accepted + +### Implementation Notes (PRs #2616, #2618, #2619) + +The core decision to use the Orchestrator plugin was implemented as proposed. +However, based on a security threat model review (FLPATH-3503), the architecture +was hardened beyond the original design: + +- **Backend gateway pattern**: The frontend no longer calls the Orchestrator API + directly. Instead, requests route through the cost-management backend + (`POST /api/cost-management/apply-recommendation`), which validates inputs, + checks `ros.apply` permission, and forwards to Orchestrator using + service-to-service authentication. +- **Input validation**: `resourceType` is validated against a server-side + allowlist; all input fields are sanitized. +- **Audit logging**: Every apply action is logged with user identity, cluster, + namespace, workload, and outcome. +- **Confirmation dialog**: The UI presents a confirmation step before applying. + +The architecture diagram below reflects the original proposed flow. +See [docs/rbac.md](../rbac.md) for current RBAC details. ## Context diff --git a/workspaces/cost-management/docs/rbac.md b/workspaces/cost-management/docs/rbac.md index 829416e6c1d..71c721358ae 100644 --- a/workspaces/cost-management/docs/rbac.md +++ b/workspaces/cost-management/docs/rbac.md @@ -2,15 +2,15 @@ The Cost Management plugin protects its backend endpoints with the builtin permi The Cost Management plugin consists of two main sections, each with its own set of permissions: -- **Optimizations**: Uses permissions starting with `ros.` -- **OpenShift**: Uses permissions starting with `cost.` +- **Optimizations**: Uses `ros.plugin` and `ros.apply` (dot) for plugin-level access, and `ros/CLUSTER` / `ros/CLUSTER/PROJECT` (slash) for cluster/project-level access +- **OpenShift**: Uses `cost.plugin` (dot) for plugin-level access, and `cost/CLUSTER` / `cost/CLUSTER/PROJECT` (slash) for cluster/project-level access ### How it works When a frontend request arrives at `/api/cost-management/proxy/*`, the backend: 1. Authenticates the user via Backstage `httpAuth` -2. Evaluates the user's permissions against the `ros.*` or `cost.*` policy +2. Evaluates the user's permissions against the `ros.*` / `ros/…` or `cost.*` / `cost/…` policies 3. Determines the authorized list of clusters and projects 4. Strips any client-supplied cluster/project filter parameters 5. Injects the server-authorized filters before forwarding to the upstream API @@ -62,7 +62,7 @@ The user is permitted to do an action if either the generic permission or the sp To get started with policies, we recommend defining roles and assigning them to groups or users in a dedicated CSV file. -Here is an example policy file that includes permissions for both Optimizations (`ros.`) and OpenShift (`cost.`) sections of the Cost Management plugin: +Here is an example policy file that includes permissions for both Optimizations (`ros.plugin`, `ros.apply`, `ros/…`) and OpenShift (`cost.plugin`, `cost/…`) sections of the Cost Management plugin: ```csv #### diff --git a/workspaces/cost-management/plugins/cost-management-backend/README.md b/workspaces/cost-management/plugins/cost-management-backend/README.md index 611309a0366..64bbb9329f3 100644 --- a/workspaces/cost-management/plugins/cost-management-backend/README.md +++ b/workspaces/cost-management/plugins/cost-management-backend/README.md @@ -12,7 +12,7 @@ When a request arrives the handler: 1. **Authenticates** the caller via Backstage `httpAuth` (requires a valid user session). 2. **Checks permissions** through the Backstage permission framework using the - `ros.*` and `cost.*` permission sets (see [docs/rbac.md](../../docs/rbac.md)). + `ros.*` / `ros/…` and `cost.*` / `cost/…` permission sets (see [docs/rbac.md](../../docs/rbac.md)). 3. **Obtains an SSO token** internally via the OAuth2 `client_credentials` grant using the `costManagement.clientId` / `costManagement.clientSecret` from `app-config`. @@ -25,10 +25,13 @@ When a request arrives the handler: ### Endpoints -| Path | Auth | Description | -| ---------------------------------- | ------------- | ----------------------------------- | -| `GET /api/cost-management/health` | None | Health check | -| `ALL /api/cost-management/proxy/*` | `user-cookie` | Secure proxy to Cost Management API | +| Path | Auth | Description | +| ------------------------------------------------- | ------------- | ---------------------------------------------------------- | +| `GET /api/cost-management/health` | None | Health check | +| `GET /api/cost-management/proxy/*` | `user-cookie` | Secure proxy to Cost Management API | +| `GET /api/cost-management/access` | `user-cookie` | Check user's Optimizations RBAC access | +| `GET /api/cost-management/access/cost-management` | `user-cookie` | Check user's OpenShift cost RBAC access | +| `POST /api/cost-management/apply-recommendation` | `user-cookie` | Apply optimization via Orchestrator (requires `ros.apply`) | ### Configuration @@ -42,6 +45,29 @@ costManagement: No `proxy` block with `dangerously-allow-unauthenticated` is needed — the backend plugin handles upstream communication directly. +### Audit Logging + +All backend endpoints emit structured audit log entries with user identity via Backstage's logger. Each entry includes: + +- **`actor`** — the authenticated user's entity ref (e.g., `user:default/admin`) +- **`action`** — the operation performed (`data_access`, `apply_recommendation`, `access_check`, `cost_access_check`) +- **`decision`** — the RBAC outcome (`ALLOW` or `DENY`) +- **`resource`** — the upstream API path accessed +- **`filters`** — the server-injected cluster/project filters (for proxy requests) + +Example log entry: + +```json +{ + "audit": true, + "actor": "user:default/admin", + "action": "data_access", + "resource": "recommendations/openshift", + "decision": "ALLOW", + "filters": { "clusters": ["cluster73"], "projects": ["rhdh"] } +} +``` + ## Getting started The plugin has been added to the example app in this workspace, meaning you'll be able to access it by running `yarn diff --git a/workspaces/cost-management/plugins/cost-management/README.md b/workspaces/cost-management/plugins/cost-management/README.md index 26af4e76f63..147b387a6e3 100644 --- a/workspaces/cost-management/plugins/cost-management/README.md +++ b/workspaces/cost-management/plugins/cost-management/README.md @@ -149,4 +149,4 @@ The procedure involves the following steps: - [License Apache 2.0](../../LICENSE.md) - [DCO](../../DCO.md) -- Find more details in the [Resource Optimization back-end](../cost-mangament-backend/README.md) part of this plugin. +- Find more details in the [Resource Optimization back-end](../cost-management-backend/README.md) part of this plugin. From 73b90d18ebe4db3a21d8532206cb5a1a11a3b072 Mon Sep 17 00:00:00 2001 From: Preeti Wadhwani Date: Thu, 2 Apr 2026 15:09:45 +0530 Subject: [PATCH 2/2] docs(cost-management): address Qodo review findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix inaccurate sanitization claim in ADR 0002: changed to "validated for presence and type" to match actual implementation - Remove non-existent `cost_access_check` audit action from backend README — the code emits `access_check` for both access endpoints Made-with: Cursor --- ...0002-applying-optimizations-using-the-orchestrator-plugin.md | 2 +- .../cost-management/plugins/cost-management-backend/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/cost-management/docs/adrs/0002-applying-optimizations-using-the-orchestrator-plugin.md b/workspaces/cost-management/docs/adrs/0002-applying-optimizations-using-the-orchestrator-plugin.md index d97fced60a7..a9bc17893f2 100644 --- a/workspaces/cost-management/docs/adrs/0002-applying-optimizations-using-the-orchestrator-plugin.md +++ b/workspaces/cost-management/docs/adrs/0002-applying-optimizations-using-the-orchestrator-plugin.md @@ -16,7 +16,7 @@ was hardened beyond the original design: checks `ros.apply` permission, and forwards to Orchestrator using service-to-service authentication. - **Input validation**: `resourceType` is validated against a server-side - allowlist; all input fields are sanitized. + allowlist; all input fields are validated for presence and type. - **Audit logging**: Every apply action is logged with user identity, cluster, namespace, workload, and outcome. - **Confirmation dialog**: The UI presents a confirmation step before applying. diff --git a/workspaces/cost-management/plugins/cost-management-backend/README.md b/workspaces/cost-management/plugins/cost-management-backend/README.md index 64bbb9329f3..b7fc8c64d13 100644 --- a/workspaces/cost-management/plugins/cost-management-backend/README.md +++ b/workspaces/cost-management/plugins/cost-management-backend/README.md @@ -50,7 +50,7 @@ backend plugin handles upstream communication directly. All backend endpoints emit structured audit log entries with user identity via Backstage's logger. Each entry includes: - **`actor`** — the authenticated user's entity ref (e.g., `user:default/admin`) -- **`action`** — the operation performed (`data_access`, `apply_recommendation`, `access_check`, `cost_access_check`) +- **`action`** — the operation performed (`data_access`, `apply_recommendation`, `access_check`) - **`decision`** — the RBAC outcome (`ALLOW` or `DENY`) - **`resource`** — the upstream API path accessed - **`filters`** — the server-injected cluster/project filters (for proxy requests)