Для всех, кто дотягивается до продакшена по SSH из эфемерной среды — пода GPU-облака, раннера CI, одноразовой ВМ. Без шагов атаки — только превращение ключей в наименее привилегированные, чтобы утечка причиняла меньше ущерба. Об основах разделения ключей см. базовый чек-лист.
Взгляд этого сайта: относитесь к эфемерным средам как к недоверенным
Одноразовые вычисления часто пропускают укрепление — это место, которое можно скомпрометировать. Положите туда root-ключ продакшена — и компрометация этой среды станет компрометацией продакшена. Этот сайт использует отдельный ключ на хост, ограничивает каждый назначением и удаляет ключи, как только они перестают использоваться (→ отдельные ключи для минимизации ущерба). Относиться к ключу не как к «удобству», а как к «самому критичному активу, который при утечке даёт доступ к каждому серверу», — в конечном счёте самый безопасный подход.
✗ root-ключ в эфемерной среде
среда скомпрометирована → root в продакшене с этим ключом → достаёт файлы, секреты и другие службы — всё.
○ ключ не root с ограничением команды
даже если тот же ключ утечёт, он может выполнить только одну указанную команду. Нет оболочки, нет проброса — ущерб локализован до одной операции.
Почему root-ключ в эфемерной среде опасен
Эфемерные среды склонны пропускать укрепление («мы её скоро удалим»), что делает их лёгкой целью и удобной промежуточной точкой для атаки на продакшен. Root-ключ продакшена там делает ущерб максимально возможным.
Скажем, вы поднимаете под для вычислений и, чтобы работать с продакшеном из него, регистрируете ключ в authorized_keys пользователя root. Удобно — но если этот под скомпрометирован, атакующий входит в продакшен как root с этим ключом. Начнётся ли атака с RCE или с утечки ключа, конечный результат один: продакшен забран украденным ключом (связано: ключ украден через RCE, и пошли мошеннические счета).
Три шага к наименее привилегированным ключам
Перейдите от «дать полную власть, потому что удобно» к «ограничить то, что он может делать, до минимума».
Удалите root-ключ эфемерной среды
~/.ssh/authorized_keys продакшена. Если бездействует, удаление не имеет последствий. Прежде всего не оставляйте неиспользуемые ключи на месте.Даже если снова нужно, не возвращайтесь к root
Ограничьте ключ одной операцией
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@cirestrict разом отключает проброс, проброс агента, 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, созданному под назначение, а продакшен однонаправлен — «он принимает, он не выходит вытягивать» (→ продакшен только принимает). Он не оставляет постоянных ключей из эфемерных вычислений в продакшен; он подключается только при необходимости, с наименьшими привилегиями. Причина ровно та же, что в этой статье: заранее ограничить то, что может каждый ключ, чтобы одна компрометация не распространилась на всё.
Читать дальше
- Новая уязвимость: OpenSSH 10.6p2 исправляет обход парольной аутентификации в sshd на Linux с musl без PAM (CVE-2026-123158)
- Основы: базовый чек-лист безопасности (наименьшие привилегии, защита админ-аккаунтов)
- Эксплуатация: отдельные ключи для минимизации ущерба / продакшен только принимает · руководство по реагированию на уязвимости
- Случаи: компрометация цепочки поставок Trivy (SSH-ключи в CI среди целей) · атака на цепочку поставок Codecov
- Инцидент: ключ украден через RCE, и пошли мошеннические счета · утечка данных Benesse (избыточные привилегии и злоупотребление инсайдера)
Источники
- Руководство 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