Skip to content

feat(#3309): pivot Kagenti auth to OAuth2 Client Credentials#3602

Open
gabemontero wants to merge 6 commits into
redhat-developer:mainfrom
gabemontero:upd-oidc-keycloak-approach
Open

feat(#3309): pivot Kagenti auth to OAuth2 Client Credentials#3602
gabemontero wants to merge 6 commits into
redhat-developer:mainfrom
gabemontero:upd-oidc-keycloak-approach

Conversation

@gabemontero

Copy link
Copy Markdown
Contributor

Summary

  • Pivots Kagenti authentication from per-user RFC 8693 token exchange to service-account OAuth2 Client Credentials Grant
  • Updates all specification, openspec, and config schema files to reflect the new approach
  • Config keys changed from boost.kagenti.auth.tokenExchange.{enabled,audience,userTokenHeader} to boost.kagenti.auth.{tokenEndpoint,clientId,clientSecret,tokenExpiryBufferSeconds}
  • Updates GitHub issue boost-backend — Keycloak service-account auth via OAuth2 Client Credentials (issue 13 of 15) #3309 title and body to reflect the pivot

Rationale

The service-account approach (matching the pattern proven in production) is simpler and sufficient for current requirements. Per-user RFC 8693 token exchange adds complexity (auth proxy dependency, per-user cache management) without proportional benefit. User identity is propagated via X-Backstage-User header for audit trails.

Files changed (11)

Specifications (2): boost-context.md, security-safety-governance.md PRD
OpenSpec (5): security design, proposal, tasks, access-control spec, runtime-config spec
Code (4): config.d.ts, schemas.ts, schemas.test.ts, report.api.md

Test plan

🤖 Generated with Claude Code

…xchange to OAuth2 Client Credentials

Replace per-user RFC 8693 token exchange approach with service-account
Keycloak authentication via OAuth2 Client Credentials Grant. The
service-account approach is simpler, proven in production, and
sufficient for current requirements. User identity is propagated
via X-Backstage-User header for audit purposes.

Config keys changed:
- boost.kagenti.auth.tokenExchange.{enabled,audience,userTokenHeader}
+ boost.kagenti.auth.{tokenEndpoint,clientId,clientSecret,tokenExpiryBufferSeconds}

Updated files:
- specifications/boost-context.md (principle 10)
- specifications/prd/security-safety-governance.md (section 3, throughout)
- openspec security-safety-governance: design, proposal, tasks, access-control spec
- openspec runtime-config spec
- config.d.ts, schemas.ts, schemas.test.ts, report.api.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gabemontero gabemontero requested review from a team, durandom and mareklibra as code owners June 26, 2026 13:49
@rhdh-gh-app

rhdh-gh-app Bot commented Jun 26, 2026

Copy link
Copy Markdown

Missing Changesets

The following package(s) are changed by this PR but do not have a changeset:

  • @red-hat-developer-hub/backstage-plugin-boost-backend

See CONTRIBUTING.md for more information about how to add changesets.

Changed Packages

Package Name Package Path Changeset Bump Current Version
@red-hat-developer-hub/backstage-plugin-boost-backend workspaces/boost/plugins/boost-backend none v0.1.1

@gabemontero gabemontero requested a review from rohitkrai03 June 26, 2026 13:50
@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 54.21%. Comparing base (71cfae4) to head (91fbac0).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3602   +/-   ##
=======================================
  Coverage   54.21%   54.21%           
=======================================
  Files        2312     2312           
  Lines       88532    88532           
  Branches    24661    24664    +3     
=======================================
  Hits        48000    48000           
  Misses      39040    39040           
  Partials     1492     1492           
Flag Coverage Δ *Carryforward flag
adoption-insights 83.70% <ø> (ø) Carriedforward from ed0e8f3
ai-integrations 67.95% <ø> (ø) Carriedforward from ed0e8f3
app-defaults 69.79% <ø> (ø) Carriedforward from ed0e8f3
augment 46.39% <ø> (ø) Carriedforward from ed0e8f3
boost 74.35% <100.00%> (ø)
bulk-import 72.46% <ø> (ø) Carriedforward from ed0e8f3
cost-management 14.10% <ø> (ø) Carriedforward from ed0e8f3
dcm 61.79% <ø> (ø) Carriedforward from ed0e8f3
extensions 61.53% <ø> (ø) Carriedforward from ed0e8f3
global-floating-action-button 71.18% <ø> (ø) Carriedforward from ed0e8f3
global-header 59.71% <ø> (ø) Carriedforward from ed0e8f3
homepage 49.84% <ø> (ø) Carriedforward from ed0e8f3
install-dynamic-plugins 56.77% <ø> (ø) Carriedforward from ed0e8f3
konflux 91.49% <ø> (ø) Carriedforward from ed0e8f3
lightspeed 68.54% <ø> (ø) Carriedforward from ed0e8f3
mcp-integrations 85.46% <ø> (ø) Carriedforward from ed0e8f3
orchestrator 38.30% <ø> (ø) Carriedforward from ed0e8f3
quickstart 63.76% <ø> (ø) Carriedforward from ed0e8f3
sandbox 79.56% <ø> (ø) Carriedforward from ed0e8f3
scorecard 82.67% <ø> (ø) Carriedforward from ed0e8f3
theme 61.26% <ø> (ø) Carriedforward from ed0e8f3
translations 7.25% <ø> (ø) Carriedforward from ed0e8f3
x2a 78.68% <ø> (ø) Carriedforward from ed0e8f3

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


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 71cfae4...91fbac0. 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.

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 26, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 1:52 PM UTC · Completed 2:05 PM UTC
Commit: 71cfae4 · View workflow run →

@gabemontero

Copy link
Copy Markdown
Contributor Author

@rohitkrai03 @mareklibra - per our discussion today, here is the change to fallback to the existing service account level OIDC flow to kagenti, i.e. what augment does today

Then, we will use the finer grained backstage RBAC (vs. what augment provides) to control who makes what calls to kagenti

I have also refactored #3309 to reflect this new approach

@durandom FYI

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review

Findings

Low

  • [fail-open] workspaces/boost/plugins/boost-backend/src/config/schemas.ts — All four OAuth2 config fields are individually .optional() with no cross-field validation requiring that if any of tokenEndpoint, clientId, or clientSecret is set, all three must be present. Since KeycloakTokenManager does not exist yet in the boost workspace, the risk is currently speculative — but should be addressed when the consumer is implemented (task 7.1).
    Remediation: Validate completeness in the KeycloakTokenManager constructor, or add .refine() cross-field validation.

  • [config-schema-mismatch] workspaces/boost/plugins/boost-backend-module-kagenti/src/types.ts:34KagentiKeycloakConfig defines {serverUrl, realm, clientId, clientSecret} which differs from the new schema's {tokenEndpoint, clientId, clientSecret, tokenExpiryBufferSeconds}. The interface is currently unused (dead type) but should be updated or removed to avoid confusion.

  • [input-validation] workspaces/boost/plugins/boost-backend/src/config/schemas.tsclientId and clientSecret accept empty strings. Other fields in the same registry (e.g., boost.model.name) use .min(1) to prevent empty values.
    Remediation: Add .min(1) to clientId and clientSecret schemas.

  • [test-adequacy] workspaces/boost/plugins/boost-backend/src/config/schemas.test.ts — No test validates that tokenEndpoint rejects non-URL values. The .url() constraint should be covered similarly to the existing boost.model.baseUrl validation test.

Previous run

Review

Findings

Medium

  • [logic-error] workspaces/boost/plugins/boost-backend/src/config/RuntimeConfigResolver.ts:146 — Pre-existing bug exposed by this PR: getEffectiveConfig() strips sensitive fields from the cache (line 180), then on cache hits (lines 147–157) only re-fetches sensitive values from getAllOverrides() (DB). The new boost.kagenti.auth.clientSecret is yaml-only + sensitive: true, so it will never be in the DB. After the first request populates the cache, subsequent calls to resolve('boost.kagenti.auth.clientSecret') will return undefined. The existing boost.encryptionSecret has the same latent bug but is never resolved at runtime (injected at startup). When KeycloakTokenManager is implemented in a follow-up PR, this must be fixed first.
    Remediation: In the cache-hit path (lines 150–157), also re-read yaml-only sensitive fields from YAML config — iterate boostConfigFields keys where sensitive === true && configScope === 'yaml-only' and populate them via readYamlValue().

Low

  • [missing-test] workspaces/boost/plugins/boost-backend/src/config/schemas.test.ts:39 — The test for tokenExpiryBufferSeconds only checks key existence, sensitivity, and DB-writability. It does not test the .int().min(0).default(60) Zod constraints (default application, negative number rejection, non-integer rejection).

  • [edge-case] workspaces/boost/plugins/boost-backend/src/config/schemas.ts:195tokenExpiryBufferSeconds uses .optional().default(60), but RuntimeConfigResolver.resolve() returns raw YAML values via readYamlValue(), not Zod-parsed values. If YAML omits the field, resolve() returns undefined rather than 60. The consumer must apply its own fallback. This is consistent with how all optional fields work in this codebase.

  • [fail-open] workspaces/boost/plugins/boost-backend/src/config/schemas.ts:166 — All four new kagenti auth config fields are optional(). When KeycloakTokenManager is implemented, it must validate that tokenEndpoint, clientId, and clientSecret are all present together (mutual dependency) rather than silently proceeding without authentication when only some are set.

Previous run (2)

Review

Reason: stale-head

The review agent reviewed commit 82cc288bc00d234af6cd7dabda30f4b09725959b but the PR HEAD is now ed0e8f3371f1dd77688353358d68eed32fed9d7d. This review was discarded to avoid approving unreviewed code.

Previous run (3)

Review

Findings

Medium

  • [edge-case] workspaces/boost/openspec/changes/security-safety-governance/specs/access-control/spec.md — The "Token refresh on authentication failure" scenario specifies that on HTTP 401 the cached token is invalidated, a fresh token is obtained, and the request is retried — but there is no retry limit or circuit breaker. If Kagenti returns 401 for reasons other than token expiry (e.g., revoked client, misconfigured audience, authorization failure), the implementation could enter an infinite retry loop: get token → call Kagenti → 401 → invalidate → get token → call Kagenti → 401 → ...
    Remediation: Add a maximum retry count (e.g., 1 retry) to the scenario: "AND the retry is attempted at most once — if the retried request also returns 401, the error is propagated to the caller." Update both spec.md and design.md.

Low

  • [edge-case] workspaces/boost/openspec/changes/security-safety-governance/specs/access-control/spec.md — The "Service-account token acquisition" scenario lists three config fields (tokenEndpoint, clientId, clientSecret) that must be provided, but there is no scenario specifying behavior when only some are configured. The Zod schemas mark all three as optional with no cross-field validation, so partial configuration (e.g., tokenEndpoint set but clientSecret missing) has unspecified behavior.

  • [input-validation] workspaces/boost/plugins/boost-backend/src/config/schemas.ts — The tokenEndpoint field uses z.string().url() validation, which accepts any URL scheme including http://. For a production Keycloak token endpoint handling OAuth2 Client Credentials, allowing plain HTTP could expose the client secret in transit. Consider restricting to HTTPS-only or adding a runtime warning when a non-HTTPS URL is configured.

  • [fail-open] workspaces/boost/plugins/boost-backend/src/config/schemas.ts — All four new config fields (tokenEndpoint, clientId, clientSecret, tokenExpiryBufferSeconds) are optional. No runtime consumer exists yet in this PR. The fail-open posture depends entirely on how the future KeycloakTokenManager consumer handles absent config — if it proceeds without authentication when these fields are missing, it would be a fail-open vulnerability. This should be verified when the consuming code lands.

  • [authentication-model-change] workspaces/boost/plugins/boost-backend/src/config/schemas.ts — This PR pivots from per-user RFC 8693 token exchange to a shared service-account OAuth2 Client Credentials Grant. The spec addresses this trade-off by propagating user identity via X-Backstage-User header for audit purposes, which is appropriate. Confirm that per-user attribution is not required at the Kagenti layer beyond what the header provides.


Labels: PR modifies boost workspace auth configuration and security specifications

Previous run (4)

Review

Reason: stale-head

The review agent reviewed commit d3886f989de550da1da7c07e8638b85def27c0ee but the PR HEAD is now 8cb43ef8b8e5c90bb137c2690b018fbd6285f58c. This review was discarded to avoid approving unreviewed code.

Previous run (5)

Review

Findings

Medium

  • [stale-reference] workspaces/boost/staged-issues.md:377 — The issue boost-backend — Keycloak service-account auth via OAuth2 Client Credentials (issue 13 of 15) #3309 entry still contains references to the old RFC 8693 token exchange approach (TokenExchangeManager, per-user token caching with TTL, boost.kagenti.auth.tokenExchange.* config keys). The PR updates all spec/design/task files and the GitHub issue, but not this local tracking document.
    Remediation: Update the issue boost-backend — Keycloak service-account auth via OAuth2 Client Credentials (issue 13 of 15) #3309 entry in staged-issues.md to reflect the pivot to OAuth2 Client Credentials Grant with KeycloakTokenManager.

  • [stale-reference] workspaces/boost/specifications/prd/platform-operations-deployment.md:189 — Stale reference to removed config keys tokenExchange.enabled, audience, userTokenHeader in a file not included in the diff. This file still describes Kagenti auth configuration using the old token exchange terminology.
    Remediation: Update line 189 to reference the new config keys: tokenEndpoint, clientId, clientSecret, tokenExpiryBufferSeconds.

  • [stale-reference] workspaces/boost/openspec/changes/platform-operations-deployment/tasks.md:22 — Task 2.5 mentions adding Zod schemas for tokenExchange but the config fields have been renamed to tokenEndpoint, clientId, clientSecret, tokenExpiryBufferSeconds.
    Remediation: Update task 2.5 to reference the new Kagenti auth config field names.

Low

  • [api-contract] workspaces/boost/plugins/boost-backend/src/config/schemas.ts:168 — The three auth config fields (tokenEndpoint, clientId, clientSecret) are all independently optional in the Zod schema, but the spec states they are required together ("WHEN tokenEndpoint is configured AND clientId and clientSecret are provided"). Interdependency validation is deferred to the runtime consumer (KeycloakTokenManager), which is consistent with how other config field groups in this file work. See also: [fail-open] finding at this location — if partial config is provided, the system will fail at runtime rather than at config validation time.

  • [fail-open] workspaces/boost/plugins/boost-backend/src/config/schemas.ts:168 — A deployment can configure tokenEndpoint and clientId but omit clientSecret. The schema passes validation, and the runtime behavior depends on the KeycloakTokenManager implementation (not yet in this PR). In the worst case, an empty client_secret would be sent to Keycloak, failing at runtime rather than at config validation time.
    Remediation: Consider adding a Zod refinement enforcing: if any of tokenEndpoint/clientId/clientSecret is set, all three must be set. Alternatively, ensure KeycloakTokenManager validates at startup.

  • [test-inadequate] workspaces/boost/plugins/boost-backend/src/config/schemas.test.ts:36 — Tests verify key existence and sensitivity but do not exercise the new Zod validation rules: url() rejection for tokenEndpoint, int().min(0) for tokenExpiryBufferSeconds, and the default(60) value. The existing test file does test validation for other fields (e.g., URL rejection for boost.model.baseUrl), so this represents a gap relative to established patterns.
    Remediation: Consider adding schema validation tests in a follow-up.

  • [schema-version] workspaces/boost/plugins/boost-backend/src/config/schemas.ts:55BOOST_CONFIG_SCHEMA_VERSION remains at 1 despite removing three config keys and adding four new ones. The comment on line 49 says "increment when fields change." While the renamed fields are all yaml-only (not DB-stored), the documented convention calls for incrementing on any field change.
    Remediation: Consider incrementing BOOST_CONFIG_SCHEMA_VERSION to 2.

  • [insecure-transport] workspaces/boost/plugins/boost-backend/src/config/schemas.ts:168 — The tokenEndpoint field uses z.string().url() validation which accepts any URL scheme including http://. A misconfigured token endpoint using HTTP would send clientSecret credentials in cleartext. This is a deployment concern rather than a code vulnerability, but a schema-level restriction to HTTPS (or a warning log in non-development environments) would be a defense-in-depth measure.
    Remediation: Consider restricting tokenEndpoint to https:// via a Zod refinement in production security modes, or logging a warning when HTTP is used.


Labels: PR modifies boost workspace auth configuration and security specifications.

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 26, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:55 PM UTC · Completed 3:06 PM UTC
Commit: 71cfae4 · View workflow run →

…schema version bump

- staged-issues.md: update issue redhat-developer#3309 entry from TokenExchangeManager/RFC 8693
  to KeycloakTokenManager/Client Credentials Grant
- platform-operations-deployment.md: update Kagenti auth config key references
- platform-operations-deployment/tasks.md: update task 2.5 config field list
- schemas.ts: bump BOOST_CONFIG_SCHEMA_VERSION from 1 to 2

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gabemontero

Copy link
Copy Markdown
Contributor Author

Review findings triage

Addressed the findings from the fullsend review agent (comment).

Fixed (commit 8cb43ef)

All 3 medium findings — stale references to the abandoned RFC 8693 / TokenExchangeManager approach that we missed in the initial commit:

# File What changed
M1 staged-issues.md Updated issue #3309 entry: tasks 7.1–7.7 now describe KeycloakTokenManager / Client Credentials Grant instead of TokenExchangeManager / RFC 8693
M2 specifications/prd/platform-operations-deployment.md Kagenti Auth config keys updated from tokenExchange.enabled, audience, userTokenHeadertokenEndpoint, clientId, clientSecret, tokenExpiryBufferSeconds
M3 openspec/changes/platform-operations-deployment/tasks.md Task 2.5 updated from "tokenExchange" → "Keycloak service-account auth"

Also bumped BOOST_CONFIG_SCHEMA_VERSION from 1 → 2 in schemas.ts (review finding L7) since the config shape changed.

Deferred (low-priority schema hardening)

The remaining low findings (L1–L6) are schema hardening suggestions — tighter Zod constraints, .regex() on clientId, .min(1) guards, etc. These are reasonable improvements but not correctness issues and can be addressed in a follow-up when we implement the actual KeycloakTokenManager class (issue #3309 task 7.1). The current schemas match the validation level of all other fields in the registry.

@fullsend-ai-review

Copy link
Copy Markdown

/fs-review

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 26, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:10 PM UTC · Completed 3:26 PM UTC
Commit: 71cfae4 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment workspace/boost Boost workspace (Backstage AI plugin) security labels Jun 26, 2026
…tch new auth approach

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 26, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:40 PM UTC · Completed 3:54 PM UTC
Commit: 71cfae4 · View workflow run →

…larity, task wording

- access-control/spec.md: add max-1-retry constraint to 401 scenario
- security-safety-governance/design.md: add retry limit to Decision 4
- security-safety-governance/tasks.md: task 7.5 'single 401 retry'
- staged-issues.md: mirror task 7.5 update
- pluggable-ai-platform-architecture/design.md: clarify per-user token
  exchange is deferred, not covered elsewhere

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fullsend-ai-review

Copy link
Copy Markdown

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 26, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:57 PM UTC · Completed 4:11 PM UTC
Commit: 71cfae4 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

Comment thread workspaces/boost/plugins/boost-backend/src/config/schemas.test.ts
Comment thread workspaces/boost/plugins/boost-backend/src/config/schemas.ts
Comment thread workspaces/boost/plugins/boost-backend/src/config/schemas.ts
@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed requires-manual-review Review requires human judgment labels Jun 26, 2026
…piryBuffer tests

- Remove .default(60) from Zod schema — raw config resolution bypasses
  Zod defaults, so the consumer (KeycloakTokenManager) must apply its
  own fallback. The description already documents the default.
- Add tests: valid integers (0, 120), reject negative, reject float,
  undefined returns undefined (consumer applies default).
- Regenerate report.api.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 26, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:20 PM UTC · Completed 4:31 PM UTC
Commit: 71cfae4 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge and removed requires-manual-review Review requires human judgment labels Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-merge All reviewers approved — ready to merge security workspace/boost Boost workspace (Backstage AI plugin)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant