Как простой скрипт Bash нанес серьезный ущерб и как легко его предотвратить.

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

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

Заказчик позвонил нам

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

Скрипт состоял примерно из 100 строк кода и заканчивался примерно так:

#! /usr/bin/bash
some-program $INPUT_PARAMETER | tar -zcf backup.tgz $INPUT_PARAMETER.txt
rm *.$RUN_DATE.txt

Ага ... он был запущен нашим клиентом ... а что касается some-program, он принимает фильтр в качестве входных данных для сужения данных, с которыми он работает ... в базе данных ...

Сколько потенциальных проблем (или катастроф) вы можете найти в этих двух строках?

ответ ниже

Диагностика

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

  1. Путь переводчика
  2. Неустановленные переменные расширяются до пустой строки по умолчанию
  3. Ошибки в конвейере маскируются следующими командами
  4. Сценарий не дает сбой при ненулевом коде выхода команды
  5. Слепое использование инструментов

Хорошо, последний не имеет отношения к Bash.

Да вы уже догадались, все было в одном простом сценарии!

Посмотрим, как это применимо к нашему скрипту.

  1. Путь интерпретатора. Из-за этого скрипт вообще не запускался, поскольку в этой системе bash не был найден по пути /usr/bin/bash. Наш клиент исправил это и с гордостью снова запустил скрипт.
  2. Неустановленные переменные, расширяющиеся до пустой строки по умолчанию - в результате some-program выполнял операции со всем набором данных вместо применения фильтра и сужения области.
  3. Ошибки в конвейере, замаскированные с помощью следующих команд - интересно, что some-program будет завершаться с ненулевым кодом, когда он работает со всем набором данных, также было больше флагов, таких как --force set, и сценарий в значительной степени полагался на это.
  4. Сценарий не дает сбой при коде выхода команды, отличном от нуля - это в сочетании с нерасширенной переменной привело к сбою tar, а выходные файлы (которые могут помочь легко восстановить повреждения) не были заархивированы - просто не было .txt файла.
  5. Использование инструментов вслепую - эта ситуация учит и напоминает, что нельзя никому доверять и проверять / тестировать инструменты, прежде чем использовать их вживую.

Фиксация

Хорошо, чтобы исправить такое количество недостатков, может потребоваться много работы? (Давайте сосредоточимся только на технических деталях, ошибочная логика реализации здесь будет другой темой)

Требуется всего 2 строки

#!/usr/bin/env bash
set -euo pipefail

Посмотрим, как это исправить недостатки скрипта:

  1. Путь интерпретатора
    #!/usr/bin/env bash в системах, Bash может быть установлен в другом месте, с этим Shebang поиск Bash будет выполняться в PATH. Конечно, здесь можно использовать и другие интерпретаторы #!/usr/bin/env python и #!/usr/bin/env ksh.
  2. Неустановленные переменные, по умолчанию расширяющиеся до пустой строки
    set -e этот параметр вызывает немедленный выход из bash, если какая-либо команда вернула ненулевой код выхода. Это в сочетании с -o pipefail остановит выполнение сценария сразу после some-program.
    Это не поведение Bash по умолчанию, потому что невыполненная команда завершит сеанс оболочки CLI. Но это определенно ожидается в сценариях.
  3. Ошибки в конвейере, замаскированные следующими командами
    set -o pipefail если какая-либо команда в конвейере завершится с ненулевым кодом, конвейер будет завершен, и этот код будет распространен как код выхода для всего конвейера.
  4. Сценарий не завершается ошибкой при коде выхода команды, отличном от нуля
    set -u вызывает ошибку при обращении к ранее необъявленным переменным. В нашем случае это предотвратит повреждение сценария.
    ПРИМЕЧАНИЕ: Иногда вы не знаете, объявлена ​​ли переменная раньше (например, переменные env), или вам нужно создать другой файл, который может вызвать эту ошибку. В этом случае вы можете временно отключить эту проверку:
set +u
# do some check, or source files
set -u

Вот и все!

Заключение

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

Также помните: не запускайте неизвестные инструменты и программное обеспечение в уязвимой среде, как это ни парадоксально, запуск какой-нибудь простой тестовой виртуальной машины или образа Docker может сэкономить вам много времени!

Спасибо за прочтение!

Источники

Https://kodeit.dev/how-to-prevent-bash-scripts-from-causing-damage/ изначально размещено в моем блоге

Http://redsymbol.net/articles/unofficial-bash-strict-mode/ - красивое более подробное описание строгого режима Bash