Skip to content

fix(cost-management): add authorization, validation, and confirmation for Apply Recommendation#2618

Open
hardengl wants to merge 12 commits intoredhat-developer:mainfrom
hardengl:fix/apply-recommendation-auth
Open

fix(cost-management): add authorization, validation, and confirmation for Apply Recommendation#2618
hardengl wants to merge 12 commits intoredhat-developer:mainfrom
hardengl:fix/apply-recommendation-auth

Conversation

@hardengl
Copy link
Copy Markdown

@hardengl hardengl commented Mar 26, 2026

Summary

Stacked on #2616 — review that PR first. This PR adds commits on top.

Addresses FLPATH-3488, FLPATH-3492, FLPATH-3491 from the FLPATH-3503 security epic.

Problem

The "Apply Recommendation" workflow was executed by the frontend calling the Orchestrator API directly. This had three security issues:

  1. No authorization check (FLPATH-3488): Any authenticated Backstage user could execute the Apply Recommendation workflow, regardless of RBAC permissions. There was no ros.apply permission gate.
  2. No input validation (FLPATH-3492): The resourceType field passed to the workflow was not validated against an allowlist. A malicious user could inject arbitrary resource types.
  3. No audit trail (FLPATH-3491): Apply Recommendation actions were not logged with user identity.

Solution

Created a new backend endpoint POST /api/cost-management/apply-recommendation that:

  1. Authenticates the user via Backstage httpAuth
  2. Authorizes against the new ros.apply permission (returns 403 if denied)
  3. Validates resourceType against a hardcoded allowlist (openshift, containers)
  4. Sanitizes inputData fields (strips control characters, enforces field length limits)
  5. Forwards to the Orchestrator plugin using service-to-service authentication (the user only needs ros.apply — no Orchestrator permissions required)
  6. Logs the action with user identity (via structured audit logging)

Frontend changes

  • Confirmation dialog: Users now see a "Are you sure?" dialog before applying recommendations
  • Permission gate: The "Apply recommendation" button is disabled with a tooltip ("You do not have permission to apply recommendations") when the user lacks ros.apply permission, using usePermission from @backstage/plugin-permission-react
  • Error surface: Backend 403 errors are displayed via ResponseErrorPanel instead of being silently swallowed
  • Routing: Execute call goes through the new backend endpoint instead of directly to Orchestrator

New permission: ros.apply

# Grant Apply Recommendation permission
p, role:default/CostApply, ros.apply, use, allow
g, user:default/some-user, role:default/CostApply

Changes

  • New POST /apply-recommendation route (applyRecommendation.ts)
  • New rosApplyPermission in permissions.ts
  • Permission registered in the backend's permissionIntegrationRouter
  • Frontend usePermission hook in OptimizationEngineTab.tsx
  • Frontend error handling in OptimizationsBreakdownPage.tsx
  • Confirmation dialog in OptimizationEngineTab.tsx
  • Unit tests for the new backend route (validation, authorization, upstream forwarding, error handling)

Test plan

Unit tests

  • yarn tsc -b — clean compilation, zero errors
  • Backend: applyRecommendation.test.ts — 6 tests covering:
    • Missing body → 400
    • Missing required fields → 400
    • Invalid resourceType → 400
    • Permission denied → 403
    • Successful forwarding → 201
    • Upstream error handling (safe JSON parsing)
  • All other backend + common tests still pass

CI

  • SonarQube Quality Gate: Passed

Deployment verification

Qodo bot findings (addressed)

  • Safe JSON parsing for upstream responses (checks Content-Type header)
  • InputData field sanitization (control character stripping, length limits)

…en exposure and RBAC bypass

