-
Notifications
You must be signed in to change notification settings - Fork 109
docs(augment): add OpenSpec proposal for fine-grained Backstage permissions #3331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
gabemontero
wants to merge
4
commits into
redhat-developer:main
Choose a base branch
from
gabemontero:finer-grained-permissions-proposal
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
3815407
docs(augment): add OpenSpec proposal for fine-grained Backstage permi…
gabemontero 0d75e75
docs(augment): address PR review feedback on fine-grained permissions…
gabemontero 8af0788
docs(augment): expand permissions scope and make admin fallback opt-in
gabemontero 46344d2
docs(augment): apply PatAKnight PR review feedback on permissions pro…
gabemontero File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
2 changes: 2 additions & 0 deletions
2
workspaces/augment/openspec/changes/fine-grained-backstage-permissions/.openspec.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| schema: spec-driven | ||
| created: 2026-06-08 |
68 changes: 68 additions & 0 deletions
68
workspaces/augment/openspec/changes/fine-grained-backstage-permissions/design.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| # Design: Fine-Grained Backstage Permissions for Augment Lifecycle Governance | ||
|
|
||
| ## Context | ||
|
|
||
| The augment plugin currently enforces 12+ authorization decisions via custom inline guards in route handlers — `checkIsAdmin`, `createdBy` comparisons, lifecycle stage checks, and self-approval prevention. These bypass Backstage's permission framework entirely. Only two coarse permissions exist (`augment.access` for read, `augment.admin` for admin operations), making fine-grained RBAC impossible. Additionally, all infrastructure operations (vector stores, documents, MCP connections, prompts, models) are gated by the single `augment.admin` permission, preventing deployers from granting targeted access to specific resource categories. | ||
|
|
||
| Backstage provides a permission framework (`@backstage/plugin-permission-node`) supporting basic permissions, resource-based permissions with conditional rules, and policy evaluation via the permission backend. The `extensions-backend` plugin in this workspace already follows this pattern and serves as a reference implementation. | ||
|
|
||
| These permissions are independent of AgenticProvider authorization. Kagenti's per-user RBAC (via RFC 8693 token exchange) and OpenAI's organization RBAC govern access to external AI services. The permissions defined here govern access within the augment plugin's own governance system — lifecycle state transitions, ownership-scoped operations, self-approval prevention, and visibility filtering. The two layers operate on different verbs, different resources, and different data stores. | ||
|
|
||
| ## Goals / Non-Goals | ||
|
|
||
| **Goals:** | ||
|
|
||
| - Replace all 12+ inline authorization decisions with Backstage permission framework calls | ||
| - Enable deployers to configure fine-grained RBAC policies for agent and tool lifecycle operations and targeted access to infrastructure resources (vector stores, documents, MCP connections, prompts, models) | ||
| - Provide an opt-in legacy fallback (`augment.permissions.legacyAdminFallback`) so existing deployments using `augment.access` + `augment.admin` can migrate incrementally | ||
| - Support conditional permission rules for ownership checks, self-approval prevention, and lifecycle stage gating | ||
| - Keep self-approval prevention as defense-in-depth (permission rule supplements hard-coded check) | ||
|
|
||
| **Non-Goals:** | ||
|
|
||
| - Modifying `augment.access` or `augment.admin` behavior or semantics (though `augment.admin` is no longer the default gate for lifecycle/infrastructure operations) | ||
| - Adding permissions for AgenticProvider-level operations (Kagenti API calls, OpenAI API calls) | ||
| - Frontend permission checks (backend-only enforcement) | ||
| - Custom permission policy plugins (works with standard Backstage RBAC) | ||
| - Removing the hard-coded self-approval prevention check (kept as defense-in-depth alongside the `IS_NOT_CREATOR` rule) | ||
|
|
||
| ## Decisions | ||
|
|
||
| ### Decision 1: Separate resource types for agents and tools | ||
|
|
||
| Define `augment-agent` and `augment-tool` as distinct resource types rather than a single `augment-resource` type. Agents and tools have different route handlers, different lifecycle transitions, and deployers may want independent RBAC scoping. | ||
|
|
||
| **Alternative considered:** Single `augment-resource` type with a discriminator field. Rejected because it conflates two domain objects that are independently routed and independently targetable by policy. | ||
|
|
||
| ### Decision 2: Opt-in legacy fallback to `augment.admin` | ||
|
|
||
| When `augment.permissions.legacyAdminFallback` is enabled in the plugin config, `authorizeLifecycleAction` checks the fine-grained permission first. If DENY, it falls back to checking `augment.admin`. When the config flag is absent or false (the default), only fine-grained permissions are evaluated — no fallback occurs. | ||
|
|
||
| **Why opt-in rather than default-on:** The augment plugin is in dev preview, which means no backward compatibility guarantees. Making the fallback default-on risks the "temporary becomes permanent" problem — once deployments rely on `augment.admin` as a catch-all, removing the fallback later becomes the very breaking change it was meant to prevent. Opt-in gives existing consumers a migration path (enable the flag, then incrementally adopt fine-grained policies, then disable the flag) without baking the fallback into every deployment's baseline. | ||
|
|
||
| **Alternative considered:** Default-on fallback for all deployments. Rejected because it creates invisible load-bearing behavior that becomes difficult to remove — every release the fallback ships as default makes removal harder, not easier. Dev preview is the right time to establish the clean model. | ||
|
|
||
| **Alternative considered:** OR-combining fine-grained and admin in a single policy evaluation. Rejected because Backstage's permission framework evaluates permissions individually — the fallback must be explicit in code. | ||
|
|
||
| ### Decision 3: Self-approval prevention as defense-in-depth | ||
|
|
||
| The `IS_NOT_CREATOR` permission rule enables RBAC-level self-approval prevention, but the existing hard-coded check (`createdBy !== userRef`) is retained. The hard-coded check is the primary guard; the permission rule allows deployers to customize or relax the policy via RBAC if desired. | ||
|
|
||
| **Alternative considered:** Removing the hard-coded check entirely and relying solely on the permission rule. Rejected because a misconfigured RBAC policy could silently disable self-approval prevention — defense-in-depth is safer for a governance control. | ||
|
|
||
| ### Decision 4: Visibility filtering via resource-based permission with 3-tier evaluation | ||
|
|
||
| `augment.agent.list` is a resource-based permission with resource type `augment-agent`, supporting 3-tier evaluation: ALLOW shows all agents, DENY shows no agents, CONDITIONAL applies the returned conditions as filters against each agent in the list (e.g., `IS_OWNER` returns only the user's own agents, `HAS_LIFECYCLE_STAGE(published)` returns only published agents). This aligns with the orchestrator plugin's existing CONDITIONAL policy patterns and gives deployers full flexibility over visibility rules via RBAC configuration. | ||
|
|
||
| **Alternative considered:** Basic (non-resource) permission with binary ALLOW/DENY where DENY hardcodes a "published + own" filter. Rejected because (a) DENY-means-filtered is non-standard permission semantics — deployers expect DENY to mean no access, (b) the hardcoded filter cannot be customized per-deployment, and (c) the performance cost of evaluating conditions per-agent is comparable to the hardcoded filter iteration we'd be doing anyway. | ||
|
|
||
| ### Decision 5: Permission rules modeled on extensions-backend pattern | ||
|
|
||
| Permission rules (`IS_OWNER`, `IS_NOT_CREATOR`, `HAS_LIFECYCLE_STAGE`) follow the exact pattern from `extensions-backend/src/permissions/rules.ts` — same `createPermissionRule` API, same `createPermissionResourceRef` for resource loading, same conditional evaluation utilities. This keeps the codebase consistent and reduces review friction. | ||
|
|
||
| ## Risks / Trade-offs | ||
|
|
||
| - **Policy migration complexity** → Deployers must configure fine-grained RBAC policies. Existing deployments can enable `augment.permissions.legacyAdminFallback` to preserve current `augment.admin` behavior during migration. | ||
| - **Conditional evaluation overhead** → Resource-based permissions require loading the resource to evaluate conditions. Mitigated by keeping list operations as basic permissions and only using resource-based permissions for mutation routes where the resource is already loaded. | ||
| - **Rule mismatch on upgrade** → If a deployer configures a fine-grained policy with a `HAS_LIFECYCLE_STAGE` condition referencing a stage name that changes, the rule silently denies. Mitigated by documenting stage names as part of the permission contract. | ||
| - **Defense-in-depth dual check** → The self-approval hard-coded check and `IS_NOT_CREATOR` rule are redundant by design. If one is relaxed without updating the other, behavior may be confusing. Mitigated by documenting this clearly. | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.