Google AX 多代理調度與網路隔離實戰

Google AX 多代理調度與網路隔離實戰

📌 本文重點

  • 將 Agent 視為 K8s Job 可利用現有雲原生能力
  • 單靠 LLM sandbox 不足,必須做 OS/網路層隔離
  • wire format 會吃掉 port 是「Agent Egress Illusion」關鍵風險
  • eBPF 或 sidecar proxy 可實作可驗證的 egress 控制

在多代理(multi-agent)系統裡,真正麻煩的不是「叫一個 LLM 幫你想辦法」,而是如何安全地讓一堆有能力執行程式碼的 Agent,在企業網路裡被可靠調度、精準限權、可觀測且可驗證地被關起來。Google AX 給了一個值得抄的架構:把 Agent 當 Kubernetes Job 來排程,所有工具和憑證都以最小權限下放,同時嘗試提供精細網路出口控制——但也踩到一個 wire format 的安全坑。

這篇文章會聚焦三件事:

  1. 設計層:為何要把 Agent 當 Job 調度、如何在企業環境做最小權限隔離。
  2. 實作層:以 AX 風格實作一個精簡版 Go agent runner,含 agent spec、network policy、執行沙盒 配置樣板。
  3. 資安與踩坑:解析「Agent Egress Illusion」中 port 資訊丟失 bug,示範用 eBPF 或 sidecar proxy 做真正可驗證的 egress 控制,以及應自動化的安全檢查。

重點說明

1. 為何把 Agent 當 K8s Job 調度?

AX 的核心設計是:每個 Agent/任務就是一個可觀測、可重試的 Job,而不是一個長駐服務。這帶來幾個直接好處:

  • 資源隔離自然落在 Pod/Job 邊界:CPU / memory / volume / ServiceAccount 都用現有 K8s 原生能力。
  • 權限最小化更容易實作:不同工具/憑證綁在不同 Job template 上,下發時只給需要的那一份。
  • 重試與狀態同步內建:Job 狀態是可觀測事件流,AX 做的是把這些 event 封裝成 Agent 狀態機(state machine),方便 orchestrator 決策下一步。

對你的專案來說,這代表:

  • 不用自己重新發明一套 job scheduler,照 K8s Job 範式套一層 Go orchestration 即可。
  • 多 Agent 協作 = 多 Job 工作流,你可以在 Go 裡用 DAG / state transition 描述整個流程。

💡 關鍵: 把每個 Agent 當一次性 Job,可直接沿用 K8s 的資源隔離與重試機制,避免重造 scheduler 輪子。

2. AX-style Agent Spec 與精細網路控制

AX 用一個類似「AgentSpec」的結構描述每個 Agent:

  • 要跑的 tool image / command
  • 限制的 network egress 規則(domain / IP / port)
  • 授權的 secrets / credentials

它宣稱可以做到「精細網路出口控制」,但在 wire format 的 protobuf/JSON 中,port 資訊沒被帶到真正執行的層級,導致你以為只開 443,實際上所有 port 都能出去,這就是「Agent Egress Illusion」。

這個教訓是:

  • 不要只相信 API 層的 policy,必須驗證到 socket 層。
  • 對自己寫的 orchestrator,要有「從 UI → spec → wire → kernel」的完整 trace,確認資訊沒有在中途被丟失或過度簡化。

3. 為何單憑 LLM sandbox 不夠?

LLM sandbox 做的通常是:

  • 限制 prompt 能呼叫哪些工具
  • 限制工具的參數範圍

但實務上常見攻擊路徑是:

  • 反向殼層(reverse shell)回到攻擊者機器
  • 內網掃描打開更多攻擊面
  • 泄漏環境中的 API key / DB credentials

這些都不會被「prompt-sandbox」阻止,只有 OS / network layer 的硬性隔離才有用:namespace、cgroup、iptables、eBPF、sidecar proxy 等。


實作範例:精簡版 AX-style Agent Runner(Go)

下面是一個簡化示意,展示如何在自己專案做一個 AX 風格的調度層。重點在 AgentSpec、NetworkPolicy、以及 runner 如何執行與觀測。

1. 定義 Agent Spec 與 Network Policy

// AgentSpec 描述一個可被調度的 Agent 任務
type AgentSpec struct {
    ID      string
    Image   string
    Command []string
    Env     map[string]string

    Network NetworkPolicy
    Secrets []string // reference to secret IDs
}

// NetworkPolicy 為每個 Agent 定義 egress 規則
type NetworkPolicy struct {
    AllowedDestinations []DestinationRule
}

type DestinationRule struct {
    Host  string // example.com or 10.0.0.0/24
    Port  int    // 0 表示任何 port(強烈不建議在生產環境使用)
    Proto string // tcp/udp
}

這個 Spec 可以直接當成你自己的 API 層 contract。關鍵是:

  • 不要在任何一層「自動忽略」Port,即使使用者沒填,也要在 schema 上明確設定預設值與含義。
  • 在轉成 wire format(JSON / protobuf)時,保證欄位不被省略。

2. Runner:把 Agent Spec 下放到 Worker 節點

下例示範一個最小可用 runner:從 queue 拿到 AgentSpec,spawn 一個 container(可以是 K8s Job、或本機用 container runtime)並附帶 network policy:

type Runner struct {
    // 抽象的 container 執行介面,可以是 K8s client,也可以是本機 Docker
    Exec ExecBackend
}

type ExecBackend interface {
    Run(spec AgentSpec) (RunHandle, error)
}

// K8sJobBackend 是一個以 K8s Job 為基礎的實作
type K8sJobBackend struct {
    // kube client, omitted
}

func (b *K8sJobBackend) Run(spec AgentSpec) (RunHandle, error) {
    job := BuildK8sJobFromAgent(spec)

    // 將 NetworkPolicy 轉成 K8s NetworkPolicy 或 CNI 插件規則
    np := BuildNetworkPolicyFromAgent(spec)

    // 建議:先創 NetworkPolicy,再創 Job
    if err := b.applyNetworkPolicy(np); err != nil {
        return RunHandle{}, err
    }
    if err := b.createJob(job); err != nil {
        return RunHandle{}, err
    }

    return RunHandle{ID: spec.ID}, nil
}

其中 BuildNetworkPolicyFromAgent 是重點,用來把 AX-style 的 egress 規則真正落到 K8s 或底層 CNI:

func BuildNetworkPolicyFromAgent(spec AgentSpec) *netv1.NetworkPolicy {
    // 示意:將每個 DestinationRule 轉為 egress rule
    // 注意:K8s NetworkPolicy 的 port 是獨立欄位,不能在轉換時丟掉
}

如果你不想依賴 K8s NetworkPolicy,也可以直接用 sidecar proxy 控制:

  • 為每個 Agent Pod 加一個 envoy/mitmproxy sidecar。
  • 在 sidecar config 中生成 per-Agent egress ACL。

3. 觀測與狀態同步

AX 的設計亮點之一是狀態同步與觀測。你可以在 runner 裡加上一層事件流:

type AgentStatus string

const (
    StatusPending AgentStatus = "PENDING"
    StatusRunning AgentStatus = "RUNNING"
    StatusSuccess AgentStatus = "SUCCESS"
    StatusFailed  AgentStatus = "FAILED"
)

type StatusStore interface {
    Update(id string, status AgentStatus, meta map[string]string) error
}

func (r *Runner) RunAndWatch(spec AgentSpec, store StatusStore) error {
    handle, err := r.Exec.Run(spec)
    if err != nil {
        return err
    }

    store.Update(spec.ID, StatusRunning, nil)

    go func() {
        result := handle.Wait()
        if result.Err != nil {
            store.Update(spec.ID, StatusFailed, map[string]string{"error": result.Err.Error()})
        } else {
            store.Update(spec.ID, StatusSuccess, nil)
        }
    }()

    return nil
}

這樣你即可在前端或控制平臺上,實時看到每個 Agent 的狀態,並進行工作流編排(例如下一個 Agent 只有在上一個成功時才啟動)。


資安與踩坑:Agent Egress Illusion 與真正可驗證的控制

1. Agent Egress Illusion:port 資訊在 wire format 被吃掉

簡化來說,問題長這樣:

  1. Config / UI 層支援 host+port,例如:example.com:443。
  2. Spec 序列化成 wire format(JSON / protobuf)時,只保留 example.com,port 被 default 掉(例如 0 或空)。
  3. 執行層的 network module 把「空 port」解讀為「any port」。

結果:你以為自己寫了 allow example.com:443,實際執行的是 allow example.com:*,甚至 allow *:*。

避免同樣踩坑,最實際的做法是:

  • 在 schema 層面強制 port 必填或有明確預設邊界(例如只允許 80/443)。
  • 在 wire / decode 層加 結構化驗證:任何 Host 沒設定 Port 就拒絕部署。
  • 寫端到端測試,驗證「指定 port 不同時,實際 socket 行為不同」。

💡 關鍵: 只要 port 在序列化或 decode 過程被默默 default,實際執行的網路權限就會遠超過 UI 上看到的設定。

2. 用 eBPF 做真正可驗證的 egress 控制

在 Linux 上,可以用 eBPF 寫一段針對 connect() 的 hook,根據 Agent ID + 目的 host/port 做精細控制。示意邏輯(偽 C):

int sock_connect(struct bpf_sock_addr *ctx) {
    __u16 dport = bpf_ntohs(ctx->user_port);
    struct agent_policy *policy = lookup_policy_for_current_pid();

    if (!policy) return 0; // 無 policy 的情況下可選擇允許或拒絕

    if (!policy_allows(policy, ctx->user_ip4, dport)) {
        return -EPERM; // 直接阻擋
    }
    return 0;
}

然後在 Go runner 裡,為每個 Agent 產生對應的 eBPF policy map:

func InstallEBPFPolicy(agentID string, net NetworkPolicy) error {
    // 1. 將 AgentID 映射到一個 cgroup 或 pid namespace
    // 2. 將 DestinationRule 寫入 eBPF map
    return nil
}

這樣你可以做到:

  • policy 與 socket 在同一層被驗證,不再依賴高層配置是否完整。
  • 可以收集 eBPF 事件,作為 shadow env 觀測:任何違反 policy 的連線都會被紀錄,甚至被 fuzz 測試工具捕捉。

3. Sidecar Proxy 方案(較好上手)

如果 eBPF 對團隊太重,你可以選擇 sidecar proxy:

  • 每個 Agent Pod 都透過 sidecar 發出所有外部連線。
  • AX-style NetworkPolicy 轉成 proxy 的 ACL:
# envoy filter pseudo config
- name: agent-egress-filter
  typed_config:
    allowed_destinations:
      - host: example.com
        port: 443
      - host: api.internal
        port: 8443

對開發者來說,這個方案的優點是:

  • 配置全部在 user space,容易 debug。
  • 可以在 staging 做 policy fuzzing:自動產生隨機目標,確認 proxy 確實阻擋。

建議與注意事項

1. 三件必做的自動化安全檢查

  1. 端到端 egress 測試:
  2. 建一個專門的 Agent,用來嘗試從各種 host/port 打出去。
  3. CI 裡檢查:不應該開放的目標全部連線失敗。

  4. policy fuzzing:

  5. 針對你的 NetworkPolicy 模組,用模糊測試工具產生多組 host/port 組合,確認 decode / merge 過程不會產生意外的「any port」。

  6. shadow env 觀測:

  7. 在 staging/灰度環境,把所有 egress 事件打到一個集中 log。
  8. 針對異常 pattern(例如 Agent 對內網 IP 連線)設 alert。

2. 多代理環境常見安全誤區

  • 只做 prompt 限制不做網路限制:LLM 只要能呼叫 bash 或 python,就能繞過任何 prompt policy。
  • 所有 Agent 共用一組 API key 或 ServiceAccount:一旦有 Agent 被利用,就等於全網被打開。
  • 忽略內網掃描:很多團隊只防外聯,不防 Agent 對內網執行 nmap/port scan,結果是 lateral movement 更容易。

實務建議:

  • 每個 Agent 類型一個 最小權限 ServiceAccount,不要共用。
  • 有寫檔需求的 Agent,只給 只讀/只寫的特定 volume,避免觸及主機檔案系統。
  • 將所有 egress 限制在白名單 domain,而不是黑名單。

💡 關鍵: 多代理環境的核心風險在橫向移動與憑證濫用,因此需以最小權限帳號和白名單 egress 為設計預設。

3. 如何在現有專案落地 AX-style 架構

  • 已有 K8s:
  • 直接定義自己的 Agent CRD + Controller,用上文的 AgentSpec 當 schema,K8s Job 當執行層。
  • 接上 NetworkPolicy 或 sidecar proxy,做 per-Agent egress。

  • 沒有 K8s、純 VM/裸機:

  • 用 Go Runner + container runtime(Docker / containerd),以 namespace + iptables 或 eBPF 做隔離。
  • 同樣透過 AgentSpec 描述任務,確保 spec → runtime 的 mapping 可觀測。

核心結論:

  • 把 Agent 當 Job 調度,可以用現有雲原生堆疊快速搭起可擴充的多代理平臺。
  • LLM sandbox 絕對不夠,必須有實體網路與系統層的防護,並且用 eBPF 或 sidecar 這類「可驗證」的手段落實。
  • 在設計 Agent 平臺時,wire format 不可忽視,任何欄位(尤其是 port)一旦在序列化過程中被吃掉,就會變成下一個「Agent Egress Illusion」。

🚀 你現在可以做的事

  • 在現有專案中定義一個 AgentSpec 結構,試著用 K8s Job 或 Docker 實作最小可用 runner
  • 為某個測試 Agent 建立白名單 egress 規則,實驗一版 sidecar proxy 或 NetworkPolicy 的落地做法
  • 寫一個簡單的 egress 測試 Agent,加入 CI 流程,驗證你的網路策略沒有出現「any port」的隱形放寬

留言

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *