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..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 @@ -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 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. + +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..b7fc8c64d13 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`) +- **`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.