В этой заметке мы рассмотрим механизм “Forced command” в SSH, который позволяет ограничить SSH-ключи выполнением одной команды.
🖐️Эй!
Подписывайтесь на наш телеграм @r4ven_me📱, чтобы не пропустить новые публикации на сайте😉. А если есть вопросы или желание пообщаться по тематике — заглядывайте в Вороний чат @r4ven_me_chat🧐.
Forced command - это механизм, позволяющий привязать SSH-ключ к выполнению строго одной команды, указанной в authorized_keys. Пользователь при этом не получает интерактивную оболочку, не может выполнять другие команды, а работает только в рамках заданного сценария.
Данный механизм используется для:
- Автоматизации (скрипты, бэкапы, развёртывание), когда нужно выдать минимально необходимый доступ;
- Безопасности - ограничение ключа одной операцией снижает риск, если ключ будет скомпрометирован;
- Интеграций между серверами - например, разрешить запуск только
rsync,mysqldumpи т.п.
В недавней статье про то, как поднять свой статический сайт с помощью Hugo, я использовал данный приём с SSH, чтобы инициировать запуск деплой скрипта на Bash.
Т.к. тема интересная, решил сделать отдельную заметку с примерами👨💻.
Подготовка на клиенте - desktop.lan
Сперва определим вводные данные:
| Key | Value |
|---|---|
| Сервер | test.r4ven.me |
| Клиент | desktop.lan |
| Пользователь | ivan |
☝️Следующие действия выполняются на локальной машине desktop.lan от имени обычного пользователя (ivan в моём случае).
На клиентской машине генерируем новый ключ SSH:
ssh-keygen -q -N "" -t ed25519 -f ~/.ssh/id_ed25519_test-q: Quiet mode - минимизирует вывод в консоль;-N "": New passphrase - устанавливает пустую парольную фразу (passphrase) для ключа, что делает ключ незащищенным паролем. Двойные кавычки означают пустую строку. Уберите данный флаг, если хотите установить парольную фразу;-t ed25519: Type - указывает тип ключа для генерации, в данном случаеed25519- современный, более безопасный алгоритм;-f ~/.ssh/id_ed25519_test- Filename - указывает имя файла, в который будет сохранен приватный ключ (и соответствующий публичный ключ с расширением.pub).
В данном случае, ключи будут сохранены в директории ~/.ssh под именами id_ed25519_test (приватный) и id_ed25519_test.pub (публичный).
Выведем в терминал публичную часть нашего ключа, ту что с суффиксом .pub:
cat ~/.ssh/id_ed25519_test.pubПолучите строку вида:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAXsCuHrzzbYmJXJqeui8j78pkYf+VbYuGeX8iK/W0EW ivan@r4ven-meНа этом настройка клиента завершена, переходим к серверной части.
Подготовка на сервере - test.r4ven.me
☝️Следующие действия выполняются на удалённом сервере test.r4ven.me от имени обычного пользователя (ivan в моём случае).
Теперь на сервере создаём специально настроенный authorized_keys для SSH:
mkdir -p ~/.ssh/
chmod 700 ~/.ssh/
echo 'command="echo Pong",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAXsCuHrzzbYmJXJqeui8j78pkYf+VbYuGeX8iK/W0EW ivan@r4ven-me' >> ~/.ssh/authorized_keysГде в место ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAXsCuHrzzbYmJXJqeui8j78pkYf+VbYuGeX8iK/W0EW ivan@r4ven-me
укажите свой публичный ключ, который вывели в терминале на клиентской машине шагом ранее.
Строка в файле authorized_keys содержит публичный ключ SSH и специфическую команду. Суть в том, что при подключении по SSH с использованием соответствующего приватного ключа, вместо открытия интерактивной оболочки, будет выполнена только указанная команда, в данном примере - echo Pong🏓.
Также указаны дополнительные опции:
no-port-forwarding: запрещает перенаправление портов;no-X11-forwarding: запрещает перенаправление X11;no-agent-forwarding: запрещает перенаправление SSH-агента;no-pty: запрещает запрос псевдо-терминала (интерактивные сессии).
Серверную часть тоже настроили, давайте перейдём к тестам.
Тестирование работы Force command
☝️Следующие действия выполняются на локальной машине desktop.lan от имени обычного пользователя (ivan в моём случае).
Пытаемся подключиться к серверу с помощью нашего нового ключа:
ssh -i ~/.ssh/id_ed25519_test ivan@test.r4ven.me
Если попробовать выполнить произвольную команду, например ls -l :

В выводе все равно отобразитсяPong🏓.
Или пробросить SSH порт:
ssh -i ~/.ssh/id_ed25519_test -L 8888:localhost:9999 ivan@test.r4ven.me
Всё равно Pong🏓.
Практический пример: запуск команды с помощью sudo
Теперь давайте настроим, например запуск скрипта /opt/script.sh через утилиту sudo:
☝️Следующие действия выполняются на удалённом сервере test.r4ven.me от имени root пользователя.
Создаём sudoers файл:
sudo visudo -f /etc/sudoers.d/90_scriptНаполняем:
ivan ALL=(root) NOPASSWD: /opt/script.sh☝️Важно
Вместо ivan укажите имя вашего пользователя.
❗️ Осторожно
Будьте очень внимательны при редактировании файлов sudo. Если допустите синтаксическую ошибку, можете потерять доступ к привилегированному режиму через sudo. Всегда держите второй терминале с УЗ root открытым на всякий случай.
Теперь создадим на сервере тестовый script.sh:
echo -e '#!/usr/bin/bash\necho Sudo-Script-Pong' | sudo tee /opt/script.sh
sudo chmod 700 /opt/script.shРедактируем файл ~/.ssh/authorized_keys и меняем команду на sudo /opt/script.sh:
from="10.10.10.5",command="sudo /opt/script.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAXsCuHrzzbYmJXJqeui8j78pkYf+VbYuGeX8iK/W0EW ivan@r4ven-me📝Обратите внимание, что вначале строки мы добавили новый параметр: from="10.10.10.5", который ограничивает выполнение заданной команды хостом с конкретным IP адресом.
Давайте протестируем его работу с клиента, на котором мы сгенерировали ключ: id_ed25519_test.
☝️Следующие действия выполняются на локальной машине desktop.lan.
Пробуем подключиться к серверу:
ssh -i ~/.ssh/id_ed25519_test ivan@test.r4ven.meВ ответ вы должны получить Sudo-Script-Pong из нашего script.sh:

Если это так, то мы все настроили корректно. Наполните script.sh необходимой вам логикой и запускайте безопасно.
Разрешить только передачу файлов через sftp
Подобным образом можно разрешить доступ только к подсистеме передачи файлов sftp. В этом случае строка в authorized_keys будет выглядеть так:
command="internal-sftp",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAXsCuHrzzbYmJXJqeui8j78pkYf+VbYuGeX8iK/W0EW ivan@r4ven-meА подключаться так:
sftp -i ~/.ssh/id_ed25519_test ivan@test.r4ven.me
Вместо заключения
Важно понимать, что механизм forced command в SSH значительно снижает риск неправомерного использования ключа, но он не является абсолютной защитой: если ограниченная команда или сценарий содержат уязвимости (например, позволяют выполнять произвольные аргументы, подменять файлы или эскалировать доступ), злоумышленник всё равно может обойти ограничения.
Поэтому с умом подходите к настройке безопасности своих систем. Удачи!


