Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions workspaces/cost-management/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines +18 to +21

This comment was marked as resolved.

- **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

Expand Down
8 changes: 4 additions & 4 deletions workspaces/cost-management/docs/rbac.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
####
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading