Загрузка 0
ПОДЕЛИТЬСЯ

Мой блог

Листай вниз

Docker для параноика: SSH-доступ для rootless-контейнеров без передачи приватного ключа

Docker для параноика: SSH-доступ для rootless-контейнеров без передачи приватного ключа

Организация безопасного SSH-доступа внутри изолированных сред часто упирается в архитектурные компромиссы. Когда приложению или системе автоматизации требуются операции с репозиториями и удаленными серверами, разработчики нередко прибегают к прямому монтированию ключей, создавая серьезные уязвимости. Я подготовил подробный материал о том, как настроить изолированную среду для работы с репозиториями и серверами без риска утечки секретов.

В этой публикации я детально разберу архитектуру, которая позволяет запустить полноценный SSH-клиент внутри rootless-контейнера без передачи самого файла приватного ключа. Мы рассмотрим особенности сопоставления UID, настройку специального агента аутентификации и сопутствующие меры защиты на уровне операционной системы.

Передача Unix-сокета ssh-agent в контейнер
В контейнер передается только сокет агента без прямого доступа к материалу ключа.
Синхронизация файловых систем при работе с внешними сервисами
Схема независимых файловых систем при использовании внешнего API-сервиса.
Полная цепочка SSH-аутентификации в контейнере
Полная схема прохождения запроса от клиента до удаленного сервера.
Дополнительный элемент конфигурации среды
Вспомогательный элемент конфигурации тестового окружения.
Проверка параметров безопасности демона
Диагностика состояния демона в rootless-режиме.
Тестирование подключения через SSH-агент
Проверка работоспособности ключей внутри изолированной среды.
Проверка прав доступа к Unix-сокету
Контроль владельца и прав Unix-сокета.
Диагностика состояния монтирования
Проверка точек монтирования каталогов с сокетами.
Перезапуск службы SSH-агента
Процесс обновления сокета без пересоздания контейнеров.
Границы защиты изолированной среды
Анализ уровня безопасности и возможных векторов угроз.
Итоговая проверка конфигурации безопасности
Проверка всех задействованных ограничений и изоляции.
Дополнительные настройки сетевой изоляции
Дополнительные параметры ограничения трафика.
Завершающий этап настройки окружения
Финальная проверка работоспособности всех компонентов.
Docker для параноика: SSH-доступ для rootless-контейнеров без передачи приватного ключа
Docker для параноика: SSH-доступ для rootless-контейнеров без передачи приватного ключа
Docker для параноика: SSH-доступ для rootless-контейнеров без передачи приватного ключа

Введение и требования к безопасности

Полноценный SSH-клиент может потребоваться контейнеру для самых разных задач: клонирования приватных репозиториев через git, выполнения команд на удаленных машинах, передачи файлов по scp или обслуживания серверной инфраструктуры. Классический подход с монтированием директории ~/.ssh напрямую внутрь контейнера нарушает базовые принципы изоляции учетных данных. Даже монтирование в режиме только для чтения защищает файл лишь от изменения, но не от прямого чтения со стороны запущенного ПО.

Реклама

Чтобы исключить подобные риски на уровне архитектуры, я выставил жесткие требования к целевой конфигурации:

  • Демон Docker работает в rootless-режиме без привилегий суперпользователя на хосте;
  • процесс внутри контейнера функционирует от имени непривилегированного пользователя;
  • у контейнера полностью удалены лишние Linux capabilities и запрещено получение новых привилегий;
  • корневая файловая система монтируется только для чтения;
  • материал приватного ключа физически отсутствует в пространстве имён контейнера;
  • категорически запрещено использование небезопасного параметра StrictHostKeyChecking=no;
  • ключ не передается через переменные окружения и не монтируется с хоста в читаемом виде.

Вместо передачи самого секретного файла мы будем использовать Unix-сокет агента аутентификации. Однако при работе в защищенном rootless-режиме этот метод сталкивается с техническими нюансами, связанными с преобразованием идентификаторов.

Зачем нужен SSH в контейнере и альтернативные подходы

Типичные сценарии использования SSH в изолированных средах включают в себя автоматизированное развертывание, работу с подмодулями репозиториев и применение стандартных утилит OpenSSH. Альтернативным вариантом может стать полный отказ от SSH внутри контейнера с вынесением всех операций в отдельный API-сервис или промежуточный слой наподобие MCP-серверов, популярных в системах с ИИ-агентами.

