標籤: sidecar proxy

  • 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」的隱形放寬