diff --git a/docs-site/content/kagent/1.x/about/agent-substrate.md b/docs-site/content/kagent/1.x/about/agent-substrate.md index f5cf26fc..7aa9fe85 100644 --- a/docs-site/content/kagent/1.x/about/agent-substrate.md +++ b/docs-site/content/kagent/1.x/about/agent-substrate.md @@ -29,7 +29,7 @@ kagent names each atespace after the Kubernetes namespace of the AgentInstance w ## Sandboxing -Because an Actor often runs a model-directed agent that calls tools and executes commands, Substrate runs each Actor in an isolated sandbox rather than a plain container. A WorkerPool's `sandboxClass` field selects the sandbox technology for its Workers: [gVisor](https://gvisor.dev) or a micro-VM technology such as [Kata Containers](https://katacontainers.io). Both technologies isolate an Actor from its Worker's host kernel, and both support suspend and resume operations. +Because an Actor often runs a model-directed agent that calls tools and executes commands, Substrate runs each Actor in an isolated sandbox rather than a plain container. A WorkerPool's `sandboxClass` field selects the sandbox technology for its Workers: [gVisor](https://gvisor.dev), or a micro-VM that runs the workload under [Cloud Hypervisor](https://www.cloudhypervisor.org) with a [Kata Containers](https://katacontainers.io) kernel and root image. Both technologies isolate an Actor from its Worker's host kernel, and both support suspend and resume operations. kagent compiles every ActorTemplate to the `gvisor` class, so a kagent agent runs in a {{< gloss "gVisor" >}}gVisor{{< /gloss >}} sandbox today and the micro-VM class is a Substrate capability that kagent does not yet select. Keep a WorkerPool that backs kagent Harnesses on `gvisor`. For what each class isolates, see [Sandboxing]({{< link path="substrate-runtime/sandboxing" >}}). diff --git a/docs-site/content/kagent/1.x/about/core-concepts.md b/docs-site/content/kagent/1.x/about/core-concepts.md index 4bfaa60d..98fde568 100644 --- a/docs-site/content/kagent/1.x/about/core-concepts.md +++ b/docs-site/content/kagent/1.x/about/core-concepts.md @@ -105,6 +105,6 @@ Actors are the reason why AgentInstances can suspend and resume cheaply instead An AgentTemplate's tools are not limited to MCP servers. A tool binding can also point at another AgentTemplate, which lets one agent hand work to a specialist agent. -Every agent-tool binding carries an isolation mode, and `Shared` is the only mode that kagent implements today. A `Shared` binding runs the bound agent inside its parent's Actor, so the nesting costs no extra compute and the two agents share one sandbox. The schema also accepts `Dedicated`, which would give the bound agent an Actor of its own, but a binding that sets it fails to compile and the pair never becomes ready. For both modes and the state of `Dedicated`, see [Shared and Dedicated isolation]({{< link path="skills-and-mcp/about-tools#shared-and-dedicated-isolation" >}}). +Every agent-tool binding carries an isolation mode. kagent implements only `Shared`, which runs the bound agent inside its parent's Actor, so the two agents share one sandbox and the nesting creates no second Actor. The schema also accepts `Dedicated`, a mode that would give the bound agent an Actor of its own, but a binding that sets it fails to compile and the pair never becomes ready. For details about both modes, see [Shared and Dedicated isolation]({{< link path="skills-and-mcp/about-tools#shared-and-dedicated-isolation" >}}). -Because a `Shared` binding nests one agent inside another's runtime boundary, the compiler constrains the shape of the resulting tree. Nesting stops at one level, so a bound agent cannot itself bind a third. That cap keeps the model predictable: every agent runs either in its own Actor or in the Actor of the agent that bound it, so working out where any agent runs is never more than a single step. For the rest of the rules that a tree must satisfy, see [What a Shared tree allows]({{< link path="skills-and-mcp/about-tools#what-a-shared-tree-allows" >}}). +Because a `Shared` binding nests one agent inside another's runtime boundary, the compiler constrains the shape of the resulting tree. Nesting stops at one level: a bound agent cannot bind a third. That cap keeps the model predictable, because every agent runs either in its own Actor or in the Actor of the agent that bound it, never deeper. For the remaining rules that a tree must satisfy, see [What a Shared tree allows]({{< link path="skills-and-mcp/about-tools#what-a-shared-tree-allows" >}}). diff --git a/docs-site/content/kagent/1.x/reference/release-notes/1.0.md b/docs-site/content/kagent/1.x/reference/release-notes/1.0.md index c9000f63..7537d78f 100644 --- a/docs-site/content/kagent/1.x/reference/release-notes/1.0.md +++ b/docs-site/content/kagent/1.x/reference/release-notes/1.0.md @@ -19,7 +19,7 @@ The runtime moved off Kubernetes Deployments, so the cost of an idle agent and t - **Each conversation runs as an {{< gloss "Actor" >}}Actor{{< /gloss >}} on {{< gloss "Agent Substrate" >}}Agent Substrate{{< /gloss >}}**, rather than as a long-lived Deployment that serves every conversation for one agent. - **Conversations suspend between turns.** When a turn ends, Agent Substrate writes the Actor's memory and filesystem to a {{< gloss "Snapshot" >}}snapshot{{< /gloss >}} and releases the {{< gloss "Worker" >}}Worker{{< /gloss >}} that hosted it. The next message restores that snapshot onto whichever Worker is free. For the lifecycle, see [Suspend and resume]({{< link path="substrate-runtime/suspend-and-resume#suspension-between-turns" >}}). -- **Every agent runs in a gVisor sandbox by default.** 0.10.x relied on a Kubernetes `securityContext` to constrain an agent, and 1.0 provides process, network, and filesystem isolation without per-agent security context configuration. For what the sandbox separates, see [Sandboxing]({{< link path="substrate-runtime/sandboxing#sandbox-classes" >}}). +- **Every agent runs in a gVisor sandbox by default.** 0.10.x relied on a Kubernetes `securityContext` to constrain an agent, and 1.0 provides process, network, and filesystem isolation without per-agent security context configuration. For what the sandbox separates, see [Sandboxing]({{< link path="substrate-runtime/sandboxing#what-the-sandbox-separates" >}}). ### How you declare an agent diff --git a/docs-site/content/kagent/1.x/setup/installation.md b/docs-site/content/kagent/1.x/setup/installation.md index 8aabc446..8d2d4cc8 100644 --- a/docs-site/content/kagent/1.x/setup/installation.md +++ b/docs-site/content/kagent/1.x/setup/installation.md @@ -15,7 +15,7 @@ author: kagent.dev 1. Install the following CLI tools. * [`helm`](https://helm.sh/docs/intro/install/), the Kubernetes package manager. Use Helm 3. * [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl), the Kubernetes command line tool. - * [`jq`](https://jqlang.org/download/), to read the root certificate out of the generated CA pool. + * [`jq`](https://jqlang.org/download/), to read the cluster's token issuer and the root certificate out of the generated CA pool. * [`openssl`](https://www.openssl.org), to convert that certificate to PEM format. * [`kubectl-ate`](https://github.com/kagent-dev/substrate/releases), the Agent Substrate command line tool, published as a `kubectl` plugin with each Agent Substrate release. ```bash @@ -128,18 +128,22 @@ Deploy the Agent Substrate control plane and data plane into the `ate-system` na --from-literal=ca.crt="${actor_id_ca_root}" ``` -6. Create the authentication configuration. The `kubernetes` provider accepts Kubernetes ServiceAccount tokens that are issued for the Agent Substrate API server audience. +6. Create the authentication configuration. The `kubernetes` provider accepts Kubernetes ServiceAccount tokens that are issued for the Agent Substrate API server audience. Kubernetes distributions advertise different issuers, so read the issuer from the cluster rather than naming one. An issuer that does not match the cluster's own is accepted when you create the ConfigMap, and surfaces later as `token issuer ... not trusted` on every `kubectl ate` call. ```bash + k8s_issuer="$(kubectl get --raw /.well-known/openid-configuration | jq -r .issuer)" + kubectl create configmap ate-api-authentication -n ate-system \ - --from-literal=authentication.yaml='actorIdentityJWTProvider: kubernetes + --from-literal=authentication.yaml="actorIdentityJWTProvider: kubernetes jwtProviders: - name: kubernetes - issuer: https://kubernetes.default.svc + issuer: ${k8s_issuer} audiences: [api.ate-system.svc] certificateAuthorityFile: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt discoveryTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token - ' + " ``` + > [!NOTE] + > An in-cluster issuer, such as `https://kubernetes.default.svc` or `https://kubernetes.default.svc.cluster.local`, publishes no discovery document that a public client can reach, so `certificateAuthorityFile` and `discoveryTokenFile` point the API server at its own projected ServiceAccount certificate authority and token. Omit both lines on a cluster that advertises an external issuer, such as a GKE cluster. `certificateAuthorityFile` replaces the client's root certificate authorities with the cluster's own, so retaining it fails the Transport Layer Security (TLS) handshake against an external issuer. 7. Roll Agent Substrate out again so that its pods mount the identity material, and wait for them to become ready. ```bash diff --git a/docs-site/content/kagent/1.x/substrate-runtime/identity.md b/docs-site/content/kagent/1.x/substrate-runtime/identity.md index 2658f4fc..f890d9ca 100644 --- a/docs-site/content/kagent/1.x/substrate-runtime/identity.md +++ b/docs-site/content/kagent/1.x/substrate-runtime/identity.md @@ -1,14 +1,14 @@ --- title: Identity -description: Understand how kagent authenticates callers, scopes an AgentInstance to its creator, and how Agent Substrate identifies its own components. +description: Understand how kagent resolves a caller's identity, scopes an AgentInstance to its creator, and how Agent Substrate identifies its own components. weight: 30 author: kagent.dev --- -A {{< reuse "kagent-docs/snippets/name-product.md" >}} installation authenticates three different kinds of caller, and each one is handled by a different system. This page describes what each layer establishes, and what it does not. +A {{< reuse "kagent-docs/snippets/name-product.md" >}} installation identifies three different kinds of caller, and each one is handled by a different system. This page describes what each layer establishes, and what it does not. - An operator applying a {{< gloss "Harness" >}}Harness{{< /gloss >}} is authenticated by [Kubernetes](#the-kubernetes-plane). -- A caller creating or talking to an {{< gloss "AgentInstance" >}}AgentInstance{{< /gloss >}} is authenticated by [kagent's own gRPC API](#the-kagent-plane). +- A caller creating or talking to an {{< gloss "AgentInstance" >}}AgentInstance{{< /gloss >}} is assigned a principal by [kagent's own gRPC API](#the-kagent-plane). - The components inside [Agent Substrate](#the-agent-substrate-plane) authenticate each other. ## The Kubernetes plane @@ -18,24 +18,27 @@ Harness and AgentTemplate are Kubernetes custom resources, so Kubernetes role-ba kagent's gRPC API reaches the same two resources by a second path. The AgentTemplate service creates, updates, and deletes AgentTemplates, and the Harness service creates and deletes Harnesses, both through the kagent controller. The `kagent apply -f` command calls the AgentTemplate service, and any client that reaches the gRPC endpoint can call either service. The controller writes these resources with its own service account rather than the caller's, so Kubernetes RBAC never evaluates the caller. The kagent plane authorizes this path instead. > [!WARNING] -> Because the open source build's authorizer permits every check, any caller that reaches the gRPC endpoint can author an agent's runtime and behavior, whatever their Kubernetes permissions are. Do not expose port `8083` outside the cluster. +> The open source build neither authenticates nor authorizes this path: the authenticator it installs admits every request, and the authorizer it installs permits every check. Any caller that reaches the gRPC endpoint can author an agent's runtime and behavior, whatever their Kubernetes permissions are. Do not expose port `8083` outside the cluster. For the identity that the open source build gives an anonymous caller, see [The kagent plane](#the-kagent-plane). A Harness's `allowedAgentTemplates` selector adds a second, narrower control on top of RBAC. Whoever holds edit access on a Harness decides which AgentTemplates that Harness admits. In this way, RBAC governs who can write the resources, and the selector governs which pairs can run. For more information on the one-way match, see the [Harness core concept]({{< link path="about/core-concepts/#harness" >}}). ## The kagent plane -An AgentInstance is not a Kubernetes resource. kagent's gRPC API creates the AgentInstance and kagent's database tracks it, so Kubernetes RBAC does not reach it. kagent authenticates these calls itself. +An AgentInstance is not a Kubernetes resource. kagent's gRPC API creates the AgentInstance and kagent's database tracks it, so Kubernetes RBAC does not reach it. kagent resolves a principal for these calls itself. -Every call on the AgentInstance API requires an authenticated principal. A call that arrives without one is rejected as unauthenticated before any other check runs. +Every call on the AgentInstance API carries a principal. An authenticator resolves one before the request reaches the service, and a call that the authenticator declines is rejected as unauthenticated before any other check runs. + +> [!WARNING] +> The authenticator that the open source build installs declines nothing. On the gRPC API, it reads the caller's identity from the `X-User-Id` header. The same authenticator guards the controller's `/mcp` endpoint, where it also reads a `user_id` query parameter that takes precedence over the header. It verifies neither value, and it names a caller that supplies neither `admin@kagent.dev`. A caller therefore selects its own principal. Treat the principal on a call as a label that the caller chose, and restrict network access to both endpoints rather than relying on it. ### Creator ownership -kagent records a **creator** on every AgentInstance, taken from the authenticated principal that created it. That creator is then part of the database query for every read, so a caller who asks for an AgentInstance that another principal created receives a not-found response rather than a permission error. +kagent records a **creator** on every AgentInstance, taken from the principal on the call that created it. That creator is then part of the database query for every read, so a caller who asks for an AgentInstance that another principal created receives a not-found response rather than a permission error. Listing behaves the same way. A list returns the caller's own AgentInstances by default. A caller that sets the request's all-creators flag asks to widen that to every creator in the namespace, and kagent authorizes that request separately from an ordinary list. > [!IMPORTANT] -> Creator ownership is the boundary that the open source build enforces. kagent calls an authorizer before every AgentInstance operation. However, the authorizer that this build installs permits every check, so a widened list is available to any authenticated caller. Treat authentication and creator scoping as the guarantees that this build makes. +> Creator ownership separates callers in the open source build without containing them. kagent calls an authorizer before every AgentInstance operation. The authorizer that this build installs permits every check, so any caller can widen a list. The same build also lets a caller name its own principal. A caller that presents another creator's identifier reads that creator's AgentInstances. Creator scoping keeps one user's conversations out of another user's list. It is not a security boundary. ### Shares diff --git a/docs-site/playwright/provisioners/kagent-kind.sh b/docs-site/playwright/provisioners/kagent-kind.sh index f8b797c2..85149a2b 100755 --- a/docs-site/playwright/provisioners/kagent-kind.sh +++ b/docs-site/playwright/provisioners/kagent-kind.sh @@ -265,11 +265,11 @@ kubectl create secret generic actor-id-ca-certs -n ${ATE_NAMESPACE} \ run_sh "kubectl create configmap ate-api-authentication" " set -euo pipefail -# Ask the cluster for its issuer rather than assuming one. kind 1.37 advertises -# https://kubernetes.default.svc.cluster.local, while the older hardcoded value was -# https://kubernetes.default.svc; a mismatch is accepted at install time and only shows -# up later as \`token issuer ... not trusted\` on every kubectl-ate and ateapi call. -# kagent's own setup-cluster.sh derives it the same way (kagent#2763, fixed in #2770). +# Ask the cluster for its issuer rather than assuming one: kind 1.37 advertises +# https://kubernetes.default.svc.cluster.local, and a mismatch is accepted at install +# time, surfacing later as \`token issuer ... not trusted\` on every kubectl-ate and +# ateapi call. kagent's own setup-cluster.sh and the install guide both derive it +# (kagent#2763, fixed in #2770). k8s_issuer=\"\$(kubectl get --raw /.well-known/openid-configuration | jq -r .issuer)\" kubectl create configmap ate-api-authentication -n ${ATE_NAMESPACE} \ --from-literal=authentication.yaml='actorIdentityJWTProvider: kubernetes