Руководства по безопасности

Наименьшие привилегии SSH-ключа: не храните root-ключи продакшена в эфемерных средах, которые можно скомпрометировать

Root-ключ продакшена в эфемерной среде (GPU, CI) при утечке даёт root атакующему. Решение: пользователь не root и ключ с ограничением команды.

Опубликовано 2026-06-11 Обновлено 2026-10-06 12 мин чтения
На этой странице

Для всех, кто дотягивается до продакшена по SSH из эфемерной среды — пода GPU-облака, раннера CI, одноразовой ВМ. Без шагов атаки — только превращение ключей в наименее привилегированные, чтобы утечка причиняла меньше ущерба. Об основах разделения ключей см. базовый чек-лист.

Взгляд этого сайта: относитесь к эфемерным средам как к недоверенным

Одноразовые вычисления часто пропускают укрепление — это место, которое можно скомпрометировать. Положите туда root-ключ продакшена — и компрометация этой среды станет компрометацией продакшена. Этот сайт использует отдельный ключ на хост, ограничивает каждый назначением и удаляет ключи, как только они перестают использоваться (→ отдельные ключи для минимизации ущерба). Относиться к ключу не как к «удобству», а как к «самому критичному активу, который при утечке даёт доступ к каждому серверу», — в конечном счёте самый безопасный подход.

✗ root-ключ в эфемерной среде

среда скомпрометирована → root в продакшене с этим ключом → достаёт файлы, секреты и другие службы — всё.

○ ключ не root с ограничением команды

даже если тот же ключ утечёт, он может выполнить только одну указанную команду. Нет оболочки, нет проброса — ущерб локализован до одной операции.

С самого начала ограничьте, что может делать ключ. Это меняет ущерб при утечке.

Почему root-ключ в эфемерной среде опасен

Эфемерные среды склонны пропускать укрепление («мы её скоро удалим»), что делает их лёгкой целью и удобной промежуточной точкой для атаки на продакшен. Root-ключ продакшена там делает ущерб максимально возможным.

root→всё
утёкший ключ даёт полный контроль над продакшеном
одноразово
эфемерные среды пропускают укрепление
переиспользован
одна утечка раскрывает каждый хост
наим. прив.
ограничьте ключ одной операцией — и ущерб тоже будет ограничен

Скажем, вы поднимаете под для вычислений и, чтобы работать с продакшеном из него, регистрируете ключ в authorized_keys пользователя root. Удобно — но если этот под скомпрометирован, атакующий входит в продакшен как root с этим ключом. Начнётся ли атака с RCE или с утечки ключа, конечный результат один: продакшен забран украденным ключом (связано: ключ украден через RCE, и пошли мошеннические счета).

Три шага к наименее привилегированным ключам

Перейдите от «дать полную власть, потому что удобно» к «ограничить то, что он может делать, до минимума».

1

Удалите root-ключ эфемерной среды

Удалите ключи, которые больше не используете, из ~/.ssh/authorized_keys продакшена. Если бездействует, удаление не имеет последствий. Прежде всего не оставляйте неиспользуемые ключи на месте.
2

Даже если снова нужно, не возвращайтесь к root

При повторной регистрации для работы с продакшеном создайте выделенного пользователя не root и зарегистрируйте ключ на него. Даже если он утечёт, охват ограничен привилегиями этого пользователя.
3

Ограничьте ключ одной операцией

Добавьте command="..." и restrict к строке ключа в authorized_keys. Даже успешный вход может выполнить только одну указанную команду, с отключёнными пробросом порта и оболочкой. Утечка локализована до этой одной операции.
# Register a key restricted to ONE operation in a non-root user's authorized_keys.
# Logging in with it can run only the given command; forwarding and PTY are off.
command="/usr/local/bin/deploy-pull",restrict ssh-ed25519 AAAA...rest-of-public-key deploy@ci

restrict разом отключает проброс, проброс агента, PTY, X11 и прочее; command="..." фиксирует исполняемое действие на одной вещи. Базовый ход — ключ, ограниченный одним назначением — только вытянуть развёртывание, только запустить конкретный скрипт — создаваемый под каждый случай использования.

Распространённая конфигурация (опасно)

  • зарегистрировать ключ на root продакшена из пода/CI
  • оставить завершённые ключи на месте
  • переиспользовать один ключ на многих хостах
  • разбрасывать ключи, дающие полную оболочку

Наименее привилегированная конфигурация

  • дотягиваться до продакшена только как пользователь не root
  • ключи ограничены одной операцией (с ограничением команды)
  • отдельные ключи по хостам и назначениям
  • убирать ключи из authorized_keys в тот миг, когда они не используются

Относитесь к переиспользуемому ключу как к самому критичному активу

Думайте о ключе как об «удобстве» — и вы переиспользуете его и забудете удалить. Вместо этого относитесь к нему как к самому критичному активу, который при утечке даёт доступ к каждому серверу. Разделяйте по хостам и назначениям, держите ключи продакшена вне эфемерных сред и всегда убирайте их после использования. Одно это не даёт одной утечке распространиться на всё.

Основные ограничения, которые можно задать в строке ключа

В OpenSSH опции, записанные в начале строки ключа в authorized_keys, сужают то, что может этот ключ. Наиболее частые:

ОпцияЧто ограничиваетКогда применять
command="..."Вход с этим ключом выполняет только указанную команду; команда, присланная клиентом, игнорируетсяКлючи для одной фиксированной задачи, например pull развёртывания или резервное копирование
restrictРазом отключает проброс портов, агента и X11, выделение PTY и выполнение ~/.ssh/rc (OpenSSH 7.2+)Почти все ключи для автоматизации; включайте обратно только нужное, например pty
from="..."Помимо ключа, имя хоста или IP-адрес клиента должны совпадать со спискомCI или рабочие серверы с фиксированным исходным IP
expiry-time="..."После заданной даты/времени ключ больше не принимается (OpenSSH 7.7+)Ключи, которые должны работать только для временной задачи
PermitRootLogin (в sshd_config)Разрешён ли прямой вход root; forced-commands-only разрешает root только для ключей с command=no, если прямой доступ root действительно не нужен

В примечаниях к релизу OpenSSH restrict описан как включающий «все текущие и будущие ограничения ключей». Вместо того чтобы перечислять no-pty, no-port-forwarding и прочее по одному, добавьте restrict и включайте обратно только нужное — тогда ничего не проскользнёт при появлении новых функций.

# Also narrowing the source and the lifetime (the IP is a documentation address)
from="192.0.2.10",expiry-time="20261231",command="/usr/local/bin/deploy-pull",restrict ssh-ed25519 AAAA...rest-of-public-key deploy@ci-2026-10

Если вы управляете ограниченным по времени доступом для многих ключей, ещё один вариант — сертификаты OpenSSH (подпись через ssh-keygen -s, срок действия через -V, ограничения через force-command или source-address). Для небольшой конфигурации для начала достаточно опций в строке ключа.

Как проверить свою среду

Эти проверки только читают настройки и журналы серверов, которыми вы управляете. Меняя настройки, держите одну SSH-сессию открытой, проверьте новое подключение из другого терминала и закрывайте старую сессию, только убедившись, что не потеряли доступ.

1. Выведите список зарегистрированных ключей

# Show the fingerprint and comment of each key registered for root and each user
sudo ssh-keygen -lf /root/.ssh/authorized_keys
sudo ssh-keygen -lf /home/deploy/.ssh/authorized_keys

Для каждого ключа выводится одна строка вида 256 SHA256:... deploy@ci (ED25519). По комментарию (часть deploy@ci в конце) отберите ключи неизвестного происхождения, ключи из эфемерных сред, с которыми вы закончили, и ключи, зарегистрированные на root. Старое имя файла authorized_keys2 по умолчанию тоже читается, поэтому, если он есть, проверьте его так же.

2. Проверьте конфигурацию, которую реально использует sshd

# Check the syntax, then print the settings actually in effect
sudo sshd -t
sudo sshd -T | grep -iE 'permitrootlogin|authorizedkeysfile|pubkeyauthentication|passwordauthentication'

sshd -T выводит действующие значения после чтения файлов конфигурации. Если permitrootlogin равен yes или prohibit-password, любой, у кого есть ключ, может войти как root напрямую. Если authorizedkeysfile указывает не на путь по умолчанию, добавьте это место в шаг 1.

3. Посмотрите, какие ключи реально используются

После успешного входа по ключу sshd обычно пишет в журнал строку вида Accepted publickey for deploy from 192.0.2.10 port 50022 ssh2: ED25519 SHA256:.... Читайте её в /var/log/auth.log на Debian и Ubuntu, в /var/log/secure на системах семейства Red Hat или через journalctl -u ssh на хостах с systemd (-u sshd на системах семейства Red Hat).

Сопоставив эти отпечатки со списком из шага 1, вы увидите, какие ключи не используются. Отчёт NIST по управлению SSH-ключами (NISTIR 7966) советует наблюдать за журналами примерно 6–12 месяцев, прежде чем удалять неиспользуемые ключи, чтобы по ошибке не удалить редко используемые. Ключи, о которых вы точно знаете, что они больше не нужны (например, ключ эфемерной среды), можно удалить сразу.

4. Проверьте эфемерную сторону

  • Не лежит ли закрытый ключ, дающий доступ к продакшену, в секретах CI или в ~/.ssh пода GPU?
  • Не подключаетесь ли вы к эфемерной среде через ssh -A (проброс агента) по пути к продакшену?
  • Не используется ли один ключ в нескольких системах CI или на нескольких серверах?

Частые ошибки и как их исправить

  • Удалить закрытый ключ на эфемерной стороне и на этом остановиться: если закрытый ключ уже скопировали, он продолжает работать, пока открытый ключ остаётся на сервере. Всегда удаляйте соответствующую строку из authorized_keys продакшена.
  • Выполнять команду клиента (переменная окружения SSH_ORIGINAL_COMMAND) как есть внутри скрипта, вызываемого через command=: это то же самое, что отсутствие ограничения. Используйте значение, только если оно точно совпадает с одним из фиксированного набора вариантов.
  • Перейти на пользователя не root, но разрешить ему неограниченный sudo: если этот пользователь может выполнить через sudo что угодно без пароля, он по сути root. Если sudo нужен, ограничьте его одной командой в sudoers.
  • Входить в эфемерную среду через ssh -A: любой, у кого там достаточно привилегий, может использовать ключи из вашего локального агента через проброшенное соединение (руководство OpenSSH предупреждает об этом). Если нужно пройти через хост, используйте ssh -J (подключение через промежуточный хост).
  • Оставлять ключи без комментария: при проверке вы не сможете понять, откуда они. Указывайте назначение и дату при создании, например ssh-keygen -C "deploy@ci-2026-10".
  • Использовать один ключ на нескольких людей или среды: NISTIR 7966 тоже требует запрещать совместное использование закрытых ключей и их копирование на другие системы. Вы не сможете отследить, кто и где его использовал, а одна утечка означает замену везде.

Реальный пример: SSH-ключи были среди того, что собирал скомпрометированный инструмент CI

В марте 2026 года появился вредоносный релиз открытого сканера уязвимостей Trivy, а его теги GitHub Actions были перезаписаны. По словам разработчика, Aqua Security, вредоносный код собирал учётные данные с раннеров CI и отправлял их наружу, а среди его целей были закрытые ключи в ~/.ssh, authorized_keys, known_hosts и SSH-ключи хоста. Aqua просит всех, кто мог запускать затронутую версию, считать раскрытым каждый секрет, который мог прочитать этот конвейер, включая SSH-ключи, и немедленно их сменить (подробности: компрометация цепочки поставок Trivy (март 2026)).

В случае Codecov в 2021 году скрипт, запускаемый в CI, тоже был изменён у источника, и около двух месяцев секреты из CI-сред клиентов отправлялись наружу (→ атака на цепочку поставок Codecov).

Оба случая показывают, что ключи внутри CI могут забрать, даже если ваш собственный код в порядке. Если забранный ключ зарегистрирован на root продакшена, продакшен в этот момент передан атакующему. С выделенным пользователем не root, command= плюс restrict и источником, суженным через from=, тот же утёкший ключ может выполнить только одну заданную вами задачу, а подключения откуда-либо ещё отклоняются.

Что делает сам этот сайт

Этот сайт использует отдельный ключ на сервер и никогда не переиспользует ключи. Развёртывания приходят к пользователю не root, созданному под назначение, а продакшен однонаправлен — «он принимает, он не выходит вытягивать» (→ продакшен только принимает). Он не оставляет постоянных ключей из эфемерных вычислений в продакшен; он подключается только при необходимости, с наименьшими привилегиями. Причина ровно та же, что в этой статье: заранее ограничить то, что может каждый ключ, чтобы одна компрометация не распространилась на всё.

Читать дальше

Источники

  • Руководство OpenBSD, «sshd(8)», AUTHORIZED_KEYS FILE FORMAT (command, restrict, from, expiry-time, SSH_ORIGINAL_COMMAND): man.openbsd.org
  • Руководство OpenBSD, «sshd_config(5)», PermitRootLogin и AuthorizedKeysFile: man.openbsd.org
  • Руководство OpenBSD, «ssh(1)», -A (предупреждение о пробросе агента) и -J: man.openbsd.org
  • Руководство OpenBSD, «ssh-keygen(1)», -l, -C и опции сертификатов: man.openbsd.org
  • Примечания к релизу OpenSSH 7.2 (добавлен restrict): openssh.org
  • Примечания к релизу OpenSSH 7.7 (добавлен expiry-time): openssh.org
  • NIST, «NISTIR 7966: Security of Interactive and Automated Access Management Using Secure Shell (SSH)» (октябрь 2015 г.): csrc.nist.gov
  • Wikibooks, «OpenSSH/Logging and Troubleshooting» (отпечатки ключей в строках журнала об успешном входе): en.wikibooks.org
  • Aqua Security, «Trivy Supply Chain Attack: What You Need to Know» (2026): aquasec.com
  • Codecov, «Bash Uploader Security Update» (2021): about.codecov.io

FAQ

QПочему опасно класть root-ключ в эфемерную среду?
A

Эфемерные среды вроде подов GPU-облака или раннеров CI одноразовы, поэтому укрепление часто пропускают, и их можно скомпрометировать. Положите туда root-ключ к продакшену — и в тот миг, когда эта среда скомпрометирована, атакующий получит root в продакшене вместе с ключом. Относитесь к эфемерным средам как к недоверенным и держите root-ключи продакшена вне их.

QЧто, если мне всё же нужно дотянуться до продакшена из эфемерной среды?
A

Не возвращайтесь к root. Создайте выделенного пользователя не root и зарегистрируйте ключ, ограничивающий то, что он может делать, одной операцией. В authorized_keys ключ с ограничением команды и флагом restrict означает, что даже успешный вход может выполнить только эту одну команду. Утечка тогда локализована до этой одной операции.

QКаков ключевой принцип управления ключами?
A

Относитесь к переиспользуемому ключу как к своему самому критичному активу и никогда не стройте конфигурацию, где одна утечка раскрывает всё. Разделяйте ключи по хостам и назначениям и убирайте ключи из authorized_keys, когда они больше не нужны. Это не даёт одной утечке распространиться на всё.

QКак проверить, какие SSH-ключи зарегистрированы на сервере?
A

Выполните ssh-keygen -lf для ~/.ssh/authorized_keys каждого пользователя — команда выведет все зарегистрированные ключи с отпечатком (SHA256:...) и комментарием. Проверьте все аккаунты, включая root, и отберите ключи, происхождение которых не можете объяснить, и ключи из эфемерных сред, которыми больше не пользуетесь. Чтобы увидеть, какие ключи реально используются, сопоставьте эти отпечатки со строками об успешном входе, которые sshd пишет в журнал.

QКакое значение задать для PermitRootLogin?
A

По умолчанию в OpenSSH стоит prohibit-password: вход root по паролю запрещён, но любой, у кого есть ключ, может войти как root напрямую. Если прямой вход root вам не нужен, задайте no и работайте от пользователя не root. Если чему-то вроде резервного копирования действительно нужен root-ключ, forced-commands-only разрешает root только для ключей с опцией command="...".

QКак безопасно развёртывать по SSH из CI или GitHub Actions?
A

Раннеры CI — типичная эфемерная среда, и есть реальные случаи, когда инструмент, используемый в CI, был скомпрометирован и собирал секреты, включая SSH-ключи. Зарегистрируйте ключ развёртывания на выделенного пользователя не root и ограничьте его одним действием pull/deploy через command="..." и restrict. Если исходный IP фиксирован, сузьте его через from="...". Используйте отдельный ключ на каждый конвейер и среду, а при объявлении о компрометации сначала удалите старые ключи, затем зарегистрируйте новые.