Skip to content

[FEATURE] User RBAC Authorization for Kagent #1270

Closed as not planned
Closed as not planned

Description

@nujragan93

📋 Prerequisites

📝 Feature Summary

Add role-based access control (RBAC) authorization for Kagent API, UI, and CLI with OIDC group integration

❓ Problem Statement / Motivation

Current Limitation

    Kagent currently lacks authorization for its UI and API. All users have full access to all resources across all namespaces. The `NoopAuthorizer` allows everything - there are no permission checks.

    ## Security Risks
    This creates security risks in multi-tenant environments where:
    - Users should only see resources in their assigned namespaces
    - Different roles need different permission levels (admins vs readonly users)
    - Sensitive data (API keys, secrets) should be masked from non-admins
    - Unauthorized users can create, modify, or delete critical resources

    ## Who Is Affected
    - **Platform teams** running Kagent in shared/multi-tenant environments
    - **Security teams** requiring audit trails and access control
    - **End users** who need read-only access without modification permissions
    - **Administrators** who need to enforce least-privilege access

    ## Why This Is Needed
    - Compliance requirements for production deployments
    - Prevent accidental or malicious resource modifications
    - Enable safe delegation of access to junior team members or contractors
    - Support enterprise OIDC/SSO integration for unified access control

💡 Proposed Solution

Overview

    Implement Casbin-based RBAC authorization enforced at the API layer (applies to UI, CLI, and direct API calls).

    ## Core Components

    ### 1. Role System
    - **2 built-in roles**: `admin` (full access) and `readonly` (read-only)
    - Support for **multiple roles per user**
    - **Default policy**: authenticated users without explicit roles get `admin` access
    - Extensible architecture for custom roles via Casbin policy configuration

    ### 2. Permission Model
    ```
    ┌─────────────────┬───────┬──────────┐
    │ Resource/Action │ admin │ readonly │
    ├─────────────────┼───────┼──────────┤
    │ Agent           │       │          │
    │   create        │ ✓     │ ✗        │
    │   get/list      │ ✓     │ ✓        │
    │   update        │ ✓     │ ✗        │
    │   delete        │ ✓     │ ✗        │
    │   invoke (chat) │ ✓     │ ✗        │
    ├─────────────────┼───────┼──────────┤
    │ ModelConfig     │       │          │
    │   create        │ ✓     │ ✗        │
    │   get/list      │ ✓     │ ✓ (masked)│
    │   update        │ ✓     │ ✗        │
    │   delete        │ ✓     │ ✗        │
    ├─────────────────┼───────┼──────────┤
    │ MCPServer       │       │          │
    │   create        │ ✓     │ ✗        │
    │   get/list      │ ✓     │ ✓        │
    │   update        │ ✓     │ ✗        │
    │   delete        │ ✓     │ ✗        │
    └─────────────────┴───────┴──────────┘
    ```

    ### 3. Authorization Architecture
    - **Casbin RBAC model** with group policy support
    - **API-level enforcement** (backend validates all requests)
    - **Secret masking** for readonly users (API keys, credentials → `****`)
    - **OIDC group integration** via group policy (`g, group:platform-team, admin`)
    - **Multi-role support** via Casbin's native grouping mechanism

    ### 4. UI Integration
    - New endpoint: `GET /api/auth/permissions` returns user's roles and capabilities
    - Permission-based UI rendering (hide/show buttons based on role)
    - Backend still enforces all permissions (UI is UX-only)

    ### 5. Policy Configuration
    **Model (model.conf):**
    ```ini
    [request_definition]
    r = sub, obj, act

    [policy_definition]
    p = sub, obj, act

    [role_definition]
    g = _, _

    [policy_effect]
    e = some(where (p.eft == allow))

    [matchers]
    m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
    ```

    **Policy (policy.csv):**
    ```csv
    # Admin permissions
    p, admin, Agent, get
    p, admin, Agent, list
    p, admin, Agent, create
    p, admin, Agent, update
    p, admin, Agent, delete
    p, admin, Agent, invoke
    # ... (full permissions for ModelConfig, MCPServer)

    # Readonly permissions
    p, readonly, Agent, get
    p, readonly, Agent, list
    p, readonly, ModelConfig, get
    p, readonly, ModelConfig, list
    p, readonly, MCPServer, get
    p, readonly, MCPServer, list

    # Group assignments (examples)
    # g, group:kagent-admins, admin
    # g, group:kagent-users, readonly
    ```

    ### 6. Role Resolution Flow
    1. User authenticates → Principal contains `User.ID`, `User.Email`, `User.Groups` (from OIDC)
    2. Casbin looks up all roles assigned to user's groups via `g` policies
    3. User receives **all matching roles** (not just first match)
    4. Permission check passes if **ANY** role grants the action
    5. If no roles match, default to `admin` role

    ## Technical Details
    - Replace `NoopAuthorizer` with `CasbinAuthorizer`
    - Policy storage: ConfigMap (static), database adapter (dynamic), or runtime (OIDC mapping)
    - Casbin caching for performance
    - Unit tests covering all role combinations

    ## Extensibility
    - Custom roles: Add policy lines (no code changes)
    - New resource types: Add to policy configuration
    - Future: Agent-to-agent authorization (Principal.Agent already available)

🔄 Alternatives Considered

    ### Alternative 1: OPA (Open Policy Agent)
    - **Pros**: Rego policy language, broader policy use cases, CNCF project
    - **Cons**: Steeper learning curve, more complex for simple RBAC, overkill for current needs
    - **Decision**: Casbin is simpler, battle-tested for RBAC, sufficient for requirements

    ### Alternative 2: Kubernetes RBAC Only
    - **Pros**: Native Kubernetes integration, no external library
    - **Cons**: Cannot control API-level actions (only CRD access), no secret masking, limited to Kubernetes resources
    - **Decision**: Need application-level authorization, not just K8s resource access

🎯 Affected Service(s)

UI Service

📚 Additional Context

Assumptions

    - Authentication (OIDC, JWT, login flow) is handled separately and provides a `Principal` with:
      - `User.ID` (from OIDC `sub` claim)
      - `User.Email` (from OIDC `email` claim)
      - `User.Groups` (from OIDC `groups` claim)
    - Roles are NOT in the Principal - they are resolved at authorization time via Casbin

    ## Out of Scope
    - Authentication implementation (OIDC provider configuration)
    - Agent-to-agent authorization (future work)
    - Audit logging (separate enhancement)
    - Namespace-based resource filtering (all resources visible, but actions restricted)

    ## Risks & Mitigations
    | Risk | Impact | Mitigation |
    |------|--------|------------|
    | Casbin policy misconfiguration | High | Unit tests for all role combinations |
    | Performance impact of authz checks | Low | Casbin has built-in caching |
    | Policy ConfigMap size limits | Low | ConfigMap supports up to 1MB |
    | Breaking existing deployments | Medium | Keep NoopAuthorizer as fallback option |

    ## Open Questions
    1. Should we support policy hot-reload without restart?
    2. Should authorization decisions be logged for compliance/audit?
    3. Should there be a UI for creating custom roles beyond `admin` and `readonly`?

🙋 Are you willing to contribute?

  • I am willing to submit a PR for this feature

Activity

  1. nujragan93 commented on Feb 11, 2026

    @nujragan93
    ContributorAuthor

    @EItanya I feel we should start by defining backend authorization interfaces in the application now, with implementations that call internal methods directly. This keeps the architecture simple, avoids unnecessary infrastructure, and minimizes latency since everything stays in-process. In the future, if requirements evolve, the same interfaces can be switched to make a network call to an external authorizer without requiring widespread refactoring across the codebase. I also feel that an external authorizer will also introduce a new single point of failure. what do you think?

  2. EItanya commented on Feb 17, 2026

    @EItanya
    Contributor

    @EItanya I feel we should start by defining backend authorization interfaces in the application now, with implementations that call internal methods directly. This keeps the architecture simple, avoids unnecessary infrastructure, and minimizes latency since everything stays in-process. In the future, if requirements evolve, the same interfaces can be switched to make a network call to an external authorizer without requiring widespread refactoring across the codebase. I also feel that an external authorizer will also introduce a new single point of failure. what do you think?

    On one hand I agree with you, but I also worry that picking a specific RBAC engine impl up front is a potentially losing battle, and by adding an extension point we allow people to put in whatever system they want, and there are many. We can of course take an opinionated approach in the Open Source like the oauth2-proxy implemented by #1293, but the flexibility means we don't need to support one specific engine going forward. What do you think?

  3. EItanya commented on Feb 17, 2026

    @EItanya
    Contributor

    Hey @nujragan93, any chance you could jump in Discord and we can continue the conversation there, I don't see you in the server currently.

  4. nujragan93 commented on Feb 17, 2026

    @nujragan93
    ContributorAuthor

    @EItanya Just joined the server, lets continue the conversation there

  5. added a commit that references this issue on Apr 9, 2026
  6. christensenjairus commented on Apr 16, 2026

    @christensenjairus

    What's the status on this?

  7. thehackercat commented on Apr 19, 2026

    @thehackercat

    Any update?

  8. danialkhatib commented on May 22, 2026

    @danialkhatib

    Any update? : )

  9. davidkarlsen commented on Jun 23, 2026

    @davidkarlsen
    Contributor

    Picking this up again, because I think the current state is a real security problem and not just a missing feature.

    Today the controller ships with NoopAuthorizer, which means every authenticated user can do everything — list, invoke, edit and delete every Agent, ModelConfig and ToolServer, across every namespace. The moment someone enables OIDC (oauth2-proxy, #1293) they quite reasonably assume access control comes with it. It doesn't. For any shared or multi-tenant install, that gap is a genuine risk, not a nice-to-have.

    The good news: the hard part is already done. The Authorizer interface is wired into every handler, and ProxyAuthenticator already hands it the full set of JWT claims. So this isn't a big refactor — it's "write one real authorizer and stop returning nil."

    On the design question raised earlier in this thread — an opinionated in-process RBAC engine vs. a pluggable extension point — I'd like to propose CEL as the default, because I think it makes both camps happy:

    • In-process, no new SPOF. cel-go is already in our dependency tree (it comes in with the k8s libraries), so there's nothing new to deploy or operate.
    • Not a hard-coded RBAC model. A policy is just an expression over the request — claims, verb, resource. Groups become one option among many rather than the mechanism: "team-a" in claims.groups, claims.role == "admin", resource.namespace == claims.dept, or whatever your IdP actually emits. That matters — pinning authz to a groups claim is too narrow; operators should be able to key off any claim.
    • Operators probably already know it. It's the same language as Kubernetes ValidatingAdmissionPolicy and CRD validation rules, and agentgateway uses CEL too — so it's consistent across the stack instead of yet another policy DSL to learn.
    • The interface stays the seam. Anyone who wants OPA or an external authz service (like External auth init #1370) can still plug it in. CEL is just the batteries-included default, not a lock-in.

    For the specific "give these people access to this agent" case: the policy can live right on the Agent CR (an annotation, or a spec.accessPolicy field), authored by whoever owns the agent — but still enforced centrally in the controller. We compile the expression once on reconcile, cache the compiled program, and surface a bad expression on status.conditions, so a misconfigured policy shows up on kubectl get agent instead of silently failing at request time.

    Worth calling out: most of this has already been prototyped. #1766 did the per-agent annotation + list filtering + A2A gating, and #1370 did the external-authorizer interface. Both stalled on inactivity rather than getting rejected. I'd rather build on them than start over — keep #1766's enforcement points, generalize allowed-groups into a CEL expression over claims, and keep #1370's interface as the external-authz escape hatch.

    If this direction sounds reasonable, I'm happy to write it up as an EP (a follow-on to EP-476, which explicitly deferred fine-grained authz) and take a run at the implementation.

    @EItanya @peterj — does "CEL as the default, behind the existing Authorizer interface" line up with where you wanted this to land?

  10. marcellodesales commented on Jul 7, 2026

    @marcellodesales

    @davidkarlsen I can be a tester... we have a private OIDC and we need this badly...

  11. elihai-risk commented on Jul 16, 2026

    @elihai-risk
    Contributor

    Adopter perspective — we're piloting kagent as a shared, multi-team agent platform, so this is directly on our critical path. A few observations that might help shape the direction, before anyone commits to an implementation:

    Our primary need is visibility scoping, not just action gating. The most common ask from our end users is "I should only see my team's agents/tool servers, not everyone else's." The current proposal explicitly puts "namespace-based resource filtering (all resources visible, but actions restricted)" out of scope, which means admin/readonly roles alone wouldn't satisfy that use case — a readonly user would still list every resource in every watched namespace. It looks like the list-filtering angle is closer to what's sketched in #1766. Is the intent to keep visibility-filtering as a separate track, or to fold it into this EP?

    Engine direction (Casbin vs. CEL). There seem to be two live directions — Casbin here, and the CEL-based proposal in #2075 (which leans on the fact that cel-go is already in the module graph and keeps Authorizer as a pluggable seam). For adopters it'd be very helpful to have a committed direction on the issue before code lands, since the two imply fairly different policy authoring/config stories. Do the maintainers have a preference, or is the pluggable-Authorizer seam (#1370) the intended contract, with Casbin/CEL as interchangeable implementations behind it?

    Default posture. The "authenticated users with no matching role default to admin" behavior would be a surprising default for us; a default-deny (or explicit opt-in fallback) would fit least-privilege expectations better in shared environments. Worth pinning down as part of the design.

    Happy to share our multi-team requirements in more detail if useful.

  12. marcellodesales commented on Jul 22, 2026

    @marcellodesales

    Here's what I'm looking for:

    • I created a separate Operator that drops in a couple of filters for the UI based on the the namespace map to a given OIDC group provided by the Oauth2-proxy component
      • That way, user's groups, determining a mapping between a list of namespaces mapped back to the OIDC groups returned...
      • A declarative mode can provide the list of mappings between the OIDC groups to the namespaces, determining which of those are Platform Engineering teams with "admin" permission to kagent itself... The list of agents already provided were great additions already provided by kagent default install
    Image

    I have found a couple of problems:

    • When the user attempts Login with OIDC, depending on the size of the groups, we can experience 400 error on nginx for the large cookie size results in failures
      • I introduced a Redis to improve on that...
    • Meanwhile, the Logout functionality is also buggy with the re-login problem when the user attempts to logout...
    • I Didn't see any front page for the unauthenticated user
      • Maybe because the intended way is to front this with agentgateway?

    Question

    How is this better managed with agentgateway? in a regular setting, I'm deploying kagent as a developer facility and where we intend to provide different Engineering groups access to their OIDC groups and, each of those groups be mapped to their namespaces... But in the end of the day, I still think agentgateway would provide the richer experience in terms of tooling and capability... Is this where this tooling is going towards?

  13. tkm112345 commented on Jul 25, 2026

    @tkm112345

    I have the same need: restrict which agents each LDAP group (via the OIDC groups claim) can see and invoke, in a shared multi-team install. +1 on prioritizing this.

  14. Rotfuks commented on Aug 6, 2026

    @Rotfuks

    Pushing this topic a bit again, because we have a need for it as well :)

    We are looking at kagent for a setup with multiple teams, where agents and sessions are shared between those teams. A few points from our side:

    I'd suggest Sessions should be part of the permission model, not only agents. We work with collaborative sessions, where several people from different teams take part in the same conversation with an agent. So we need to be able to say who can join a session, who can read its history, and who should not see it at all. The content of a session can be just as sensitive as the configuration of an agent, because it contains everything that was exchanged in it. So a team might see an Agent with all shared sessions, while team members can still decide to have private sessions with the same Agent.

    Much of the discussion here is about keeping teams separate from each other. In our case some agents and sessions are shared on purpose, so we also need a way to give a specific team access to a specific agent or session. So namespace might not be the best solution for us.

    On defaults, we would also prefer, just like @elihai-risk, that a user without an assigned role gets no access at all, instead of getting admin access. Nobody notices when access is granted by accident, which makes this very hard to defend in a security review. We are confident that existing installations can be kept working in another way, for example by making the current permissive behaviour something an operator has to switch on explicitly.

    On the choice of engine, my gut feeling is that we should not commit to one specific authorization engine here. We would rather see an interface that different engines can be plugged into, so that adopters can use the one that fits their environment. Is there a direction the maintainers are leaning towards?

    Happy to join Discord to go through our requirements in more detail, and happy to help push this forward wherever it is useful, reviewing an EP, requirements input, tests, or taking implementation work. But I think we first need some commitment from the maintainers into a direction to take, right?

  15. marcellodesales commented on Aug 27, 2026

    @marcellodesales

    I've pushed a lot of work towards agentgateway and kagent to support multiple models of:

    • Agent Runners: how to allow not only ADK Agents to run in Kagent, but Claude Code Agents in the same Kagent deployment model
    • Oauth Model: The Oauth model I think a lot of people will agree: this feature should live at the Gateway level, where we are used to when it comes to regular APIs (OpenAPI, gRPC, GraphQL)... However, I've been finding it extremely hard to move that responsiblity model at the gateway when most of the MCP servers are NOT, and have not been implemented with good design-petterns because most of the time if design Human-in-the-loop (HITL) models, then you are requiring Humans to always authenticate to those MCP servers... Since the Kagent UI provided a CHAT experience, I leaned towards moving those checks at the level of the deployment Kagent because that could be the experience where customers would be playing with, validating and sharing... but since those are A2A Agents, they could be exposed directly through the Gateway... so I found myself implementing a Redis Broker for JWT, a Slack and Buzz Broker for different Communications channels... It is something that I have been finding hard to inflate agentgateway with all of those Cross-cutting concerns but I still can't find a good user experience to place them outside in a reserved User Experience new UI because kagent provided that... So, Yes, the Authorization level I've been implementing is at the OIDC level, so anything (authentik, ....) should be compatible...
  16. tlyyxjz commented on Sep 4, 2026

    @tlyyxjz

    Following this thread since we're watching the space closely — from the Casbin side (I contribute tooling to the Casbin ecosystem), here's how the requirements discussed here map to what already exists, in case it saves the team some design time:

    The core need discussed here (per-namespace visibility + role gating + OIDC groups) is very close to Casbin's RBAC-with-domains model:

    [request_definition]
    r = sub, dom, obj, act
    
    [policy_definition]
    p = sub, dom, obj, act
    
    [role_definition]
    g = _, _, _
    
    [matchers]
    m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
    • dom maps naturally to a Kubernetes namespace; OIDC group claims become g, <user>, <role>, <namespace> assignments (or map groups to roles once at login). The multiple adopters asking for "LDAP/OIDC group → which namespaces I can see" is literally g(r.sub, p.sub, r.dom) + GetImplicitPermissionsForUser().
    • Sessions-as-permission-model (suggested by @Rotfuks) fits as additional obj types (agent, session, apikey) in the same policy table rather than a separate system.
    • Since Kagent is Go, the enforcer is an in-process library (no sidecar/service to deploy) — swapping NoopAuthorizer for a casbin-backed Authorizer interface is a small adapter, and policies can live in the existing DB via gorm-adapter.

    On tooling/testing (relevant to the testing concerns raised earlier): we built casbin-config-doctor (diagnoses policy/model mismatches) and authztest (Go test helpers: authztest.AssertAllow(t, e, "alice", "ns1", "agent", "invoke")) — useful if the team adopts policy-as-config and wants CI enforcement of expected allow/deny matrices.

    Honest caveats so you can evaluate fairly: casbin gives you decision (match request → allow/deny), not enforcement plumbing — you'd still wire the middleware, OIDC claim extraction, and data masking (masking secrets for non-admins is app-layer). And wildcard/ABAC matchers are powerful but easy to get subtly wrong, which is exactly the class of bug our doctor tool exists for.

    Question for the maintainers: is the design discussion leaning towards a pluggable authorizer interface (so casbin / OpenFGA / custom could all be implementations), or a built-in model? If pluggable, I'm happy to sketch what a casbin-based implementation of that interface would look like against the requirements in this thread.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    staleThis issue or PR has become stale

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions