Русский
Русский
English
Статистика
Реклама

Перевод Shell-скрипт, который удалил базу данных, и история о том, как ShellCheck мог бы помочь это предотвратить

Сегодня хочу рассказать об одном случае из жизни, когда невинная ошибка при написании скрипта командной оболочки привела к удалению базы данных, используемой в продакшне. Расскажу я и о том, как ShellCheck (инструмент для линтинга и анализа скриптов, выходящий под лицензией GPLv3) мог бы обнаружить эту ошибку и предотвратил бы катастрофу. Да, сразу скажу, что я автор ShellCheck.



Происшествие


Вот описание того печального происшествия, о котором я хочу рассказать. Следующее я взял из поста на StackOverflow:

Наш разработчик закоммитил код с огромной ошибкой и мы нигде не можем найти нашу базу данных MongoDB. Пожалуйста, спасите нас!!!

Он вошёл на сервер и сохранил следующий код в ~/crontab/mongod_back.sh:

#!/bin/shDUMP=mongodumpOUT_DIR=/data/backup/mongod/tmp   // TAR_DIR=/data/backup/mongod     // DATE=`date +%Y_%m_%d_%H_%M_%S`   // DB_USER=Guitang           // DB_PASS=qq____________       // DAYS=14               // 14TAR_BAK="mongod_bak_$DATE.tar.gz"  // cd $OUT_DIR             // rm -rf $OUT_DIR/*          // mkdir -p $OUT_DIR/$DATE       // $DUMP -d wecard -u $DB_USER -p $DB_PASS -o $OUT_DIR/$DATE // tar -zcvf $TAR_DIR/$TAR_BAK $OUT_DIR/$DATE    // find $TAR_DIR/ -mtime +%DAYS -delete       // 14

А потом выполнил команду ./mongod_back.sh, после чего последовало множество сообщений об отказе в доступе. Дальше он нажал Ctrl + C и сервер автоматически выключился.

Потом он связался с AliCloud, инженер подключил диск к другому, работающему серверу, что позволило бы нашему разработчику проверить диск. Далее, он понял, что некоторые папки куда-то пропали, включая /data/, где была база данных MongoDB!

P.S. Он не сделал снепшот диска.

В общем то это кошмар для любого инженера.

Анализ причин данного происшествия это интересная задачка, для решения которой достаточно будет базовых навыков написания Shell-скриптов. Если вы хотите попытаться сами во всём разобраться попробуйте, сейчас самое время это сделать. А если вам нужны подсказки вот что выдал ShellCheck после анализа данного скрипта.

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

Что пошло не так?


Вот код минимального воспроизводимого примера (MCVE), запуск которого позволит любому всерьёз и надолго испортить себе жизнь:

#!/bin/shDIR=/data/tmp  // Директория, которую нужно удалитьrm -rf $DIR/*  // Удаление директории

Тут имеется одна фатальная ошибка, которая заключается в том, что последовательность // в shell-скриптах это не комментарий. Это путь к корневой директории, аналогичный /.

На некоторых платформах строка с командой rm сама по себе может привести к тяжёлым последствиям, так как она сводится к rm -rf / с ещё несколькими аргументами. Правда, реализация этой команды в наши дни часто такого не позволяет. Случай, о котором я рассказал, произошёл на Ubuntu, а в ней GNU-реализация rm не дала бы выполнить такую команду:

$ rm -rf //rm: it is dangerous to operate recursively on '//' (same as '/')rm: use --no-preserve-root to override this failsafe

Система сообщает о том, что опасно проводить рекурсивные действия над директорией // (что то же самое, что и /), и предлагает, если надо, отключить эту защитную меру, с помощью опции --no-preserve-root.

Именно тут в игру вступает команда присвоения значения переменной.

Оболочка воспринимает присвоение значения переменной и команды как две стороны одной медали. Вот что сказано об этом в стандарте POSIX:

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

(Простая команда отличается от сложной команды, которая представляет собой структуру наподобие инструкций if или циклов for, содержащую одну или большее количество простых или сложных команд.)

Это означает, что var=42 и echo Hello это простые команды. В первой имеется одно необязательное присвоение значения и нет необязательных слов. Во второй нет необязательных присвоений значений, но есть два необязательных слова.

Это так же означает, что в состав отдельной простой команды может входить и то и другое: var=42 echo Hello.

Если не вдаваться в подробности стандарта, то окажется, что операция присвоения значения переменной в простой команде действительна только при выполнении вызванной команды. А если в простой команде нет имени команды команда присвоения действует на уровне текущей оболочки. Последнее из вышеприведённых утверждений объясняет конструкцию var=42. А вот с первым утверждением, пожалуй, стоит разобраться.

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

$ echo "$PAGER" # Показать текущее значение переменной PAGERless$ PAGER="head -n 5" man asciiASCII(7)    Linux Programmer's Manual   ASCII(7)NAMEascii - ASCII character set encoded in octal,decimal, and hexadecimal$ echo "$PAGER" # Текущее значение PAGER не изменилосьless

Именно это нечаянно и было сделано в вышеописанном случае. Так же, как в предыдущем примере новое значение PAGER действовало лишь во время выполнения команды man, тогда область действия DIR была ограничена //:

$ DIR=/data/tmp  // Директория, которую нужно удалитьbash: //: Is a directory$ echo "$DIR" # Переменная не установлена(эта команда ничего не выводит)

Это значит, что конструкция rm -rf $DIR/* превратилась в rm -rf /* и в итоге её не отловила проверка, ориентированная на rm -rf /.

(А почему команда rm просто не отказывалась бы выполняться и при передаче ей /*? Дело в том, что она не видит конструкцию /*, так как оболочка сначала раскрывает эту конструкцию и команда rm видит лишь /bin /boot /dev /data и так далее. Хотя rm, очевидно, может отказать пользователю и в удалении директорий первого уровня, это уже начнёт мешать правильному применению данной команды, что, в соответствии с философией Unix тяжкий грех.)

Как ShellCheck мог бы помочь предотвратить эту проблему?


Проанализируем в ShellCheck опасный фрагмент скрипта, который, напомню, выглядит так:

#!/bin/shDIR=/data/tmp  // The directory to deleterm -rf $DIR/*  // Now delete it

Вот что у нас получилось (тут можно поэкспериментировать с интерактивным примером):

$ shellcheck myscriptIn myscript line 2:DIR=/data/tmp  // The directory to delete^-- SC1127: Was this intended as a comment? Use # in sh.In myscript line 3:rm -rf $DIR/*  // Now delete it^----^ SC2115: Use "${var:?}" to ensure this never expands to /* .^--^ SC2086: Double quote to prevent globbing and word splitting.^-- SC2114: Warning: deletes a system directory.

Мы уже обсудили две проблемы этого скрипта, своевременное обнаружение которых могло бы помочь предотвратить неприятности:

  • Анализатор ShellCheck обратил внимание на то, что первая последовательность //, вероятно, была задумана как комментарий (вики-страница SC1127).
  • ShellCheck указал на то, что вторая последовательность // приведёт к воздействию на системную директорию (вики-страница SC2114).

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

  • ShellCheck предложил использовать конструкцию вида rm -rf ${DIR:?}/* для того чтобы остановить выполнение команды в том случае, если переменная по любой причине будет пустой или неустановленной (вики-страница SC2115).

Это свело бы на нет последствия целой плеяды ошибок, способных привести к тому, что переменная окажется пустой, включая echo /tmp | read DIR (подоболочки), DIR= /tmp (неправильная расстановка пробелов) и DIR=$(echo /tmp) (потенциальные сбои форка или команды).

Итоги


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

Если в мире есть инструменты для проверки скриптов почему бы ими не воспользоваться? Даже если (или даже не если, а когда!) вы редко пишете shell-скрипты, вы можете установить shellcheck, воспользовавшись своим менеджером пакетов, а так же установить подходящий плагин для своего редактора, вроде Flycheck (Emacs) или Syntastic (Vim) и просто, до определённого момента, обо всём этом забыть.

Когда вы, после установки этих инструментов, будете писать скрипт, редактор автоматически выведет предупреждения и даст советы. Вы можете обращать внимание на сообщения о стилистических погрешностях, а можете и не обращать, но, всё равно, возможно, вам стоит поглядывать на уведомления о неожиданных ошибках и на предупреждения. Это вполне может спасти вашу базу данных.

Совершали ли вы опасные ошибки при написании shell-скриптов?


Источник: habr.com
К списку статей
Опубликовано: 16.05.2021 12:20:18
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании ruvds.com

Системное администрирование

*nix

Linux

Shell script

Ruvds_перевод

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru