Изоляция исходящего сетевого трафика для развёртываний в Docker

Когда Hermes работает внутри Docker, режим network_mode: host по умолчанию даёт процессу агента неограниченный исходящий доступ в сеть. В этом гайде показано, как разделить трафик так, чтобы ядро агента дотягивалось только до нужных ему сервисов, а произвольные исходящие соединения блокировались.

Прежде всего это защита от атак через инъекцию промпта, которые пытаются утащить данные наружу с помощью curl, wget или сырого HTTP из shell-команд, сгенерированных инструментами.

Модель угроз

Модель доверия Hermes описана в §2 файла SECURITY.md. Основная граница исполнения — терминальный бэкенд. Но при network_mode: host любая команда, которую выполняет агент, может достучаться до любого адреса в сети, включая внешние.

Изоляция исходящего трафика добавляет второй слой: даже если вредоносная команда выполнится внутри контейнера, до адресов вне явно разрешённого набора она не дотянется.

Архитектура

┌─────────────────────────────────────────────┐
│  Docker Network: internal (no internet)     │
│                                             │
│   ┌──────────────┐   ┌──────────────────┐   │
│   │ hermes-agent │   │ hermes-dashboard │   │
│   └──────┬───────┘   └────────┬─────────┘   │
│          │                    │              │
│          ▼                    │              │
│   ┌──────────────┐            │              │
│   │ hermes-gtw   │◄───────────┘              │
│   └──────┬───────┘                           │
│          │                                   │
└──────────┼───────────────────────────────────┘
           │
┌──────────┼───────────────────────────────────┐
│  Docker Network: egress (internet-capable)   │
│          │                                   │
│          ▼                                   │
│   ┌─────────────────┐                        │
│   │ egress-proxy     │──► allowlisted hosts  │
│   │ (squid / envoy)  │                       │
│   └─────────────────┘                        │
└──────────────────────────────────────────────┘

Две Docker-сети:

  • internal — без маршрута по умолчанию и без доступа в интернет. Здесь работают агент, дашборд и шлюз.
  • egress — с доступом в интернет. К этой сети подключены только сервисы, которым нужно ходить во внешние API.

Сервис шлюза подключён к обеим сетям сразу, чтобы принимать входящие сообщения из Telegram/Slack и других платформ и передавать их агенту во внутренней сети.

Конфигурация Compose

Переопределите стандартный docker-compose.yml файлом docker-compose.override.yml:

# docker-compose.override.yml
# Изоляция исходящего трафика для production-развёртываний.
#
# Использование:
#   HERMES_UID=$(id -u) HERMES_GID=$(id -g) docker compose up -d
#
# Заменяет network_mode: host изолированными Docker-сетями.

networks:
  internal:
    driver: bridge
    internal: true          # без маршрута по умолчанию, без интернета
  egress:
    driver: bridge

services:
  gateway:
    network_mode: ""        # сбросить host-режим по умолчанию
    networks:
      - internal
      - egress              # нужен исходящий доступ к Telegram и API LLM
    ports:
      - "127.0.0.1:9119:9119"   # прокси дашборда, только localhost

  dashboard:
    network_mode: ""
    networks:
      - internal            # только внутренняя сеть, исходящий доступ не нужен

С прокси исходящего трафика (рекомендуется)

Для более жёсткого контроля пропустите весь исходящий трафик через 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 и платформу сообщений.

Проверка настройки

Подняв стек, убедитесь, что изоляция работает:

# Из контейнера агента: должно ПРОВАЛИТЬСЯ (исходящего доступа нет)
docker compose exec gateway \
  curl -sf --max-time 5 https://example.com && echo "FAIL: egress not blocked" || echo "OK: egress blocked"

# Из контейнера агента: должно СРАБОТАТЬ (внутренняя сеть)
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 не уносит наружу ничего существенного.

  • Не замена бэкендам-песочницам: этот гайд изолирует сеть контейнера агента. Если вы используете локальный терминальный бэкенд по умолчанию, команды инструментов выполняются в том же контейнере. Для более сильной изоляции сочетайте сегментацию сети с терминальным бэкендом в песочнице (Docker, Modal, Daytona).

  • Адаптерам платформ нужен исходящий доступ: сервису шлюза требуется исходящий доступ к API платформ сообщений. Добавляя новые адаптеры платформ, внесите их API-эндпоинты в список разрешений прокси.

Смотрите также

перевод и редактура:

обновлено

⌕ ESC