Тем не менее, внешний сервис не всегда заменяет локальный git. Если приложение оперирует собственной рабочей копией, перенос операций во внешний сервис порождает две независимые файловые системы, требующие постоянной синхронизации. При использовании общей директории через volume возникает риск компрометации через Git hooks, если запуск хуков не изолирован должным образом. Именно поэтому для ряда задач оптимальным выбором остается локальный OpenSSH, работающий через защищенный сокет.

Проблема несовпадения UID в rootless Docker
Разница между идентификаторами процессов внутри контейнера и на хосте.
Сопоставление UID пользователя rootless-docker
Пример маппинга системных идентификаторов пользователя.

Особенности сопоставления UID в rootless Docker

Прежде чем переходить к настройке агента, необходимо разобраться в механизме пользовательских пространств имён. В стандартной конфигурации идентификатор пользователя внутри контейнера совпадает с хостовым. В rootless-режиме демон запускается от непривилегированного пользователя, а ядро Linux выполняет маппинг UID через пространства имён на основе файлов /etc/subuid и /etc/subgid.

Например, внутренний пользователь app с UID 1000 может отображаться на хосте как UID 100999. Проверка клиентов Unix-сокета выполняется на стороне хоста, поэтому стандартный агент аутентификации отклонит подключение из-за несовпадения идентификаторов, даже если права доступа к самому файлу сокета установлены максимально широко. Обычное применение параметра chmod 0666 не решит проблему, так как агент проверяет реальный UID подключившегося процесса.

Реклама

Настройка изолированного SSH-агента на хосте

Для согласования идентификаторов я запускаю отдельный экземпляр агента с помощью служебной службы systemd. Сам юнит работает от привилегированного пользователя для безопасного чтения файла ключа, а утилита setpriv понижает права непосредственно для процесса агента до вычисленного mapped UID.

Проверка отклонения соединения агентом
Блокировка соединения из-за различий в правах доступа.

Для реализации этой схемы на хосте создается специальная структура каталогов:

  • /opt/ssh-access/.ssh/git/ — защищенная директория с парой ключей Ed25519;
  • /opt/ssh-access/sockets/git/ — каталог для размещения Unix-сокета;
  • /opt/ssh-access/ssh_known_hosts — файл с предварительно проверенными ключами удаленных серверов.

Корневой каталог настраивается с правами 0711, что позволяет процессам проходить по пути, но исключает листинг содержимого. Директория сокета создается с правами 0710, а владельцем назначается mapped UID. Такой подход позволяет монтировать в контейнер не сам сокет, а родительский каталог, благодаря чему агент может пересоздавать сокет без перезапуска контейнера.

Запуск и проверка защищенного контейнера

При сборке собственного Dockerfile я создаю пользователя с фиксированным идентификатором 1000:1000. Запуск контейнера выполняется с жесткими ограничениями привилегий, подключением временных файловых систем и двумя точками монтирования: каталога с сокетом и файла доверенных хостов.

В процессе запуска контейнера я применяю следующие флаги:

  • --user 1000:1000 задает непривилегированного пользователя;
  • --read-only переводит корневую ФС в режим чтения;
  • --cap-drop ALL удаляет все системные возможности;
  • --security-opt no-new-privileges блокирует эскалацию прав;
  • --v подключает сокет и файл известных хостов только для чтения (:ro).

После старта контейнера проверка через команду ssh-add -l подтверждает успешную загрузку ключа. Утилиты git и ssh начинают работать штатно, используя переданный переменной окружения SSH_AUTH_SOCK путь к сокету.

Эксплуатация, ограничения и границы защиты

При обслуживании инфраструктуры важно учитывать особенности поведения bind-монтирования. Полное удаление и повторное создание директории сокета на хосте приводит к повреждению существующих привязок внутри запущенных контейнеров — в таком случае источник начинает отображаться как удаленный объект. Чтобы избежать простоев, следует удалять и пересоздавать файлы внутри каталога, не затрагивая саму примонтированную директорию.

Согласование UID клиента и агента аутентификации
Схема настройки единого пространства идентификаторов.
Изоляция процессов с помощью setpriv
Использование утилиты setpriv для безопасного запуска агента.
Структура каталогов и файлов конфигурации
Расположение защищенных ключей и сокетов на хосте.

Описанная архитектура гарантирует, что файл приватного ключа никогда не попадает в пределы контейнера. Процесс может выполнять операции подписи и аутентификации исключительно через вызовы агента, однако доступ к сокету равносилен делегированному доступу к заложенным ключам. Такой подход в сочетании с изоляцией rootless Docker позволяет выстроить надежную и безопасную среду для выполнения автоматизированных задач.

01.