Skip to content

[kelos-fake-strategist] Integration: Add OIDC and Kubernetes RBAC delegation for safe shared Console access聽#1450

Closed

Description

@kelos-bot

馃 Kelos Strategist Agent @gjkim42

Area: Integration Opportunities

Latest verdict

  • Status: VALID
  • Checked at: 2026-10-01T12:03:13Z
  • Evidence: The default branch is still e6a57fa, the same commit as the previous check. No PR has merged since then. The only PRs updated since then are agent-image bumps #1777, #1784, and #1785. I re-checked every cited claim at e6a57fa. The entrypoint still requires --token-file and uses rest.InClusterConfig(). Login and authentication still compare one shared token or its derived cookie. The inventory still lists the same ten kinds as the proposed user role. The terminal handler still opens Pod exec with the server identity. Console RBAC is still a single ClusterRoleBinding that includes pods/log and pods/exec. The chart guide still says the Console has no separate user identities or per-user authorization. A tree-wide search finds no SubjectAccessReview, OIDC, OAuth2 Proxy, or impersonation code. One external fact changed. OAuth2 Proxy v7.15.5 was published at 2026-10-01T09:05:48Z. Its advisories name it as the patched release for three issues: a critical skip-auth path bypass (GHSA-63jm-59jj-478j), critical client-IP spoofing of --trusted-ip exemptions behind --reverse-proxy (GHSA-wr5q-7wxw-x568), and credential disclosure in OAuth callback error logs (GHSA-hhqp-vx7f-5c6m). This proposal exempts /healthz and /readyz from login, so I changed the example image to v7.15.5 and made it the minimum version. I also required exact probe exemptions with header stripping kept on, prohibited --trusted-ip, and added these to the render-test criterion. All cited Kubernetes and OAuth2 Proxy documentation pages return HTTP 200, and the v7.15.5 image tag exists on Quay. Adjacent work keeps its own scope and is still open: #1671, #1020, #396, #1760, #1761, and #932. Searches for Console authentication, OIDC, SubjectAccessReview, and OAuth2 Proxy find only this slot. The slot is unassigned and has no comments. No clearly stronger candidate has appeared, so I am keeping this Integration Opportunities candidate.

Summary

Add an opt-in OIDC mode to the Kelos Console using an OAuth2 Proxy sidecar, then authorize every Console operation against the authenticated caller's Kubernetes RBAC permissions with SubjectAccessReview.

Keep the current static-token mode as the compatible default for local port-forwards and single-user installations. OIDC mode becomes the explicit path for shared team access: the identity provider authenticates each person, while namespace Role and RoleBinding resources remain the authorization source. Kelos should not introduce a separate user or policy database.

Problem

  • The Console binary requires a token file and constructs every Kubernetes client from the Pod's in-cluster identity (entrypoint).
  • The server stores one token and no principal. Protected API and asset routes accept the same bearer token or a derived cookie with a seven-day browser lifetime (authentication).
  • Request data selects the namespace. The Console lists ten Kelos resource kinds, including TaskPipeline, exposes resource YAML and Task logs, and shows relationships among resources (inventory and routes).
  • The same caller can list, create, apply, inspect, update, reset, suspend, resume, connect to, upload to, download from, and delete Sessions. A connected user can read retained chat, send prompts, manage pending messages and goals, transfer files, view workspace Git diffs in the Changes tab, and run direct shell commands through !COMMAND (Session client behavior, Changes tab).
  • The Console also exposes a separate Terminal tab that opens an interactive Bash or sh process in the Session agent container, with its workspace and mounted credentials. Its /api/sessions/{namespace}/{name}/exec WebSocket uses the same shared authentication (terminal handler, operator guidance).
  • The chart binds the Console ServiceAccount cluster-wide to all displayed Kelos resource kinds plus Pod get, logs, and exec (role).
  • The chart guide explicitly says the token represents one shared cross-namespace user and that the Console has no separate identities or per-user authorization (guidance).

This is an adoption boundary for a shared Kubernetes-native control plane. Anyone holding the token inherits every Console capability of the server ServiceAccount in every namespace. Offboarding requires rotating the token for everyone, operators cannot separate browse, logs, Session lifecycle, interactive exec, and attachment permissions, and requests cannot be attributed to a verified individual. Each new Console feature expands that shared authority.

OIDC alone would improve login but retain shared authorization. Kubernetes SubjectAccessReview is designed for an extension service to ask the API server whether a user may perform an operation (authorization documentation, SubjectAccessReview API). Combining the two reuses the namespace access model operators already understand.

Proposed MVP

1. Preserve static-token mode

Existing values keep working without an auth block:

consoleServer:
  enabled: true
  secretName: kelos-console-auth
  tokenKey: token
  secureCookie: false

When auth.mode is omitted, behavior remains staticToken. Static mode ignores forwarded identity headers. OIDC and static-token settings are mutually exclusive, and a failed OIDC request must never fall back to the static token.

2. Add an OAuth2 Proxy sidecar for OIDC mode

Use the upstream proxy for OIDC discovery, redirects, provider sessions, and cookie management instead of implementing the protocol in Kelos:

consoleServer:
  enabled: true
  auth:
    mode: oidc
    oidc:
      image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.5
      issuerURL: https://identity.example.com
      clientID: kelos-console
      redirectURL: https://console.example.com/oauth2/callback
      secretName: kelos-console-oidc
      clientSecretKey: client-secret
      cookieSecretKey: cookie-secret
      groupsClaim: groups
      usernamePrefix: "oidc:"
      groupsPrefix: "oidc:"
      reverseProxy: true
      trustedProxyIPs:
        - 10.0.0.0/8

The rendered deployment must establish these boundaries:

  • Kelos listens only on 127.0.0.1; OAuth2 Proxy owns the only externally reachable container port and Service target.
  • HTTP and WebSocket traffic take the same authenticated proxy path.
  • OAuth2 Proxy v7.15.5 or later is required. Its advisories name that release as the fix for a skip-auth path bypass (GHSA-63jm-59jj-478j), client-IP spoofing of --trusted-ip exemptions behind --reverse-proxy (GHSA-wr5q-7wxw-x568), and credential disclosure in OAuth callback error logs (GHSA-hhqp-vx7f-5c6m).
  • /healthz and /readyz are the only login exemptions. They are exact GET routes proxied to Kelos, so probes exercise both containers without exposing application APIs. Identity-header stripping stays enabled on exempt routes (--skip-auth-strip-headers), and no IP-based --trusted-ip exemption is rendered.
  • The proxy strips caller-supplied identity headers, emits only verified user and group headers, and does not forward access tokens, ID tokens, Authorization, or Basic-auth credentials. The relevant upstream controls are documented in the OAuth2 Proxy header options.
  • OIDC mode requires a secure, HTTP-only cookie and an HTTPS redirect URL.
  • Reverse-proxy mode requires explicit trusted proxy IPs or CIDRs and rejects catch-all ranges. The upstream project warns that this mode otherwise trusts forwarded headers from every source (proxy options).

3. Establish a fail-closed identity contract

In OIDC mode, Kelos accepts identity headers only from the loopback sidecar. It requires exactly one non-empty username and a bounded, well-formed group list. Missing, duplicate, oversized, malformed, or control-character-bearing identity values return 401 before any Kubernetes call.

Kelos applies configured non-empty prefixes before authorization. For example, IdP group system:masters becomes oidc:system:masters and cannot collide with Kubernetes-reserved identities. Prefixes beginning with system: are invalid.

Do not forward tokens or raw claims to Kelos. Structured audit events record the prefixed username, groups, action, authorization result, namespace, resource, subresource, and name. They must not log cookies, credentials, raw claims, prompts, transcripts, command text or output, workspace diffs, Task logs, or attachment contents.

4. Authorize each operation with SubjectAccessReview

