Repository navigation
[kelos-fake-strategist] Integration: Add OIDC and Kubernetes RBAC delegation for safe shared Console access聽#1450
Description
馃 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 ate6a57fa. The entrypoint still requires--token-fileand usesrest.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 Podexecwith the server identity. Console RBAC is still a singleClusterRoleBindingthat includespods/logandpods/exec. The chart guide still says the Console has no separate user identities or per-user authorization. A tree-wide search finds noSubjectAccessReview, OIDC, OAuth2 Proxy, or impersonation code. One external fact changed. OAuth2 Proxyv7.15.5was 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-ipexemptions behind--reverse-proxy(GHSA-wr5q-7wxw-x568), and credential disclosure in OAuth callback error logs (GHSA-hhqp-vx7f-5c6m). This proposal exempts/healthzand/readyzfrom login, so I changed the example image tov7.15.5and 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 thev7.15.5image 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}/execWebSocket 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: falseWhen 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/8The 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.5or 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-ipexemptions behind--reverse-proxy(GHSA-wr5q-7wxw-x568), and credential disclosure in OAuth callback error logs (GHSA-hhqp-vx7f-5c6m). /healthzand/readyzare the only login exemptions. They are exactGETroutes 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-ipexemption 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.ioThe 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
impersonatepermission. - Do not grant end users
pods/execorpods/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 whenauth.modeis omitted orstaticToken. - 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.5or 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 exactGETmatches for/healthzand/readyz, and no--trusted-ipexemption 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:mastersis 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 needspods/log, and a denied check opens no Pod log stream. - YAML apply checks caller
get,patch, andcreatepermissions before any downstream resource call. Tests withgetandpatchallowed butcreatedenied 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}/connectand/api/sessions/{namespace}/{name}/execcheck callerget sessionsandcreate sessions/connectbefore 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-frontendcan use permitted operations there and receives 403 inteam-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 sessionsbut withoutcreate sessions/connectcan 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 subjectaccessreviewsbeyond 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/connectand generalpatch sessions, connection and IdP session revocation limits, and RoleBinding examples.
Activity
[-]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[/+][-]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[/+][-]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[/+][-][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[/+][-][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[/+]