Docker 部署的网络出口隔离

在 Docker 中运行 Hermes 时,默认的 network_mode: host 让 agent 进程拥有不受限制的出站网络访问。本指南展示如何切分流量,让 agent 核心只能访问它需要的服务,同时阻断任意出站连接。

这主要是为了防御提示注入攻击——这类攻击试图通过工具生成的 shell 命令里的 curl、wget 或原始 HTTP 把数据外泄。

威胁模型

Hermes SECURITY.md §2 定义了信任模型。终端后端是主要执行边界。然而,当以 network_mode: host 运行时,agent 执行的任何命令都能访问网络上的任何端点,包括外部端点。

网络出口隔离增加了第二层:即使一个恶意命令在容器内执行,它也访问不到明确允许集合之外的端点。

架构

┌─────────────────────────────────────────────┐
│  Docker 网络:internal(无互联网)            │
│                                             │
│   ┌──────────────┐   ┌──────────────────┐   │
│   │ hermes-agent │   │ hermes-dashboard │   │
│   └──────┬───────┘   └────────┬─────────┘   │
│          │                    │              │
│          ▼                    │              │
│   ┌──────────────┐            │              │
│   │ hermes-gtw   │◄───────────┘              │
│   └──────┬───────┘                           │
│          │                                   │
└──────────┼───────────────────────────────────┘
           │
┌──────────┼───────────────────────────────────┐
│  Docker 网络:egress(可联网)                │
│          │                                   │
│          ▼                                   │
│   ┌─────────────────┐                        │
│   │ egress-proxy    │──► 允许列表主机         │
│   │ (squid / envoy) │                       │
│   └─────────────────┘                        │
└──────────────────────────────────────────────┘

两个 Docker 网络:

  • internal——无默认路由,无互联网访问。agent、仪表盘和网关跑在这里。
  • egress——有互联网访问。只有需要访问外部 API 的服务才接到这个网络。

网关服务是双宿主的(同时接到两个网络),因此它能接收来自 Telegram/Slack 等的入站消息,并转发给 internal 网络上的 agent。

Compose 配置

用 docker-compose.override.yml 覆盖默认的 docker-compose.yml:

# docker-compose.override.yml
# 生产部署的网络出口隔离。
#
# 用法:
#   HERMES_UID=$(id -u) HERMES_GID=$(id -g) docker compose up -d
#
# 这用隔离的 Docker 网络覆盖了 network_mode: host。

networks:
  internal:
    driver: bridge
    internal: true          # 无默认路由,无互联网
  egress:
    driver: bridge

services:
  gateway:
    network_mode: ""        # 清除 host 模式默认值
    networks:
      - internal
      - egress              # 需要出站访问 Telegram、LLM API
    ports:
      - "127.0.0.1:9119:9119"   # 仪表盘代理,仅 localhost

  dashboard:
    network_mode: ""
    networks:
      - internal            # 仅内部,无需 egress

配合出口代理(推荐)

要更严格的控制,把所有出站流量经一个带明确允许列表的 HTTP 代理路由:

# docker-compose.override.yml(带出口代理)

networks:
  internal:
    driver: bridge
    internal: true
  egress:
    driver: bridge

services:
  gateway:
    network_mode: ""
    networks:
      - internal
      - egress
    environment:
      - HTTP_PROXY=http://egress-proxy:3128
      - HTTPS_PROXY=http://egress-proxy:3128
      - NO_PROXY=hermes,hermes-dashboard,localhost

  dashboard:
    network_mode: ""
    networks:
      - internal

  egress-proxy:
    image: ubuntu/squid:6.10-24.04_edge
    networks:
      - egress
    volumes:
      - ./config/squid-allowlist.conf:/etc/squid/conf.d/allowlist.conf:ro
    restart: unless-stopped

config/squid-allowlist.conf 示例:

# 只允许到这些主机的 HTTPS CONNECT
acl allowed_hosts dstdomain api.openai.com
acl allowed_hosts dstdomain api.anthropic.com
acl allowed_hosts dstdomain openrouter.ai
acl allowed_hosts dstdomain generativelanguage.googleapis.com
acl allowed_hosts dstdomain api.telegram.org
acl allowed_hosts dstdomain api.github.com
acl allowed_hosts dstdomain discord.com

http_access allow CONNECT allowed_hosts
http_access deny all

按你的 LLM 提供商和消息平台调整允许列表。

验证设置

起栈后,验证隔离:

# 从 agent 容器:这条应失败(无 egress)
docker compose exec gateway \
  curl -sf --max-time 5 https://example.com && echo "FAIL: egress not blocked" || echo "OK: egress blocked"

# 从 agent 容器:这条应成功(internal 网络)
docker compose exec gateway \
  curl -sf --max-time 5 http://hermes-dashboard:9119/health && echo "OK: internal reachable" || echo "FAIL"

# 若用出口代理:这条应成功(在允许列表中)
docker compose exec gateway \
  curl -sf --max-time 5 --proxy http://egress-proxy:3128 https://api.openai.com/v1/models && echo "OK" || echo "FAIL"

限制

  • DNS 解析: internal 网络仍能解析外部 DNS 名,除非你还跑一个阻断外部查询的本地 DNS 解析器。对大多数威胁模型这可接受,因为仅 DNS 解析本身不会外泄有意义的数据。
  • 不能替代沙箱后端: 本指南隔离的是 agent 容器的网络。如果你用默认的本地终端后端,工具命令在同一容器内执行。要更强的隔离,把网络切分与沙箱终端后端(Docker、Modal、Daytona)结合使用。
  • 平台适配器需要 egress: 网关服务需要出站访问消息平台 API。如果你新增平台适配器,把它们的 API 端点加到代理允许列表。

相关