For every protected operation, submit authorization.k8s.io/v1 SubjectAccessReview with the verified username and groups plus the exact verb, API group, resource, subresource, namespace, and name. Parse the target once and reuse it for authorization and execution.

Do not cache decisions in Kelos in the MVP. Check every new protected HTTP request and WebSocket handshake against the API server; RoleBinding changes apply to subsequent checks once the API server observes them. Return 403 for denial and 503 for review transport or evaluation failure. Neither response may perform the downstream API, log, attachment, or Pod-exec action.

An established WebSocket is authorized for its connection lifetime; automatic disconnection on permission changes is outside this MVP. IdP account disablement or group removal also depends on the proxy session refresh and expiry behavior. OAuth2 Proxy retains authentication session state between requests, so fresh Kubernetes authorization checks alone do not imply immediate IdP offboarding. Document these limits and the configured session lifetime explicitly.

Console operation Required authorization attributes
Inventory and relationship graph list each displayed kelos.dev resource kind in the active namespace; fetch only allowed kinds and omit relationships whose source or target kind is denied
Read resource YAML get the exact kelos.dev resource, namespace, and name
Read Task logs get tasks and get tasks/logs for the exact namespace and name
List Session summaries list sessions in the active namespace
Load Session creation options list sessions, list workspaces, and list agentconfigs; authorize all three before fetching any
Read or clone a Session source get sessions for the exact namespace and name
Create through the form create sessions in the target namespace
Server-side apply YAML get sessions and patch sessions for the parsed namespace and name, plus create sessions in the target namespace; require all three for every apply in the MVP
Change section or display name get sessions and patch sessions for the exact namespace and name
Delete delete sessions for the exact namespace and name
Reset get sessions and create sessions/reset for the exact namespace and name
Suspend get sessions and create sessions/suspend for the exact namespace and name
Resume get sessions and create sessions/resume for the exact namespace and name
Open or observe interactive chat (/connect) get sessions and create sessions/connect for the exact namespace and name
Open or reconnect an interactive terminal (/exec) get sessions and create sessions/connect for the exact namespace and name
Upload an attachment get sessions and create sessions/attachments for the exact namespace and name
Download an attachment get sessions and get sessions/attachments for the exact namespace and name

tasks/logs, sessions/connect, sessions/reset, sessions/suspend, sessions/resume, and sessions/attachments are synthetic authorization subresources evaluated by Kubernetes RBAC; they do not need to become served CRD endpoints. This separates Console endpoint permissions without granting human users generic pods/log or pods/exec access.

YAML apply can create a missing Session as well as update an existing one. Kubernetes requires create permission when server-side apply creates a resource. Because the Console executes apply with its own ServiceAccount, the MVP must require caller get, patch, and create authorization before every YAML apply, including updates to existing Sessions. This deliberately makes the Console apply endpoint stricter for update-only users, avoids an existence-check race, and prevents its ServiceAccount from supplying create authority the caller lacks. Document this endpoint requirement explicitly.

A caller with the YAML-apply permissions above can edit Session spec and metadata through YAML apply. It can therefore express lifecycle changes through Session.spec.suspend, the reset request annotation, and the idle-resume request annotation. The same spec authority includes spec.worker.podOverrides.serviceAccountName, which #1774 made mutable, so these callers can move an existing Session to any service account in its namespace. General Session patch/update permission also permits these lifecycle writes directly through the Kubernetes API. Operators who need lifecycle restrictions must omit general Session patch/update permissions and grant only the dedicated actions they intend. The example role below deliberately grants full Session editing and lifecycle access; synthetic subresources do not impose field-level restrictions on direct Kubernetes API writes.

sessions/connect intentionally represents complete interactive access through both the chat (/connect) and terminal (/exec) WebSockets: retained chat including provider-initiated follow-up replies, prompts, tool activity, user-input responses, pending-message and goal controls, direct shell commands, attachment metadata, workspace change snapshots, and an interactive shell in the agent container. Use the same permission for both endpoints because chat already permits direct shell commands through !COMMAND; a separate terminal permission would not isolate shell authority. Command-level authorization inside either stream is out of scope for the MVP and must not be implied.

5. Ship an unbound Console user role

OIDC mode may render a reusable ClusterRole, but must never bind a user or group automatically:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: kelos-console-user
rules:
  - apiGroups: [kelos.dev]
    resources:
      - agentconfigs
      - sessions
      - sessionspawners
      - taskbudgets
      - taskpipelines
      - taskrecords
      - tasks
      - taskspawners
      - workerpools
      - workspaces
    verbs: [get, list]
  - apiGroups: [kelos.dev]
    resources: [sessions]
    verbs: [create, patch, delete]
  - apiGroups: [kelos.dev]
    resources: [sessions/connect, sessions/reset, sessions/suspend, sessions/resume]
    verbs: [create]
  - apiGroups: [kelos.dev]
    resources: [sessions/attachments]
    verbs: [get, create]
  - apiGroups: [kelos.dev]
    resources: [tasks/logs]
    verbs: [get]

Operators bind this role, or a narrower role, only in namespaces where an IdP group should work:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: kelos-console-developers
  namespace: team-frontend
subjects:
  - kind: Group
    name: oidc:frontend-developers
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: kelos-console-user
  apiGroup: rbac.authorization.k8s.io

The Console ServiceAccount retains only the underlying Kelos and Pod permissions its handlers need and gains create on subjectaccessreviews. Human roles contain no core Pod permissions. Enabling OIDC grants no human access until an operator creates a RoleBinding.

Scope boundaries

  • Do not add or change a Kelos CRD.
  • Do not implement OIDC protocol handling in Kelos, create an Ingress, automate TLS, or add identity-provider-specific flows.
  • Do not use Kubernetes impersonation in the MVP; SubjectAccessReview delegates decisions without broad impersonate permission.
  • Do not grant end users pods/exec or pods/log.
  • Do not remove static-token mode or change its default behavior.
  • Do not present this as workload admission or sandboxing. A principal allowed to create Sessions can still select the fields exposed by Session.spec; #1020 and #396 are complementary controls.

Acceptance criteria

  • Existing consoleServer.secretName, tokenKey, secureCookie, Service, and port-forward installations render and behave exactly as today when auth.mode is omitted or staticToken.
  • OIDC mode routes all HTTP and WebSocket traffic through OAuth2 Proxy. Kelos is loopback-only, probes reach Kelos through the sidecar, and no Service or container port exposes Kelos directly.
  • Chart validation rejects missing OIDC secrets or URLs, empty or reserved identity prefixes, mixed static and OIDC settings, insecure cookies, reverse-proxy mode without explicit trusted proxy CIDRs, catch-all trusted proxy CIDRs, and partially protected deployments.
  • Render tests pin the proxy image at v7.15.5 or later and the security-relevant flags. Incoming identity headers are stripped, including on exempt routes. Only verified user and group headers are emitted. Access-token, ID-token, Authorization, and Basic-auth forwarding are disabled. The only skip-auth routes are exact GET matches for /healthz and /readyz, and no --trusted-ip exemption is rendered.
  • Tests prove caller-supplied headers cannot spoof a principal and cover missing, malformed, duplicate, oversized, and control-character-bearing username and group values. IdP group system:masters is prefixed and receives no reserved Kubernetes authority.
  • Every current Console operation has happy-path, denied-path, and authorization-error coverage: inventory and relationships, YAML detail, Task logs, Session list, options and source, form create, YAML apply, section and display-name update, delete, reset, suspend, resume, chat connect, terminal exec, attachment upload, and attachment download.
  • Inventory contains only resource kinds the caller may list. A denied kind leaks no names, counts, relationship nodes, or edges. If no displayed kinds are allowed, return 403. An authorization-service error fails the request instead of appearing as an ordinary empty or denied kind. Cover TaskPipeline inventory and YAML reads, pipeline-to-Task ownership, and pipeline-to-Workspace/AgentConfig/WorkerPool relationships with allowed and denied source/target kinds.
  • Task-log authorization checks both the Task and tasks/logs. A caller never needs pods/log, and a denied check opens no Pod log stream.
  • YAML apply checks caller get, patch, and create permissions before any downstream resource call. Tests with get and patch allowed but create denied return 403 for both an existing Session and a missing name, with no apply or resource get. Cover successful creation and update when all three checks allow, and fail closed when any review errors.
  • Both /api/sessions/{namespace}/{name}/connect and /api/sessions/{namespace}/{name}/exec check caller get sessions and create sessions/connect before fetching the Session, constructing an exec executor, or upgrading the WebSocket. Cover successful chat and terminal connections, denial of either permission, and authorization errors. Every reconnect performs fresh checks.
  • A denied or failed review makes no downstream list, get, create, patch, delete, log, attachment-transfer, or Pod-exec call.
  • A user bound only in team-frontend can use permitted operations there and receives 403 in team-backend.
  • Once the API server observes removal of the caller's relevant RoleBinding, subsequent protected HTTP requests and new WebSocket connections are denied. Tests and documentation distinguish this from existing connection lifetime and IdP session refresh/expiry; do not promise immediate offboarding.
  • A user with get sessions but without create sessions/connect can inspect Session YAML but cannot read retained chat, send a prompt, manage pending messages or goals, answer an input request, interrupt work, run !COMMAND, open or reconnect the Terminal tab, or inspect attachment metadata. Dedicated reset, suspend, resume, attachment-upload, attachment-download, and Task-log endpoints each enforce their own checks. Tests use narrowly scoped roles; do not claim lifecycle isolation from general Session patch permission or attachment-content isolation from interactive shell access.
  • The generated user ClusterRole contains no core Pod permissions and no RoleBinding is created automatically. The server role adds only create subjectaccessreviews beyond the underlying permissions required by handlers.
  • Integration or e2e coverage uses two identities with different group RoleBindings and proves namespace isolation plus successful Console inventory and relationships, Task logs, Session suspend and resume, reconnecting WebSocket chat and terminal, direct shell execution, and attachment round-trip for the allowed identity.
  • Documentation positions static-token mode for local or single-user access and OIDC mode for shared team access. It documents TLS, identity prefixes, trusted proxy CIDRs, partial inventory and relationship behavior, synthetic subresources, the full authority represented by sessions/connect and general patch sessions, connection and IdP session revocation limits, and RoleBinding examples.

Activity

  1. changed the title [-]API: Add queue-aware autoscaling to WorkerPool for burst capacity and scale-to-zero[/-] [+]API: Add task-boundary workspace reset to WorkerPool for reproducible pooled execution[/+] on Jul 15, 2026
  2. changed the title [-]API: Add task-boundary workspace reset to WorkerPool for reproducible pooled execution[/-] [+]Integration: Add OIDC and Kubernetes RBAC delegation to Session server for safe team access[/+] on Jul 17, 2026
  3. changed the title [-]Integration: Add OIDC and Kubernetes RBAC delegation to Session server for safe team access[/-] [+][kelos-fake-strategist] Integration: Add OIDC and Kubernetes RBAC delegation to Session server for safe team access[/+] on Aug 8, 2026
  4. changed the title [-][kelos-fake-strategist] Integration: Add OIDC and Kubernetes RBAC delegation to Session server for safe team access[/-] [+][kelos-fake-strategist] Integration: Add OIDC and Kubernetes RBAC delegation for safe shared web access[/+] on Aug 20, 2026
  5. changed the title [-][kelos-fake-strategist] Integration: Add OIDC and Kubernetes RBAC delegation for safe shared web access[/-] [+][kelos-fake-strategist] Integration: Add OIDC and Kubernetes RBAC delegation for safe shared Console access[/+] on Aug 21, 2026
  6. self-assigned this
    on Oct 1, 2026
  7. added
    kind/featureCategorizes issue or PR as related to a new feature
    on Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions