NVIDIA / NVIDIA/OpenShell

feat: allow granting Platform Admin by OIDC subject in gateway config

Open
#2,613 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

state:triage-needed
Dominant language
Rust
Stars
8.7k
Forks
1.3k
Avg merge
2d 7h
Merged PRs (30d)
243

Description

Problem Statement

Today, granting Platform Admin privileges on an OpenShell gateway requires the IdP to assign a specific role (e.g. openshell-admin) in the JWT claims. There is no way to designate a specific OIDC subject (sub claim) as a Platform Admin directly in the gateway configuration. This creates friction when bootstrapping a new gateway (chicken-and-egg: you need an admin to configure roles, but the admin role must come from the IdP), when the IdP doesn't support custom roles, or when an operator wants gateway-local admin overrides independent of IdP role assignments.

A gateway-level admin_subjects configuration option would grant Platform Admin to specific OIDC subjects regardless of their JWT role claims, working alongside the existing role-based admin check — either mechanism grants admin access.

Technical Context

The gateway's authorization system has two layers that enforce admin status:

  1. Middleware-level RBAC (AuthzPolicy.check()) — evaluates whether an authenticated identity's roles include the configured admin_role to gate access to platform-admin-scoped gRPC methods.
  2. Handler-level workspace authorization (workspace_authz::is_platform_admin()) — called directly by gRPC handlers to bypass workspace membership checks for platform admins.

Both layers currently check only the identity's JWT roles against the configured admin_role string. The OIDC subject (sub claim) — already available on the Identity struct — is never consulted for admin status determination.

Affected Components

Component Key Files Role
Config (core) crates/openshell-core/src/config.rs OidcConfig struct — holds OIDC settings including admin_role
CLI args crates/openshell-server/src/cli.rs Clap arg definitions, env var bindings, config-file overlay logic
Server state crates/openshell-server/src/lib.rs ServerState — carries admin_role: String used by all handlers
AuthzPolicy (middleware) crates/openshell-server/src/auth/authz.rs AuthzPolicy.check() — method-level RBAC using roles
Workspace authz (handler) crates/openshell-server/src/auth/workspace_authz.rs is_platform_admin(), require_platform_admin(), authorize_workspace()
Multiplex layer crates/openshell-server/src/multiplex.rs Builds AuthzPolicy from OIDC config for the gRPC auth middleware
gRPC handlers crates/openshell-server/src/grpc/{sandbox,workspace,service,provider,policy}.rs Pass &state.admin_role to authorization functions
Inference handlers crates/openshell-server/src/inference.rs Also passes &state.admin_role to authorization functions
Helm chart deploy/helm/openshell/values.yaml, templates/gateway-config.yaml OIDC config rendered into gateway.toml
Docs docs/reference/gateway-config.mdx, docs/reference/gateway-auth.mdx Gateway config reference and auth guide

Technical Investigation

Architecture Overview

Authentication and authorization are separated per RFC 0001. Authentication providers (OIDC, mTLS, etc.) produce an Identity with subject, display_name, roles, and scopes. The authorization layer then evaluates this identity without knowing which provider authenticated the caller.

The admin check flows through two paths:

Path 1 — Middleware RBAC (authz.rs):
OidcConfig.admin_roleAuthzPolicy.admin_role (constructed in multiplex.rs:287) → AuthzPolicy.check() compares identity.roles against admin_role to gate platform-admin-scoped gRPC methods.

Path 2 — Handler workspace authorization (workspace_authz.rs):
OidcConfig.admin_roleServerState.admin_role (set in lib.rs:208) → passed as &state.admin_role to authorize_workspace() / require_platform_admin() → calls is_platform_admin(identity_roles, admin_role) which checks admin_role.is_empty() || identity_roles.iter().any(|r| r == admin_role).

Both paths have access to Identity.subject but never check it for admin determination.

Code References
Location Description
crates/openshell-core/src/config.rs:522 OidcConfig struct — #[serde(deny_unknown_fields)], holds admin_role: String
crates/openshell-core/src/config.rs:541 admin_role field with default_admin_role()"openshell-admin"
crates/openshell-server/src/lib.rs:176 ServerState.admin_role: String field
crates/openshell-server/src/lib.rs:205-208 admin_role populated from config.oidc.admin_role in ServerState::new()
crates/openshell-server/src/auth/authz.rs:30 AuthzPolicy struct with admin_role: String
crates/openshell-server/src/auth/authz.rs:75-95 AuthzPolicy.check() — role-based method authorization
crates/openshell-server/src/auth/workspace_authz.rs:170-172 is_platform_admin() — core admin check: admin_role.is_empty() || identity_roles.iter().any(|r| r == admin_role)
crates/openshell-server/src/auth/workspace_authz.rs:149-157 require_platform_admin() — called by handlers for cross-workspace ops
crates/openshell-server/src/auth/workspace_authz.rs:73-86 authorize_workspace() — checks is_platform_admin() to bypass membership
crates/openshell-server/src/multiplex.rs:287-290 AuthzPolicy construction from oidc config
crates/openshell-server/src/cli.rs:172-175 CLI --oidc-admin-role arg, env OPENSHELL_OIDC_ADMIN_ROLE
crates/openshell-server/src/cli.rs:659-673 Config file overlay applies TOML admin_role to CLI args
deploy/helm/openshell/values.yaml:305 oidc.adminRole Helm value
deploy/helm/openshell/templates/gateway-config.yaml:103-104 Renders admin_role into [openshell.gateway.oidc] TOML section
Current Behavior

The core admin determination happens in workspace_authz.rs:

fn is_platform_admin(identity_roles: &[String], admin_role: &str) -> bool {
    admin_role.is_empty() || identity_roles.iter().any(|r| r == admin_role)
}

When admin_role is empty (OIDC not configured or auth-only mode), every authenticated user is treated as platform admin. Otherwise, the user must have the admin_role in their JWT roles claim.

At the middleware level, AuthzPolicy.check() similarly compares identity.roles against admin_role to determine if the caller can access platform-admin-scoped methods. Admin role holders implicitly satisfy user-role requirements.

There are ~30+ call sites across grpc/sandbox.rs, grpc/workspace.rs, grpc/service.rs, grpc/provider.rs, grpc/policy.rs, and inference.rs that pass &state.admin_role to authorize_workspace(), authorize_sandbox_workspace(), or require_platform_admin().

What Would Need to Change

Config layer (openshell-core): Add admin_subjects: Vec<String> to OidcConfig. The deny_unknown_fields serde attribute means this must be an explicit field.

CLI layer (openshell-server/cli.rs): Add --oidc-admin-subjects CLI arg with OPENSHELL_OIDC_ADMIN_SUBJECTS env var. Add config-file overlay logic in the same pattern as admin_role.

Server state (openshell-server/lib.rs): Add admin_subjects field to ServerState, populated from config.oidc.admin_subjects in ServerState::new().

Middleware RBAC (authz.rs): Add admin_subjects: HashSet<String> to AuthzPolicy. Modify check() to also grant admin access when identity.subject is in admin_subjects.

Handler authorization (workspace_authz.rs): Modify is_platform_admin() signature to accept the caller's subject and the admin_subjects set. Update require_platform_admin(), authorize_workspace(), and is_platform_admin_principal() accordingly.

Call site churn mitigation: The ~30+ call sites that pass &state.admin_role would need updating. A recommended approach: introduce an AdminPolicy struct (or similar) that bundles admin_role and admin_subjects, then change ServerState to carry admin_policy: AdminPolicy and update the authorization function signatures to take &AdminPolicy. This localizes the change and prevents future churn if more admin criteria are added.

Multiplex (multiplex.rs): Update AuthzPolicy construction to include admin_subjects.

Helm chart: Add adminSubjects: [] to values.yaml under server.oidc. Render as admin_subjects = ["sub1", "sub2"] in the gateway-config.yaml template.

Docs: Update docs/reference/gateway-config.mdx and docs/reference/gateway-auth.mdx for the new config field, CLI flag, and env var.

Alternative Approaches Considered
  1. Flat approach — add admin_subjects as a sibling field to admin_role in all signatures. Simpler to implement but results in ~30+ call-site changes and makes future admin-criteria additions equally expensive.

  2. AdminPolicy struct — bundle admin_role + admin_subjects into one struct, thread that through instead of separate fields. More upfront work but reduces call-site churn to a type change and prevents future N+1 field threading. Recommended.

  3. Synthetic role injection — when the subject matches admin_subjects, inject the admin_role into the identity's roles list at the authentication layer, before authorization runs. This would require zero changes to the authorization layer but mixes concerns: the auth layer would be modifying identity claims, which could confuse audit logging and GetCurrentUser responses. Not recommended.

Patterns to Follow
  • Config fields: Follow the admin_role pattern — #[serde(default)] with a default function, matching CLI arg + env var + config-file overlay.
  • CLI env vars: Use OPENSHELL_OIDC_ADMIN_SUBJECTS with comma-separated values (consistent with how list-type env vars are handled elsewhere).
  • Helm values: Follow the existing OIDC block pattern with conditional rendering in the template.
  • Tests: The workspace_authz.rs test module has thorough coverage of admin scenarios. New tests should follow platform_admin_bypasses_membership_check and auth_disabled_empty_admin_role_is_platform_admin patterns. authz.rs has role-based tests that should be extended for subject-based admin access.

Proposed Approach

Introduce an AdminPolicy struct that bundles the admin role name and admin subjects set, replacing the flat admin_role: String threading. Add admin_subjects to OidcConfig (config), CLI args (with env var OPENSHELL_OIDC_ADMIN_SUBJECTS), and the Helm chart. Modify is_platform_admin() and AuthzPolicy.check() to grant admin access when the caller's OIDC subject is in the configured admin_subjects set, alongside the existing role-based check. Update gateway config and auth documentation.

Scope Assessment

  • Complexity: Medium — well-scoped changes across multiple files, but the pattern is clear and mechanical
  • Confidence: High — clear path forward, no architectural unknowns
  • Estimated files to change: ~15 (config, CLI, server state, 2 auth modules, multiplex, Helm values + template, 2 docs pages, plus test updates)
  • Issue type: feat

Risks & Open Questions

  • Revocation latency: Admin subjects are static in gateway config — revoking an admin subject requires a gateway restart (or config reload if supported). Should be documented as an operational consideration.
  • Audit trail: When a user is granted admin via admin_subjects rather than IdP roles, the GetCurrentUser response won't show an admin role in roles[]. Operators may want to distinguish how admin access was granted. Consider whether the auth grant path should be surfaced (e.g. in logs or response metadata).
  • Auth-only mode interaction: When both admin_role and user_role are empty (auth-only mode), everyone is already admin. admin_subjects should be a no-op in that mode — document this.
  • CLI arg format: Comma-separated OPENSHELL_OIDC_ADMIN_SUBJECTS=sub1,sub2 or multi-value --oidc-admin-subject sub1 --oidc-admin-subject sub2? Clap supports both; comma-separated env var + multi-value CLI flag is the most ergonomic.
  • Large subject lists: For gateways with many admin subjects, a HashSet<String> is appropriate for O(1) lookup. The current role check is O(n) on roles which is fine for small role lists.

Disposition Readiness

  • State: validated
  • Assessment: The investigation identified all affected code paths, confirmed feasibility, and mapped the implementation surface. The feature is a well-scoped addition to an existing, well-tested authorization system with clear patterns to follow.
  • Missing evidence: None

Test Considerations

  • Unit tests in workspace_authz.rs: Add tests for subject-based admin grant, subject-based admin with workspace membership bypass, subject-based admin + role-based admin coexistence, and empty admin_subjects (no effect).
  • Unit tests in authz.rs: Add tests for admin_subjects granting access to platform-admin-scoped methods.
  • Config serialization tests: Ensure admin_subjects round-trips through TOML serialization/deserialization.
  • CLI integration tests: Verify OPENSHELL_OIDC_ADMIN_SUBJECTS env var and --oidc-admin-subjects CLI arg are parsed correctly.
  • Helm template tests: Extend deploy/helm/openshell/tests/gateway_config_test.yaml to verify admin_subjects rendering.
  • E2e tests (optional): The existing e2e/rust/tests/oidc_pkce.rs tests could be extended with a Keycloak user whose subject (not role) is configured as admin_subjects, but this is a stretch goal given the e2e test infrastructure required.
  • Existing test patterns: Follow the platform_admin_bypasses_membership_check and auth_disabled_empty_admin_role_is_platform_admin patterns in workspace_authz.rs. The test module creates user_principal(subject, roles) helpers that can be reused.

Created by spike investigation. Validated means the issue is ready for human disposition. A human applies state:accepted if OpenShell should pursue the work and places it on the roadmap separately. To queue unattended agent planning, a human applies agent:plan-requested; a direct request to an agent does not require that label.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with OidcConfig in crates/openshell-core/src/config.rs, then trace admin_role through cli.rs, lib.rs, auth/authz.rs, auth/workspace_authz.rs, and multiplex.rs. Use the existing workspace_authz.rs and authz.rs admin tests as the starting point, and inspect the Helm templates and gateway documentation. Done means configured OIDC subjects receive the same admin access as the configured role, with CLI, Helm, tests, and docs updated.

Written by the indexing model from the issue text.

Assessment

Tech stack
helm, rust
Domain
authentication, authorization, documentation
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.