Правильно проанализировать аргументы в скрипте, которые ведут себя как оболочка, вызываемая через SSH

У меня есть сервер с очень ограниченным использованием, которым я хочу иметь возможность запускать две очень специфичные (и пользовательские) инструкции через SSH. Чтобы сделать это, я установил оболочку для этого ограниченного пользователя как пользовательский сценарий BASH, который будет принимать только эти две «команды» .

Это /etc/passwd для этого пользователя:

 limited:x:1000:1000:,,,:/home/limited:/usr/sbin/limited_shell.bash 

Это сценарий limited_shell.bash (ну, что у меня до сих пор, что работает неправильно):

 #!/bin/bash readonly RECORDER_SCRIPT="/usr/sbin/record.py" echo "Verifying params: \$1: $1, \$2: $2" shift case $1 in [0-9]*) nc -z localhost $1 < /dev/null >/dev/null 2>&1 result=$? if [[ $result -eq 0 ]] then echo "O" else echo "C" fi exit $result ;; record) $RECORDER_SCRIPT ${*:3} exit $? ;; *) exit 0 ;; esac exit 0 

Как вы могли бы вывести из сценария, две команды, которые я хочу, чтобы limited_shell.bash принимал, – это число и строка «запись». Когда limited_shell.bash вызывается с номером, он возвращает, соответствует ли он открытому или закрытому локальному порту. Другая разрешенная команда вызывает вызов сценария python ( /usr/sbin/record.py ), который затем записывает видео с входа. Эта вторая команда должна быть вызвана с дополнительными аргументами, и именно там начинаются проблемы.

Когда на другом компьютере я пытаюсь удаленно выполнить команду record

 ssh limited@192.168.1.5 record -option1 foo -option2 bar 

… что на самом деле прибывает в limited_shell.bash : -c 'record -option1 foo -option2 bar' (технически два аргумента, один из которых – -c а второй – целая строковая команда + args, которую я хочу выполнить )

Я думал о переносе первого аргумента ( -c ) и разбил второй аргумент (фактическая команда record с аргументами) на пробел, но это грязно и даст мне много проблем, если один из параметров, которые я хочу передать сценарию record.py является строка, содержащая пробел.

Каков правильный способ анализа команд? Я уверен, что должен быть лучший способ, чем переключение и разделение.

Ваш скрипт предназначен для реализации оболочки . То есть, интерпретатор командной строки.

Когда вы запускаете:

 ssh host echo '$foo;' rm -rf '/*' 

ssh (клиент), объединяет аргументы (с символами SPC ASCII) и отправляет их в sshd . sshd вызывает оболочку входа пользователя как:

 exec("the-shell", "-c", "the command-line") 

Вот здесь:

 exec("the-shell", "-c", "echo $foo; rm -rf /*") 

И это было бы точно так же, если бы вы запустили:

 ssh host echo '$foo;' 'rm -rf' '/*' ssh host 'echo $foo;' "rm -rf /*" ssh host 'echo $foo; rm -rf /*' 

(более поздний из них является предпочтительным, поскольку он дает более четкое представление о том, что делается).

Решать, что делать с этой командной строкой, зависит the-shell . Например, Bourne-подобная оболочка будет расширять $foo до содержимого переменной foo , она будет учитывать ; как разделитель команд, и будет расширять /* в отсортированный список не скрытых файлов в / .

Теперь, в вашем случае, вы можете делать все, что хотите. Но поскольку это предназначено для ограниченного пользователя, вы можете сделать как можно меньше, например, не расширять переменную, замену команд, глобусы, не разрешать несколько команд, а не выполнять перенаправления …

Другое дело иметь в виду, что bash читает ~/.bashrc при вызове ssh даже при неинтерактивном (как при интерпретации скриптов). Таким образом, вы, вероятно, хотите избежать bash (или, по крайней мере, называть его sh ), или убедитесь, что ~/.bashrc не написан пользователем или использует параметр --norc .

Теперь, поскольку вам решать, как интерпретировать командную строку, вы можете просто разделить одно пространство, новую строку или вкладку:

 #!/bin/sh - [ "$1" = "-c" ] || exit set -f set -- $2 case $1 in (record) shift exec /usr/sbin/record.py "$@" ;; ("" | *[!0-9]*) echo >&2 "command not supported" exit 1 ;; (*) nc ... ;; esac 

Но это означает, что record не сможет принимать аргументы, содержащие пробелы, вкладки или символы новой строки или пустые.

Если вы хотите, чтобы они могли это сделать, вам нужно предоставить какой-то синтаксис цитат.

zsh есть инструмент синтаксического анализа цитат, который может вам помочь:

 #! /bin/zsh -f [ "$1" = "-c" ] || exit set -- "${(Q@)${(Z:cn:)2}}" case $1 in (record) shift exec /usr/sbin/record.py "$@" ;; ("" | *[!0-9]*) echo >&2 "command not supported" exit 1 ;; (*) nc ... ;; esac 

Это поддерживает одиночные, двойные кавычки и обратную косую черту. Но он также учитывал бы такие вещи, как $(foo bar) как один аргумент (даже если не расширять его).

 #!/bin/bash shopt -s extglob # domain-specific language: provide a "record" command record () { /usr/sbin/record.py "$@" } command=$2 set -- $command # re-use the positional parameters case $1 in record) # let the shell parse the given command eval "$command" ;; +([0-9])) nc ... # as above ;; *) exit 0 ;; esac 

Примечание. Я использую расширенный шаблон glob +([0-9]) чтобы соответствовать слову, которое представляет собой одну или несколько цифр. Шаблон [0-9]* соответствует слову, начинающемуся с цифры, так что 1foo будет соответствовать – я предполагаю, что это не то, что вы хотите.

Осторожно, использование eval очень опасно. Подумайте о добавлении дополнительной проверки команды в команду, которую пользователь дает вам. Что происходит, когда:

 ssh limited@192.168.1.5 record -option1 foo -option2 \`rm -rf /\` 

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

Вы можете объявить, что для открытого ключа SSH разрешено запускать определенную команду . Создайте пару ключей, скопируйте открытый ключ на сервер и добавьте директиву command=… Сделайте это для каждого небольшого количества команд, которые вы хотите разрешить. Это означает, что вы набираете строки в файле ~/.ssh/authorized_keys на сервере:

 command="/path/to/command1" ssh-rsa AAAA…== key1 command="/path/to/command2" no-pty ssh-rsa AAAA…== key2 

Когда вы выполняете удаленную команду, выберите соответствующий закрытый ключ:

 ssh -i ~/.ssh/key1.id_rsa server.example.com 'this is ignored' 

Вы можете написать любой код оболочки в command директиве. Команда, предоставленная клиентом, доступна в переменной SSH_ORIGINAL_COMMAND .