kagent¶
kagent agent controller and UI for one tenant behind the OpenShift login; the platform kagent-operator provides the CRDs and the access to team namespaces
| Chart | kagent |
| Version | 0.5.1 |
| Application version | 0.10.4 |
| Kind | Application |
| Source | charts/kagent |
The values are listed under Values, the changes per version under Changelog.
Purpose¶
Runs the kagent agent controller and its web UI for one tenant in your project, behind the OpenShift login. Agents (Agent objects) in your project, and in the team projects the platform assigns to this controller, are deployed and served by it; the UI lists and chats with them.
The chart wraps the upstream kagent chart (upstream.* values) and keeps everything inside the release namespace:
kagent-login(OpenShift oauth-proxy) logs people in through the cluster OAuth server with its ServiceAccount as OAuth client and forwards to the UI. The Route points at it.- The UI and controller come from upstream; RBAC is namespaced (
upstream.rbac.namespaces), never cluster-wide. - NetworkPolicies admit the login proxy from the router only and the UI from the login proxy only. The upstream UI Route is off (
upstream.ui.route.enabled: false). login.provider: openshift(default): the controller runs in auth modeunsecureand every user of one instance acts as the same kagent user (admin@kagent.dev), so all of them see the same chat sessions. kagent takes a user identity only from a JWT, which the OpenShift login does not issue.login.provider: oidc: upstreamoauth2-proxylogs people in against an OIDC provider and the controller runstrusted-proxy, so each person has their own sessions. Setupstream.oauth2-proxy.enabled, itsextraEnv(OIDC_ISSUER_URL,OIDC_REDIRECT_URL=https://<route.host>/oauth2/callback,UPSTREAM_URL=http://kagent-ui:8080) andextraArgs.allowed-group, andupstream.controller.auth.mode: trusted-proxy; the chart refuses other combinations. The chart sets the scope, the Secretkagent-oidcand a login skip list of the login page and static assets only. Withsecrets.type: externalSecretthe chart writes that Secret,kagent-oidc, from your secret store.
Choices:
- The CRDs, the kmcp controller access, the tool servers of team namespaces and the controller's access to them belong to the platform (kagent-operator), not to this chart: kmcp, kagent-tools and the built-in agents are off.
- With
upstream.kmcp.enabled(andupstream.kmcp.rbac.create: false) the chart adds kmcp's leader-election Role; the platform grants kmcp the rest. - The controller's database is a CloudNativePG
Clusterkagent-postgresin the release namespace (database.cnpg); the controller reads its connection URL from the generated Secretkagent-postgres-app. Without the CloudNativePG operator, turndatabase.cnpg.enabledoff and use upstream's bundled PostgreSQL (development only) or your own database.
Install¶
helm repo add grncloud https://apps.openshift.eu
helm install kagent grncloud/kagent -n team-kagent \
--set 'upstream.rbac.namespaces={team-kagent}' \
--set route.host=kagent.apps.example.com
With Argo CD, set the namespace from the destination: spec.source.helm.parameters upstream.rbac.namespaces[0] = $ARGOCD_APP_NAMESPACE.
upstream.rbac.namespaces(required): The release namespace. The chart refuses to render without it, because empty gives cluster-wide RBAC.route.host(default: empty): Hostname. Empty: OpenShift assigns one. Set it at install; a tenant cannot change the hostname of an existing Route.route.labels,route.timeout(default: empty, 3600s),route.annotations(default: cert-manager issuer, used whenroute.hostis set).login.provider(default: openshift):openshiftoroidc, see Purpose.login.oidc.keycloakClient.create(default: false): also write the login client as an EDPKeycloakClientin the release namespace (realmlogin.oidc.keycloakClient.realmRef, clientIdlogin.oidc.clientId, redirecthttps://<route.host>/oauth2/callback, client secret fromkagent-oidc). On a shared realm a tenant's clientId starts with<tenant>-.login.allowedGroups(default: empty): OpenShift groups admitted. Only their members log in: the chart grants them the verbloginonservices/proxyof the login Service, whichadminandeditdo not hold. Empty: everyone who may edit the project.login.loginClusterRole(default: empty): ClusterRole holding the verbloginonservices/proxy;login.allowedGroupsare bound to it instead of to the chart's own Role. An installer without that permission (a project admin, the project's Argo CD) needs this value.login.image(default:openshift/oauth-proxy:v4.4from the cluster registry),login.port(default: 4180),login.resources.secrets.cookieSecret(default: empty): OpenShift login cookie key, generated once and kept on upgrade.secrets.type(default: kubernetes):externalSecretwithlogin.provider: oidcwrites Secretkagent-oidcthrough an ExternalSecret:secrets.externalSecret.storeKind,storeName,refreshInterval, anditems.oidcClientSecretanditems.oidcCookieSecret(remote keys whoselogin.passwordholds the value). Setupstream.oauth2-proxy.config.existingSecret: kagent-oidc.login.oidc.clientId(default: empty): Client id at the OIDC provider, written intokagent-oidc.database.cnpg.enabled(default: true): CloudNativePGClusterkagent-postgresas the controller's database. Off: setupstream.database.postgres.urlFileto empty andbundled.enabled,urlor your ownurlFile, andupstream.controller.volumesandvolumeMountsto[].database.cnpg.instances(default: 1): One instance has no PodDisruptionBudget, so a node drain restarts it; two or more get one.database.cnpg.storage.size,storage.storageClass,imageName,resources(default: 1Gi, cluster default, operator default, empty).database.cnpg.operatorNamespaceSelector(default:kubernetes.io/metadata.name: openshift-operators): Namespace of the CloudNativePG operator, admitted to the instances.networkPolicy.enabled(default: true),networkPolicy.agentNamespaceSelector(default: empty): Namespaces whose agent pods reach the controller API on port 8083.networkPolicy.agentPodSelector(default: empty, every pod of those namespaces) narrows it, for example toapp.kubernetes.io/managed-by: kagent. Withroute.hostthe router also reaches cert-manager's HTTP-01 solver.remoteMCPServers(default: empty): RemoteMCPServers in the release namespace for MCP servers of your own:name,description,url,protocol(STREAMABLE_HTTPorSSE),timeout,allowedNamespaces(default{from: Same};{from: Selector, selector: ...}lets Agents in other namespaces use it) andheaders. A header takes its value fromvaultItem(a remote key ofsecrets.externalSecret.storeName, propertyvaultProperty, defaultlogin.password, written into Secretmcp-<name>behind an optionalprefixsuch asBearer) or fromexistingSecret(name,key).examples.enabled(default: false): The tool server of the upstream example agents, RemoteMCPServerkagent-tool-server, bound toexamples.tools.clusterRole(defaultview) in the release namespace only. Switch the agents on per agent withupstream.<agent>.enabled(for examplek8s-agent,helm-agent); the chart refuses them withoutexamples.enabled. Upstream's ownkagent-toolsbinds cluster-admin and stays refused.upstream.providers(default: upstream OpenAI with Secretkagent-openai): The default model. Create the API key Secret, or pointupstream.providers.openAI.config.baseUrlat an OpenAI-compatible gateway.upstream.controller.env(default: empty): Extra controller environment, for exampleWATCH_NAMESPACESfrom a pod annotation the platform writes.
Tenant realm or shared realm¶
The login goes to the tenant's own Keycloak realm when the tenant has one, otherwise to the shared realm. The platform decides, not the chart: the tenant's Application sets three values to the same realm.
| Value | Tenant with its own realm (grncloud, realm grn.cloud) |
Tenant on the shared realm (realm public) |
|---|---|---|
upstream.oauth2-proxy.extraEnv OIDC_ISSUER_URL |
https://<keycloak>/realms/grn.cloud |
https://<keycloak>/realms/public |
login.oidc.keycloakClient |
create: true, realmRef the tenant's ClusterKeycloakRealm: the tenant owns its login client |
create: false: the platform writes the client in the shared realm, tenants cannot |
extraArgs.allowed-group |
the tenant's groups, for example grncloud-admins,grncloud-engineering |
the same groups |
Either way the claim groups holds the tenant's groups with the tenant prefix: in a tenant realm the client scope tenant-groups emits its realm roles behind the prefix (admins as grncloud-admins); in the shared realm a person brokered from the tenant realm carries the same names as realm roles. Downstream checks (allowed-group, agents' tenant token check) therefore see one shape; add the tenant realm's issuer to the agents' a2a.tenantAuth.oidcIssuers.
Who gets in¶
kagent itself checks nobody: in trusted-proxy mode the controller decodes the caller's JWT without verifying it and its authorizer allows every request. The login proxy and the NetworkPolicies are the boundary, and inside it everyone may do everything:
| Caller | Path | Admitted when |
|---|---|---|
| Person in a browser | Route, oauth2-proxy, UI, controller | Logged in at the OIDC provider and the claim groups holds one of extraArgs.allowed-group |
| API client with a bearer token | Route, oauth2-proxy | The token is issued for the client and passes the same allowed-group check (oauth2-proxy authorizes bearer sessions like cookie sessions) |
| ServiceAccount, agent pod | Controller Service, port 8083 | The pod runs in a namespace networkPolicy.agentNamespaceSelector selects |
A Kubernetes ServiceAccount token carries no groups, so it never passes the login proxy; ServiceAccounts reach the controller inside the cluster only. Select the tenant's namespaces in networkPolicy.agentNamespaceSelector by a label tenants cannot set themselves. Every person admitted can create, change and delete Agents, ModelConfigs and tool servers in every namespace the controller serves; chat sessions are per person.
Upstream's controller Roles kagent-writer-role and kagent-getter-role carry wildcard rules that a project admin cannot grant, so the chart renders them with explicit rules within a project admin's rights; the platform's kagent-operator grants the controller its full rules in every namespace of the tenant, the release namespace included.
Delivery instructions¶
openshift: people log in through the cluster's OAuth server with their OpenShift account; no login client is needed.oidc: register a confidential client with redirect URIhttps://<route.host>/oauth2/callbackwhose tokens carry the groups thatupstream.oauth2-proxy.extraArgs.allowed-groupnames.- Team projects are served when the platform assigns them: it writes the controller's watch list and binds the controller in each of them. Without that, the controller serves the release namespace only.
- The default
ModelConfigreconciles once its API key Secret exists. - Do not add a NetworkPolicy that admits the router to the UI: only the login Route may reach it.
- From 0.4.0 the chart runs upstream kagent 0.10.4: the CRDs of the platform's kagent-operator must be 0.10.4 before the upgrade.