使用 Secure Profile 的 Demo

本指南在 secure profile 下重新运行 使用 Core Profile 的快速开始 中的 weather-agent 场景——AuthBridge sidecar 会注入到 agent 中,operator 会为每个 workload 注册一个 Keycloak client,SPIRE 会签发 workload SVID,而每个请求都必须携带有效的 Bearer 令牌。tool workload 仍然以普通 Deployment 形式运行,不带 sidecar;这与上游 weather-agent demo 模式一致,并让 tool 到公共地理编码 API 的出站调用继续走 pod 的默认 egress 路径。

如果你需要一个更严格的变体——tool 上也启用 AuthBridge,并在 agent 与 tool 之间使用 RFC 8693 token exchange——请在完成本指南后继续查看 高级 Demo

前提条件

开始之前,请确保:

  1. secure-profile 依赖已安装——cert-manager、SPIRE、Keycloak、Istio ambient mesh。请参见 安装 Secure-Profile 依赖
  2. Kagenti operand 上已启用 secure profilespec.featureGates.globalEnabled: truespec.authbridgeConfig.enabled: truespec.keycloak.publicUrl 已设置)。请参见 启用 Secure Profile
  3. 集群内可访问一个 提供 OpenAI-compatible chat API 的 InferenceService。本指南在 models 中使用 qwen36-27b-gguf;请在你看到这些名称的所有位置替换为你自己的模型——有关如何解析 LLM_API_BASELLM_MODELLLM_API_KEY,请参见 快速开始中的 model-endpoint recipe
  4. 拥有目标集群的 kubectl 访问权限。

创建 demo 命名空间并让其启用 secure profile:

kubectl create namespace team1
kubectl label namespace team1 kagenti-enabled=true
WARNING

kagenti-enabled=true 标签是必需的。如果没有它,operator 不会 为该命名空间创建 authbridge-config ConfigMap,workload 的 Keycloak client 注册不会启动,并且每个 pod 都会因为缺少凭据 Secret 而一直处于 Pending。

步骤 1:部署 MCP tool server

tool workload 与 core-profile 版本 完全相同。它以普通 Deployment 运行,不使用 AgentRuntime,也不带 AuthBridge sidecar——默认情况下,tool workload 的 sidecar 注入是关闭的(spec.featureGates.injectTools: false),因此即使你尝试,仅仅为该 Deployment 添加 kagenti.io/type: tool 标签也不会注入 sidecar。其安全模型是 agent 侧的入站保护:agent 的 AuthBridge 会验证客户端的 JWT,然后 agent → tool 调用只是一次普通的集群内 HTTP 跳转。

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: weather-tool
  namespace: team1
  labels:
    app.kubernetes.io/name: weather-tool
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: weather-tool
  template:
    metadata:
      labels:
        app.kubernetes.io/name: weather-tool
    spec:
      containers:
      - name: mcp
        image: docker.io/alaudadockerhub/weather_tool:v0.1.0-rc.1
        imagePullPolicy: IfNotPresent
        env:
        - name: PORT
          value: "8000"
        - name: HOST
          value: 0.0.0.0
        - name: UV_CACHE_DIR
          value: /app/.cache/uv
        ports:
        - containerPort: 8000
        volumeMounts:
        - mountPath: /app/.cache
          name: cache
      volumes:
      - name: cache
        emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: weather-tool-mcp
  namespace: team1
spec:
  selector:
    app.kubernetes.io/name: weather-tool
  ports:
  - name: http
    port: 8000
    targetPort: 8000
EOF

kubectl rollout status deploy/weather-tool -n team1
kubectl get pod -n team1 -l app.kubernetes.io/name=weather-tool
# NAME                            READY   STATUS    RESTARTS   AGE
# weather-tool-XXXXXXXXXX-YYYYY   1/1     Running   0          1m

READY 1/1 表明即使在 secure profile 下,tool 也仍然以单容器运行——没有注入 sidecar。

agent 和 tool 镜像必须遵守

$PORT 在 secure profile 下,AuthBridge proxy-sidecar 会绑定 workload 的 service port,而 operator 会将应用重映射到不同的 PORT 值。若镜像硬编码了端口,就会与反向代理冲突(address already in use)。上游 weather_service / weather_tool 镜像从 v0.1.0 起已遵守 PORT,因此可以直接使用。

步骤 2:部署受保护的 agent

agent 的 Deployment、Service 和 AgentRuntimecore-profile 快速开始 中相同——不需要任何 secure-profile 特有字段,因为 AuthBridge webhook 会根据 Kagenti operand 和命名空间标签在准入时自动完成所有挂接工作。AgentRuntime 会触发 mutating webhook 注入 sidecar,并挂载 operator 创建的 Keycloak client Secret。

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: weather-agent
  namespace: team1
  labels:
    app.kubernetes.io/name: weather-agent
    protocol.kagenti.io/a2a: ""
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: weather-agent
  template:
    metadata:
      labels:
        app.kubernetes.io/name: weather-agent
    spec:
      containers:
      - name: agent
        image: docker.io/alaudadockerhub/weather_service:v0.1.0-rc.1
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8000
        env:
        - name: PORT
          value: "8000"
        - name: UV_CACHE_DIR
          value: /app/.cache/uv
        - name: MCP_URL
          value: http://weather-tool-mcp.team1.svc.cluster.local:8000/mcp
        - name: LLM_API_BASE
          value: http://qwen36-27b-gguf-predictor.models.svc.cluster.local/v1
        - name: LLM_API_KEY
          value: dummy
        - name: LLM_MODEL
          value: qwen36-27b-gguf
---
apiVersion: v1
kind: Service
metadata:
  name: weather-agent
  namespace: team1
spec:
  selector:
    app.kubernetes.io/name: weather-agent
  ports:
  - name: http
    port: 8000
    targetPort: 8000
---
apiVersion: agent.kagenti.dev/v1alpha1
kind: AgentRuntime
metadata:
  name: weather-agent-runtime
  namespace: team1
spec:
  type: agent
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: weather-agent
EOF

步骤 3:验证 secure wiring

确认 AuthBridge sidecar 已注入、SPIRE 已签发 SVID,并且 operator 已为该 workload 注册 Keycloak client:

# AuthBridge sidecar container present alongside the app container
kubectl get pod -n team1 -l kagenti.io/type=agent \
  -o jsonpath='{.items[0].spec.containers[*].name}{"\n"}'
# agent authbridge-proxy

# Agent pod is 2/2 (app + sidecar); tool pod is still 1/1
kubectl get pods -n team1
# NAME                             READY   STATUS    RESTARTS   AGE
# weather-agent-XXXXXXXXX-YYYYY    2/2     Running   0          1m
# weather-tool-XXXXXXXXX-YYYYY     1/1     Running   0          6m

# SPIRE X.509 SVID mirrored into the sidecar
kubectl exec -n team1 -c authbridge-proxy \
  $(kubectl get pod -n team1 -l app.kubernetes.io/name=weather-agent -o name | head -1) \
  -- ls -l /opt/svid.pem

# Keycloak client credentials Secret created by the operator and mounted at /shared/
kubectl get secret -n team1 | grep kagenti-keycloak-client
# kagenti-keycloak-client-credentials-<hash>   Opaque   2   30s

该 Secret 为 workload 动态注册的 Keycloak client(名为 team1/weather-agent)提供 client-id.txtclient-secret.txt。webhook 会将其挂载到 agent pod 的 /shared/,AuthBridge 从那里读取。

现在请求需要 token

启用 secure profile 后,向 agent 发起的 未认证 请求会被 AuthBridge jwt-validation plugin 拒绝:

{"error":"auth.unauthorized","message":"missing Authorization header","plugin":"jwt-validation"}

步骤 4:发送已认证查询

由于 workload 自身的 Keycloak 凭据 Secret 已经挂载在集群内,验证受保护路径的最简单方式是启动一个短时 pod,该 pod 会:

  1. 将相同的凭据 Secret 挂载到 /creds/
  2. 通过 Keycloak client_credentials grant 获取 access token。
  3. 带上 Authorization: Bearer <token> 发送 A2A message/send 请求。

无需 port-forwarding,且 client secret 不会离开集群。

查找 agent 的凭据 Secret 名称:

kubectl get secret -n team1 -o name | grep kagenti-keycloak-client
# secret/kagenti-keycloak-client-credentials-<hash>

从 pod 中运行已认证查询。将 <credentials-secret> 替换为上一条命令输出的名称:

kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: weather-query
  namespace: team1
spec:
  restartPolicy: Never
  containers:
  - name: query
    image: docker.io/alaudadockerhub/curl:8.1.2
    command: ["/bin/sh", "-c"]
    args:
    - |
      CID=$(cat /creds/client-id.txt)
      CSEC=$(cat /creds/client-secret.txt)

      # 1. Get an access token from Keycloak (client_credentials grant).
      # Use the same Keycloak hostname form set as `keycloak.publicUrl` on the
      # Kagenti operand (the JWT `iss` claim must match what AuthBridge validates).
      # The recommended in-cluster form is the short svc URL:
      TOKEN=$(curl -s \
        http://keycloak-service.keycloak.svc:8080/realms/kagenti/protocol/openid-connect/token \
        -d grant_type=client_credentials \
        --data-urlencode "client_id=$CID" \
        --data-urlencode "client_secret=$CSEC" \
        | sed 's/.*"access_token":"\([^"]*\)".*/\1/')

      # 2. Call the agent with the token in the Authorization header
      curl -sS -X POST http://weather-agent.team1.svc.cluster.local:8000/ \
        -H "Authorization: Bearer $TOKEN" \
        -H "Content-Type: application/json" \
        -d '{"jsonrpc":"2.0","id":"1","method":"message/send","params":{"message":{"role":"user","parts":[{"kind":"text","text":"What is the weather in NY?"}],"messageId":"m1"}}}'
    volumeMounts:
    - name: creds
      mountPath: /creds
      readOnly: true
  volumes:
  - name: creds
    secret:
      secretName: <credentials-secret>
EOF

读取结果并清理:

kubectl logs weather-query -n team1
kubectl delete pod weather-query -n team1

你将获得与 core-profile 第 4 步 相同的已完成 A2A task——现在是通过已认证、JWT 已验证的路径完成的。

从集群外部调用

如果要从集群外部调用 agent,请先从通过 Ingress/Gateway 暴露的 Keycloak endpoint 请求 token,然后使用相同的 Authorization: Bearer <token> header 将请求发送到 agent 的暴露 endpoint。token 请求是标准的 OIDC 调用:POST <keycloak>/realms/kagenti/protocol/openid-connect/token,并携带 grant_type=client_credentialsclient_idclient_secret

AuthBridge 部署模式

AuthBridge 可以以两种形态注入。模式决定会添加哪个 sidecar 镜像、如何验证入站流量,以及如何拦截出站流量。它是一个 按命名空间 生效的默认值(管理员配置项),而不是按 workload 生效——该命名空间内的所有 agent 和 MCP tool 都共享同一种模式。

模式Sidecar 容器 / 镜像入站(client → workload)出站(workload → 其他服务)
proxy-sidecar(默认)authbridge-proxy 容器,镜像 authbridge在 workload 的 service port 上进行反向代理 → JWT 验证 → 转发到偏移后的应用端口。协作式:webhook 注入指向 sidecar 上 :8081 处 HTTP forward proxy 的 HTTP_PROXY / HTTPS_PROXY 环境变量。应用通过遵守这些环境变量来接入。
envoy-sidecarenvoy-proxy 容器,镜像 authbridge-envoy(Envoy + authbridge 作为 ext_proc)。另外会添加一个 proxy-init initContainer,用于安装 iptables 规则。透明重定向:iptables 将入站流量发送到 Envoy,Envoy 再通过 gRPC ext_proc 调用 authbridge 做 JWT 验证。透明式:iptables 将出站 TCP 重定向到 Envoy 的 outbound listener。应用上不会设置 HTTP_PROXY 环境变量。

选择模式

模式按以下顺序解析(第一个非空项生效):

  1. 命名空间 ConfigMap authbridge-runtime-config —— config.yaml 中的 mode: 字段。
  2. 集群级默认值:proxy-sidecar

要将某个命名空间切换为 envoy-sidecar,请应用一个 ConfigMap 并重启 agent 与 tool 部署(见 模式变更后重新加载):

apiVersion: v1
kind: ConfigMap
metadata:
  name: authbridge-runtime-config
  namespace: team1
data:
  config.yaml: |
    mode: envoy-sidecar
    mtls:
      mode: disabled
WARNING

envoy-sidecar 在当前版本中会在 mtls.mode: permissive正常启动——authbridge-envoy 二进制通过 go-spiffe 在进程内将 X.509 SVID 写入 /opt/svid*.pem,这正是 operator 的 Envoy filesystem-SDS 配置所期望的行为。不过,目前仍有两个 operator 模板缺口,正在跟进下一个版本:

  • 入站认证强制:Envoy 配置模板会在 route 级别注入 x-authbridge-direction: inbound,而 router filter 会在 ext_proc 之后 应用该值。因此 AuthBridge 会将入站请求归类为出站请求,并应用默认透传策略——JWT-validation plugin 不会运行。后续补丁会将该 header 注入移动到位于 ext_proc 之前的 Lua HTTP filter 中。
  • 通过 sidecar 的外部 HTTPS:Envoy 的 outbound listener 默认假定为 HTTP,因此对任意公共主机的客户端 TLS 握手会被当作 HTTP 解析并破坏。该模式下没有 NO_PROXY 逃逸口;后续会在 Kagenti operand 上添加 spec.egressBypass 字段,并下传为 proxy-init 的 OUTBOUND_PORTS_EXCLUDE / hosts allowlist。

当前版本建议: 在生产环境中使用 proxy-sidecar。它的入站 JWT 验证以及 NO_PROXY 出站 egress 选项(见下文)都已完成端到端验证。envoy-sidecar 适合用于启动和检查数据路径,但在上述两个项目落地之前,不应将其作为生产认证强制的依据。

模式变更后重新加载

由于 manager 和按 workload 注入的配置都会在 pod 创建时缓存,因此模式变更需要执行三个操作:

# 1. Roll the operator so it picks up the new namespace ConfigMap.
kubectl -n kagenti-system rollout restart deploy/kagenti-controller-manager

# 2. Delete stale per-workload ConfigMaps so the webhook re-renders them
#    with the new mode. (Kept for both modes; the operator preserves
#    existing values, see issue upstream #433.)
kubectl -n team1 delete cm envoy-config-weather-agent envoy-config-weather-tool 2>/dev/null

# 3. Recycle the workloads so the webhook re-injects the sidecars.
kubectl -n team1 rollout restart deploy/weather-agent deploy/weather-tool

确认注入已按预期生效:

# proxy-sidecar: container "authbridge-proxy" runs image "authbridge:..."
# envoy-sidecar: container "envoy-proxy" runs image "authbridge-envoy:...",
#                and an "proxy-init" initContainer appears.
kubectl -n team1 get pod -l app=weather-agent -o jsonpath='{range .spec.containers[*]}{.name}={.image}{"\n"}{end}'

何时使用每种模式

当满足以下任一条件时,使用 proxy-sidecar(默认值)——这是当前版本经过测试并受支持的路径:

  • 你需要 sidecar-to-sidecar mTLS(permissivestrict)。
  • 你只会调用其他集群内 HTTP 服务(agent → MCP tool、LLM InferenceService 等),不需要 CONNECT tunneling。
  • workload 不需要 SPIRE。

当你明确需要以下功能时,使用 envoy-sidecar

  • 数据路径上的 Envoy 路由 / 可观测性能力。
  • 透明的 iptables 拦截(应用上没有 HTTP_PROXY 环境变量)。

proxy-sidecar 模式下允许出站流量

本指南遵循的上游 weather-agent demo 模式——tool workload 上没有 AgentRuntime,也没有 sidecar(spec.featureGates.injectTools: false 是 operand 默认值)——会完全绕开这个问题:tool 以单容器运行,没有 HTTP_PROXY/HTTPS_PROXY 环境变量,因此它对公共 API 的出站调用会直接通过 pod 的默认 egress。对于 tool 不需要自身入站 JWT 验证或出站 token exchange 的场景,这是推荐的布局。

本节其余内容仅适用于你有意将 AuthBridge sidecar 注入到也需要外部 HTTPS 的 workload 中的情况——例如你在 Kagenti operand 上将 injectTools: true 打开(参见 高级 Demo),或者在单个 Deployment 上添加 kagenti.io/authbridge-inject: "true"。AuthBridge 的 HTTP forward proxy 仅支持 HTTP——它会对 CONNECT 返回 405 Method Not Allowed,因此注入后的应用如果通过 sidecar 发起出站 HTTPS 请求,就会遇到 Tunnel connection failed: 405。这符合设计预期:出站 token-exchange plugin 需要读取和修改请求 header,而这在不透明的 TLS tunnel 中无法实现。

当 outbound-heavy workload 上带有 sidecar 时,有两个后果需要了解:

  1. 集群内 HTTP 可以直接工作(agent → tool、LLM)。无需配置。

  2. 外部 HTTPS(例如 weather MCP tool 使用的公共 API geocoding-api.open-meteo.com)需要绕过 sidecar。将目标 hostname 添加到 app 容器上的 NO_PROXY。kagenti webhook 只有在容器中尚未存在时才设置 HTTP_PROXY / HTTPS_PROXY / NO_PROXY,因此在 workload 上预先声明 NO_PROXY 可以干净地覆盖默认值:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: weather-tool
      namespace: team1
    spec:
      template:
        spec:
          containers:
          - name: mcp                 # the app container, not the sidecar
            env:
            - name: NO_PROXY
              value: "127.0.0.1,localhost,.svc,.svc.cluster.local,geocoding-api.open-meteo.com,api.open-meteo.com"

    其效果是:未被 NO_PROXY 匹配的任何主机上的出站调用仍会通过 AuthBridge sidecar 路由(并继续具备出站 token exchange、tracing 等能力);而列出的公共 API 调用会绕过 sidecar,直接通过 pod 的默认 egress。流量是否真正能到达公共目标,由集群的 egress network policy 决定,而不是由 AuthBridge 决定。

    INFO

    只添加你确实需要对外访问的主机。NO_PROXY 中的每一项都是出站检查路径中的一个漏洞——sidecar 将无法再在该 hostname 上注入 Bearer 令牌。

清理

kubectl delete namespace team1

这会删除 demo 命名空间及其中的所有资源。集群级别的 secure-profile 安装(SPIRE、Keycloak、Istio ambient、Kagenti operand)不会受到影响。

故障排查

现象检查项
未认证请求被拒绝并返回 auth.unauthorized这是预期行为——secure profile 要求有效的 Bearer 令牌。有关 client-credentials 的示例,请参见 步骤 4
带 token 的请求返回 401JWT iss claim 不匹配:token 是从与 Kagenti operand 上设置的 keycloak.publicUrl 不同的 Keycloak hostname 获取的。请从 AuthBridge 实际验证的相同 hostname 获取 token(通常是 http://keycloak-service.keycloak.svc:8080)。
Agent pod 卡在 1/1 而不是 2/2sidecar 未注入。检查命名空间标签(kagenti-enabled=true)、Kagenti operand(featureGates.globalEnabled: trueauthbridgeConfig.enabled: true),以及 AgentRuntime 是否已完成调谐(kubectl describe agentruntime weather-agent-runtime -n team1)。
命名空间中没有 kagenti-keycloak-client-credentials-* SecretKeycloak client 注册尚未完成。检查 kagenti-controller-manager 日志中的 ClientRegistrationReconciler 错误,并确认 keycloak-admin-secret 存在于 kagenti-system 中。
tool 显示“weather service temporarily unavailable”tool 的 get_weather 会调用 geocoding-api.open-meteo.com——这是一个公共 HTTPS endpoint。使用推荐模式(tool 上不带 sidecar)时,它会直接工作。如果你给 tool 注入了 sidecar,HTTP_PROXY 环境变量会把 HTTPS 路由到 AuthBridge,而它会用 405 拒绝 CONNECT;请改回无 sidecar 布局,或者将目标加入 NO_PROXY——参见 proxy-sidecar 模式下允许出站流量
AuthBridge 镜像出现 ImagePullBackOffKagenti operand 上的 defaults.images 指向了集群无法拉取的 registry。请将 AuthBridge sidecar 镜像迁移到集群可访问的 registry(见 启用 Secure Profile)。
AgentRuntime 上的 MTLSReady=False确认 SPIRE 已安装,并且其 trust domain 与 operand 上的 defaults.spiffe.trustDomain 匹配。在 proxy-sidecar 模式下,即使没有 mTLS,sidecar 仍然可以验证 JWT——对 smoke test 来说,真正需要关注的是 runtime 上的 Ready=True