Addresses security findings from the RHDH Cost Plugin threat model:
- Removed /token endpoint that exposed SSO credentials to the browser
- Added secure backend proxy (/api/cost-management/proxy/*) with server-side
  RBAC enforcement and internal SSO token management
- Removed dangerously-allow-unauthenticated proxy configuration
- Updated frontend clients to route through the secure backend proxy

FLPATH-3487

Made-with: Cursor
…hitecture

- Remove all references to dangerously-allow-unauthenticated proxy configuration
- Document new secure backend proxy architecture and endpoints
- Update static and dynamic plugin setup instructions
- Add server-side RBAC enforcement explanation to rbac.md

Made-with: Cursor
…C bypass

- Reject proxyPath containing dot-segments (../) or leading slashes to prevent
  path traversal beyond /cost-management/v1/
- Post-construction check ensures resolved pathname stays under the base path
- Decode query parameter keys before RBAC matching so percent-encoded variants
  (e.g. filter%5Bexact%3Acluster%5D) are correctly stripped
- Replace regex-based stripping with Set-based decoded key lookup

Made-with: Cursor
…uplication

Extract resolveAccessForSection() to eliminate structural duplication between
resolveOptimizationsAccess and resolveCostManagementAccess. Each section now
provides only a data-fetching callback while the common authorize-cache-filter
logic lives in one place.

Made-with: Cursor
@rhdh-gh-app
Copy link
Copy Markdown

rhdh-gh-app bot commented Mar 26, 2026

Important

This PR includes changes that affect public-facing API. Please ensure you are adding/updating documentation for new features or behavior.

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 minor v2.0.2
@red-hat-developer-hub/plugin-cost-management-common workspaces/cost-management/plugins/cost-management-common minor v2.0.1
@red-hat-developer-hub/plugin-cost-management workspaces/cost-management/plugins/cost-management minor v2.0.1

@rhdh-qodo-merge
Copy link
Copy Markdown

Review Summary by Qodo

Add authorization, validation, and secure proxy for Apply Recommendation and Cost Management API access

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add server-side authorization and input validation for Apply Recommendation workflow via new `POST
  /apply-recommendation` backend endpoint
• Implement secure backend proxy (/api/cost-management/proxy/*) that enforces RBAC server-side and
  eliminates token exposure to browser
• Register ros.apply permission and add confirmation dialog to prevent accidental workflow
  execution
• Remove /token endpoint and dangerously-allow-unauthenticated proxy configuration; route all
  data fetching through secure backend
Diagram
flowchart LR
  A["Frontend Request"] --> B["Backend Secure Proxy"]
  B --> C["Authenticate via httpAuth"]
  C --> D["Check RBAC Permissions"]
  D --> E["Obtain SSO Token Server-Side"]
  E --> F["Strip Client Filters"]
  F --> G["Inject Server-Authorized Filters"]
  G --> H["Forward to Cost Management API"]
  H --> I["Return Filtered Response"]
  
  J["Apply Recommendation Request"] --> K["POST /apply-recommendation"]
  K --> L["Validate resourceType Allowlist"]
  L --> M["Check ros.apply Permission"]
  M --> N["Forward to Orchestrator"]
  N --> O["Return Workflow Instance ID"]
Loading

Grey Divider

File Changes

1. workspaces/cost-management/plugins/cost-management-backend/src/routes/applyRecommendation.ts ✨ Enhancement +182/-0

New endpoint for authorized workflow execution

workspaces/cost-management/plugins/cost-management-backend/src/routes/applyRecommendation.ts


2. workspaces/cost-management/plugins/cost-management-backend/src/routes/applyRecommendation.test.ts 🧪 Tests +209/-0

Comprehensive tests for Apply Recommendation endpoint

workspaces/cost-management/plugins/cost-management-backend/src/routes/applyRecommendation.test.ts


3. workspaces/cost-management/plugins/cost-management-backend/src/routes/secureProxy.ts ✨ Enhancement +353/-0

Server-side proxy with RBAC enforcement and token management

workspaces/cost-management/plugins/cost-management-backend/src/routes/secureProxy.ts


View more (20)
4. workspaces/cost-management/plugins/cost-management-backend/src/models/RouterOptions.ts ✨ Enhancement +4/-0

Add discovery and auth service dependencies

workspaces/cost-management/plugins/cost-management-backend/src/models/RouterOptions.ts


5. workspaces/cost-management/plugins/cost-management-backend/src/plugin.ts ⚙️ Configuration changes +13/-3

Register new services and auth policies for endpoints

workspaces/cost-management/plugins/cost-management-backend/src/plugin.ts


6. workspaces/cost-management/plugins/cost-management-backend/src/service/router.ts ✨ Enhancement +16/-4

Wire up secure proxy and apply-recommendation routes

workspaces/cost-management/plugins/cost-management-backend/src/service/router.ts


7. workspaces/cost-management/plugins/cost-management-backend/src/service/router.test.ts 🧪 Tests +2/-0

Update router tests with new service dependencies

workspaces/cost-management/plugins/cost-management-backend/src/service/router.test.ts


8. workspaces/cost-management/plugins/cost-management-common/src/permissions.ts ✨ Enhancement +9/-0

Add ros.apply permission for workflow execution

workspaces/cost-management/plugins/cost-management-common/src/permissions.ts


9. workspaces/cost-management/plugins/cost-management-common/src/clients/cost-management/CostManagementSlimClient.ts 🐞 Bug fix +37/-200

Route requests through backend secure proxy instead of direct API calls

workspaces/cost-management/plugins/cost-management-common/src/clients/cost-management/CostManagementSlimClient.ts


10. workspaces/cost-management/plugins/cost-management-common/src/clients/optimizations/OptimizationsClient.ts 🐞 Bug fix +39/-128

Simplify client to use backend proxy for frontend requests

workspaces/cost-management/plugins/cost-management-common/src/clients/optimizations/OptimizationsClient.ts


11. workspaces/cost-management/plugins/cost-management-common/src/clients/optimizations/OptimizationsClient.test.ts 🧪 Tests +9/-21

Update tests to reflect backend proxy routing

workspaces/cost-management/plugins/cost-management-common/src/clients/optimizations/OptimizationsClient.test.ts


12. workspaces/cost-management/plugins/cost-management-common/src/clients/orchestrator-slim/OrchestratorSlimClient.ts 🐞 Bug fix +24/-16

Route workflow execution through cost-management backend endpoint

workspaces/cost-management/plugins/cost-management-common/src/clients/orchestrator-slim/OrchestratorSlimClient.ts


13. workspaces/cost-management/plugins/cost-management/src/pages/optimizations-breakdown/components/optimization-engine-tab/OptimizationEngineTab.tsx ✨ Enhancement +77/-27

Add confirmation dialog before applying recommendations

workspaces/cost-management/plugins/cost-management/src/pages/optimizations-breakdown/components/optimization-engine-tab/OptimizationEngineTab.tsx


14. workspaces/cost-management/plugins/cost-management-backend/README.md 📝 Documentation +40/-2

Document secure proxy architecture and configuration

workspaces/cost-management/plugins/cost-management-backend/README.md


15. workspaces/cost-management/plugins/cost-management-backend/app-config.dynamic.yaml ⚙️ Configuration changes +0/-6

Remove dangerously-allow-unauthenticated proxy configuration

workspaces/cost-management/plugins/cost-management-backend/app-config.dynamic.yaml


16. workspaces/cost-management/plugins/cost-management/README.md 📝 Documentation +12/-51

Update setup instructions to remove proxy configuration

workspaces/cost-management/plugins/cost-management/README.md


17. workspaces/cost-management/docs/dynamic-plugin.md 📝 Documentation +27/-28

Update dynamic plugin setup to remove proxy endpoint

workspaces/cost-management/docs/dynamic-plugin.md


18. workspaces/cost-management/docs/rbac.md 📝 Documentation +45/-1

Document server-side RBAC enforcement and ros.apply permission

workspaces/cost-management/docs/rbac.md


19. workspaces/cost-management/plugins/cost-management-common/report-clients.api.md 📝 Documentation +3/-2

Update API documentation for client changes

workspaces/cost-management/plugins/cost-management-common/report-clients.api.md


20. workspaces/cost-management/plugins/cost-management-common/report-permissions.api.md 📝 Documentation +6/-0

Add ros.apply permission to API documentation

workspaces/cost-management/plugins/cost-management-common/report-permissions.api.md


21. workspaces/cost-management/plugins/cost-management-common/report.api.md 📝 Documentation +1/-1

Update API documentation for client visibility

workspaces/cost-management/plugins/cost-management-common/report.api.md


22. workspaces/cost-management/.changeset/apply-recommendation-auth.md 📝 Documentation +12/-0

Changelog entry for Apply Recommendation authorization

workspaces/cost-management/.changeset/apply-recommendation-auth.md


23. workspaces/cost-management/.changeset/secure-proxy-server-side.md 📝 Documentation +13/-0

Changelog entry for secure proxy architecture

workspaces/cost-management/.changeset/secure-proxy-server-side.md


Grey Divider

Qodo Logo

@rhdh-qodo-merge
Copy link
Copy Markdown

rhdh-qodo-merge bot commented Mar 26, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Remediation recommended

1. Proxy drops request body🐞 Bug ✓ Correctness
Description
The backend mounts secureProxy for ALL HTTP methods, but the upstream fetch call never forwards
the incoming request body. Any proxied POST/PUT/PATCH request will silently lose its payload.
Code

workspaces/cost-management/plugins/cost-management-backend/src/routes/secureProxy.ts[R328-335]

+      const upstreamResponse = await fetch(targetUrl.toString(), {
+        headers: {
+          'Content-Type': 'application/json',
+          Accept: acceptHeader,
+          Authorization: `Bearer ${token}`,
+        },
+        method: req.method,
+      });
Evidence
createRouter mounts router.all('/proxy/*', ...), but the proxy forwards only method and
headers and omits body entirely, so non-GET calls cannot work correctly through this proxy.

workspaces/cost-management/plugins/cost-management-backend/src/service/router.ts[55-58]
workspaces/cost-management/plugins/cost-management-backend/src/routes/secureProxy.ts[328-335]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`secureProxy` is registered for all methods but does not forward request bodies to upstream. This breaks any future (or current) non-GET endpoints routed through `/proxy/*`.

### Issue Context
If `/proxy/*` is intended to be read-only, mount only the safe methods (GET/HEAD) to prevent accidental misuse. If it must support mutations, forward the raw body (and consider content-type passthrough).

### Fix Focus Areas
- workspaces/cost-management/plugins/cost-management-backend/src/service/router.ts[55-58]
- workspaces/cost-management/plugins/cost-management-backend/src/routes/secureProxy.ts[326-335]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Query decode can throw🐞 Bug ⛯ Reliability
Description
secureProxy uses decodeURIComponent on raw query keys/values without guarding against malformed
percent-encoding. A malformed query string can trigger a thrown URIError and turn the request into
a 500 response.
Code

workspaces/cost-management/plugins/cost-management-backend/src/routes/secureProxy.ts[R284-295]

+      const rawQuery = req.originalUrl.split('?')[1] || '';
+      const rawParams = rawQuery.split('&').filter(p => p.length > 0);
+
+      for (const param of rawParams) {
+        const eqIdx = param.indexOf('=');
+        const rawKey = eqIdx >= 0 ? param.substring(0, eqIdx) : param;
+        const rawVal = eqIdx >= 0 ? param.substring(eqIdx + 1) : '';
+        const decodedKey = decodeURIComponent(rawKey);
+
+        if (!rbacControlledKeys.has(decodedKey)) {
+          targetUrl.searchParams.append(decodedKey, decodeURIComponent(rawVal));
+        }
Evidence
decodeURIComponent(rawKey) and decodeURIComponent(rawVal) are called directly inside the request
handler loop; malformed encodings (e.g. trailing %) will throw at decode time and fall into the
handler catch, returning 500 instead of a controlled 400.

workspaces/cost-management/plugins/cost-management-backend/src/routes/secureProxy.ts[284-295]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`decodeURIComponent` is called on raw query fragments without validation/try-catch. Malformed percent-encoding throws and results in a 500.

### Issue Context
This proxy handles arbitrary query strings from browsers. It should be resilient and return a clear 400 (or skip the offending param) when encoding is invalid.

### Fix Focus Areas
- workspaces/cost-management/plugins/cost-management-backend/src/routes/secureProxy.ts[284-295]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Upstream JSON parse brittle🐞 Bug ⛯ Reliability
Description
applyRecommendation parses the orchestrator response with upstreamResponse.json() before checking
status/content-type. If orchestrator returns non-JSON or an empty body on error, the handler throws
and returns a generic 500 instead of propagating the upstream status/body.
Code

workspaces/cost-management/plugins/cost-management-backend/src/routes/applyRecommendation.ts[R139-162]

+      const upstreamResponse = await fetch(executeUrl, {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+          Authorization: `Bearer ${token}`,
+        },
+        body: JSON.stringify({ inputData }),
+      });
+
+      const payload = await upstreamResponse.json();
+
+      if (!upstreamResponse.ok) {
+        logger.warn('audit:apply-recommendation:upstream-error', {
+          action: 'apply_recommendation',
+          decision: 'ALLOW',
+          workflowId,
+          cluster: inputData.clusterName,
+          namespace: inputData.resourceNamespace,
+          workload: inputData.resourceName,
+          resourceType: inputData.resourceType,
+          upstreamStatus: upstreamResponse.status,
+        });
+        return res.status(upstreamResponse.status).json(payload);
+      }
Evidence
The handler unconditionally calls await upstreamResponse.json() and only afterwards checks
upstreamResponse.ok; JSON parse failures will be caught by the outer try/catch and converted into
a 500, masking the true upstream failure mode.

workspaces/cost-management/plugins/cost-management-backend/src/routes/applyRecommendation.ts[139-162]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`applyRecommendation` assumes orchestrator always returns JSON and parses unconditionally. Non-JSON/empty responses can cause parsing exceptions and collapse upstream errors into a generic 500.

### Issue Context
The secure proxy already does content-type based handling; `applyRecommendation` should similarly check `content-type` and handle empty bodies safely, while still propagating `upstreamResponse.status`.

### Fix Focus Areas
- workspaces/cost-management/plugins/cost-management-backend/src/routes/applyRecommendation.ts[139-162]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Exact filters stripped 🐞 Bug ✓ Correctness
Description
secureProxy always removes RBAC-controlled query keys like
filter[exact:cluster]/filter[exact:project] and replaces them with server-derived filters, so
exact cluster/project filtering coming from the UI will never reach the upstream API. This can
change OpenShift/Optimizations filtering behavior and may return broader datasets than the user
selected.
Code

workspaces/cost-management/plugins/cost-management-backend/src/routes/secureProxy.ts[R278-320]

+      const rbacControlledKeys = new Set(
+        access.filterStyle === 'ros'
+          ? ['cluster', 'project']
+          : ['filter[exact:cluster]', 'filter[exact:project]'],
+      );
+
+      const rawQuery = req.originalUrl.split('?')[1] || '';
+      const rawParams = rawQuery.split('&').filter(p => p.length > 0);
+
+      for (const param of rawParams) {
+        const eqIdx = param.indexOf('=');
+        const rawKey = eqIdx >= 0 ? param.substring(0, eqIdx) : param;
+        const rawVal = eqIdx >= 0 ? param.substring(eqIdx + 1) : '';
+        const decodedKey = decodeURIComponent(rawKey);
+
+        if (!rbacControlledKeys.has(decodedKey)) {
+          targetUrl.searchParams.append(decodedKey, decodeURIComponent(rawVal));
+        }
+      }
+
+      // Inject server-side RBAC filters (empty arrays = full access, no filter needed)
+      if (access.clusterFilters.length > 0) {
+        if (access.filterStyle === 'ros') {
+          access.clusterFilters.forEach(c =>
+            targetUrl.searchParams.append('cluster', c),
+          );
+        } else {
+          access.clusterFilters.forEach(c =>
+            targetUrl.searchParams.append('filter[exact:cluster]', c),
+          );
+        }
+      }
+      if (access.projectFilters.length > 0) {
+        if (access.filterStyle === 'ros') {
+          access.projectFilters.forEach(p =>
+            targetUrl.searchParams.append('project', p),
+          );
+        } else {
+          access.projectFilters.forEach(p =>
+            targetUrl.searchParams.append('filter[exact:project]', p),
+          );
+        }
+      }
Evidence
The proxy explicitly drops any query params whose decoded key is in rbacControlledKeys and only
re-adds cluster/project filters based on RBAC decisions. The OpenShift filters hook generates
filter[exact:<key>] parameters (including cluster/project), and the cost-management client also
appends filter[exact:cluster], meaning those user-selected exact filters are guaranteed to be
stripped by the proxy before forwarding.

workspaces/cost-management/plugins/cost-management-backend/src/routes/secureProxy.ts[274-320]
workspaces/cost-management/plugins/cost-management/src/hooks/useOpenShiftFilters.ts[180-209]
workspaces/cost-management/plugins/cost-management-common/src/clients/cost-management/CostManagementSlimClient.ts[220-281]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The backend `secureProxy` strips `filter[exact:cluster]` / `filter[exact:project]` (and ROS `cluster`/`project`) from the client query string unconditionally and then injects only RBAC-derived filters. This makes user-selected “exact” filtering impossible because those parameters never reach the upstream Cost Management API.

### Issue Context
You still need server-side RBAC enforcement (no client-controlled widening of scope). The fix should preserve user intent by intersecting client-requested filters with authorized filters (or allowing them through when the user has full access), rather than dropping them.

### Fix Focus Areas
- workspaces/cost-management/plugins/cost-management-backend/src/routes/secureProxy.ts[274-320]
- workspaces/cost-management/plugins/cost-management/src/hooks/useOpenShiftFilters.ts[180-209]
- workspaces/cost-management/plugins/cost-management-common/src/clients/cost-management/CostManagementSlimClient.ts[220-281]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

hardengl and others added 5 commits March 26, 2026 20:09
…eclare config schema

Defense-in-depth: hardcode fetch method to 'GET' instead of passing req.method,
preventing SSRF amplification if the route registration ever changes from router.get
to router.all. Add costManagementProxyBaseUrl to config.d.ts with @visibility backend
so the config key is schema-validated and documented.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… for Apply Recommendation

- New ros.apply permission required to execute Apply Recommendation workflow
- New POST /apply-recommendation backend endpoint validates resourceType against
  server-side allowlist and checks ros.apply permission before forwarding to Orchestrator
- Workflow execution now routes through the cost-management backend instead of
  directly to the Orchestrator plugin, enabling server-side authorization and audit logging
- Confirmation dialog prevents accidental workflow execution
- Register costPluginPermissions in permission integration router (was previously missing)

Fixes: FLPATH-3488, FLPATH-3492, FLPATH-3491
Made-with: Cursor
Run yarn build:api-reports:only to update report-clients.api.md and
report.api.md with the correct auto-generated format.

Made-with: Cursor
- Wrap decodeURIComponent in try/catch, return 400 on malformed encoding
- Safe JSON parse in applyRecommendation (check content-type first)
- Change router.all('/proxy/*') to router.get (proxy is read-only)
- Delete unused routes/token.ts

Made-with: Cursor
Construct a clean inputData object with only known fields before forwarding
to the Orchestrator, preventing extra injected fields from passing through
to the workflow execution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract three helper functions from the secureProxy handler to bring
cognitive complexity from 24 down below the SonarQube threshold of 15:

- isPathTraversal: path validation guard
- parseClientQueryParams: query string parsing with RBAC key stripping
- injectRbacFilters: server-side RBAC filter injection

Made-with: Cursor
@hardengl hardengl force-pushed the fix/apply-recommendation-auth branch from 058dcfe to 0321e9f Compare March 27, 2026 13:47
…indings

Add missing OpenShift route, menu hierarchy, and explain why the
proxy.endpoints config was removed (replaced by server-side secure proxy).

Made-with: Cursor
…nt config

Update the ConfigMap example to exactly match the working configuration
deployed on ocp-edge73: icon reference name costManagementIcon and
dot-separated menuItems keys (cost-management.optimizations).

Made-with: Cursor
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@ydayagi ydayagi left a comment

Choose a reason for hiding this comment

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

LGTM @asmasarw @PreetiW wdyt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants