Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Создание безопасного хранилища для важных данных с использованием сценариев bash

Оригинал: Hacking a Safe with Bash
Автор: Adam Kosmin
Дата публикации: 28 июля 2015 г.
Перевод: А.Панин
Дата перевода: 25 августа 2015 г.

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

Алгоритм асимметричного шифрования

Алгоритм асимметричного шифрования или криптоалгоритм с открытым ключом основывается на использовании двух ключей: один из этих ключей хранится в тайне, другой - свободно публикуется. Данная модель позволяет достичь большей безопасности данных по сравнению с моделью, предусматривающей использование алгоритма симметричного шифрования, который основывается на передаче единственного ключа. GnuPG является свободным программным обеспечением, реализующим стандарт OpenPGP в соответствии со спецификацией RFC4880. GnuPG поддерживает как алгоритмы асимметричного шифрования, так и алгоритмы симметричного шифрования. Обратитесь к ресурсу http://gnupg.org для получения дополнительной информации.

GPG

В данной статье описана методика интенсивного использования утилиты GPG для взаимодействия с файлами из защищенного хранилища. На данный момент существует множество руководств и инструкций, в которых пошагово описан процесс корректной настройки хранилища ключей и управления ими. Также настоятельно рекомендуется настроить демон gpg-agent для того, чтобы избежать необходимости ввода пароля при каждом обращении к вашему закрытому ключу. Одним из наиболее популярных инструментов для выполнения данной задачи является утилита Keychain, которая также позволяет осуществлять настройку демона ssh-agent.

Давайте рассмотрим классический пример управления паролями для доступа к различным ресурсам. Это всегда непросто и несмотря на то, что существуют такие интересные утилиты, как pass и KeePassС, я пока не уверен, что они хорошо впишутся в мой рабочий процесс. Кроме того, я не в восторге от любого варианта функции копирования пароля в буфер обмена. Наверняка каждый из вас был свидетелем непреднамеренного попадания данных из буфера обмена какого-либо пользователя в чат IRC или на аналогичные ресурсы - нет, спасибо, мне такой функции не нужно! В данный момент я предлагаю остановиться на концепции "безопасного хранилища данных", предусматривающей доступ к данным, хранящимся в файле. Каждая строка файла будет соответствовать следующему простому формату:

ресурс:идентификатор_пользователя:пароль

Причем в поле "ресурс" должен использоваться какой либо мнемонический идентификатор ресурса, такой, как полностью определенное доменное имя FQDN или даже название такого устройства, как маршрутизатор, через который может осуществляться доступ к системе по протоколу telnet. Строки из полей "идентификатор пользователя" и "пароль" могут быть подсказками. Данный подход с использованием подсказок отлично работает ввиду моего постоянного стремления к сокращению количества постоянно используемых пар "имя пользователя - пароль". Благодаря этому обстоятельству подсказка является единственным необходимым ориентиром для напряжения памяти и восстановления пароля. В том случае, если определенный ресурс использует какие-либо экзотические ограничения, связанные со сложностью пароля, я могу быстро сделать вывод о необходимости небольшого изменения, отредактировав подсказку соответствующим образом. К примеру, подсказка "fo" может превратиться в подсказку "!fo" или "fO". Другой пример достижения баланса между безопасностью и удобством использования актуален в том случае, если вам необходимо использовать очень длинный пароль. Одно из практических решений может заключаться в комбинировании хорошо знакомых паролей и соответствующем оформлении подсказки. К примеру, подсказка о необходимости комбинирования паролей "fo" и "ba" может быть представлена в форме "fo..ba". Наконец, подход на основе использования подсказок предоставляет дополнительную защиту паролей, так как ограниченный объем информации едва ли сможет быть использован похитителем по назначению.

Несмотря на сокрытие информации, хранение упомянутых данных в незашифрованном виде является глупым и безответственным поступком. Настроенная утилита GnuPG позволяет зашифровать данные с использованием вашего скрытого ключа. После создания файла мой рабочий процесс выглядит аналогичным образом:

$ gpg --ear <идентификатор_моего_ключа> <файл>
$ shred -u <файл>

Последующее обновление данного файла будет связано с его расшифровкой, редактированием и повторением приведенных выше шагов. Такое положение вещей было допустимо в течение некоторого времени, так как по сути я не меняю пароли ежедневно. Однако, в процессе выполнения описанной последовательности действий я прекрасно отдавал себе отчет в том, что рано или поздно наступит день, когда эта утомительная последовательность действий станет слишком сложной для выполнения. Как и ожидалось, этот день наступил тогда, когда мне потребовалось зашифровать с помощью этой же методики файлы, содержащие информацию о страховании. Теперь, когда я перешел к разговору о работе с множеством файлов, пришло время разработать сценарий, который будет использоваться в качестве обертки. Требования, предъявляемые мной к данному файлу, являются достаточно простыми:

  1. Следует задействовать такие стандартные инструменты, как GPG, shred и встроенные функции командной оболочки bash.

  2. Следует свести к минимуму количество символов, которое придется вводить для выполнения каждой из стандартных операций (а именно, операций шифрования и дешифрования файлов, а также дополнительных операций).

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

  4. Следует использовать простые текстовые файлы, но по возможности избегать их тонкой модификации.

Любопытно, что плагин Vim vim-gnupg полностью удовлетворяет этим требованиям, так как отлично работает с файлами с расширениями .asc, .gpg или .pgp. Но, несмотря на его возможности, я хотел бы избежать работы с множеством зашифрованных файлов и работать с более высокоуровневым "хранилищем" данных. Исходя из этих соображений, был создан следующий начальный прототип сценария:

#!/bin/bash

CONF=${HOME}/.saferc
[ -f $CONF ] && . $CONF
[ -z "$SOURCE_DIR" ] && SOURCE_DIR=${HOME}/safe
SOURCE_BASE=$(basename $SOURCE_DIR)
TAR_ENC=$HOME/${SOURCE_BASE}.tar.gz.asc
TAR="tar -C $(dirname $SOURCE_DIR)"

usage() {
cat <

Данный прототип сценария достаточно прост для создания на его основе полноценного сценария и соответствует некоторым из приведенных выше требований. Для начала он позволяет избежать тонкой модификации текстовых файлов благодаря хранению этих файлов в одном архиве формата tar. В качестве значения переменной $SOURCE_DIR будет использоваться значение $HOME/safe в том случае, если вы четко не установите это значение в рамках файла конфигурации ~/.saferc. Если мыслить масштабно, то можно сделать вывод и о том, что данный подход позволит совместно работать над данным проектом различным людям без необходимости изменения значения переменной снова и снова. В любом случае, значение переменной $SOURCE_DIR используется для формирования значений переменных $SOURCE_BASE, $TAR_ENC и $TAR. В том случае, если в моем файле конфигурации ~/.saferc в качестве значения переменной $SOURCE_DIR будет использоваться строка $HOME/foo, мой файл с важными данными будет сохраняться по пути $HOME/foo.tar.gz.asc. Если же я решу, что использовать файл конфигурации ~/.saferc не следует, мой файл с важными данными будет сохраняться по пути $HOME/safe.tar.gz.asc.

Возвращаясь к данному примитивному сценарию, давайте обратим особое внимание на возможность открытия и закрытия хранилища важных данных. Давайте начнем работу с реализации функции создания хранилища важных данных create_safe() для того, чтобы впоследствии имелась возможность что-либо извлечь из него:

create_safe() {
  [ -d $SOURCE_DIR ] || { "Директория отсутствует: $SOURCE_DIR"; exit 1; }
  $TAR -cz $SOURCE_BASE | gpg -ear $(whoami) --yes -o $TAR_ENC
  find $SOURCE_DIR -type f | xargs shred -u
  rm -fr $SOURCE_DIR
}

Функция create_safe() на данном этапе выглядит достаточно неплохо ввиду того, что она предназначена для автоматизации нескольких муторных операций. В первую очередь проводится проверка существования базовой директории для хранения данных архива. В случае существования этой директории файлы из нее помещаются в архив формата tar, данные которого передаются по программному каналу непосредственно на вход утилиты GPG с целью их шифрования и записи в результирующий файл. Обратите внимание но то, каким образом используется вывод утилиты whoami после параметра -r утилиты GPG. Данный параметр используется для того, чтобы при обращении к файлу закрытого ключа использовался тот же идентификатор пользователя, который был получен пользователем после входа в систему. Описанный прием используется исключительно для удобства ввиду того, что я слежу за соответствием упомянутых значений, но в том случае, если вы используете другое рабочее окружение, вам придется модифицировать сценарий. Фактически, рано или поздно в файле конфигурации ~/.saferc должны будут появиться параметры для изменения данного поведения. На данный же момент я предлагаю отложить эту идею в долгий ящик. Наконец, данная функция позволяет использовать исполняемый бинарный файл для обработки всех файлов из базовой директории. Такой подход позволит избавиться от надоедливого вопроса "Сохранилась ли незашифрованная версия текстового файла где-либо на диске?" путем автоматизации процесса очистки содержимого базовой директории.

Теперь у вас должна появиться возможность создания безопасного хранилища для важных данных. Если предположить, что файл конфигурации ~/.saferc существует и значение переменной $PATH содержит путь к директории со сценарием safe.sh, вы можете приступить к тестированию сценария:

$ cd
$ mkdir safe
$ for i in $(seq 5); do echo "важные данные #$i" > 
  safe/file${i}.txt; done
$ safe.sh -c

После этого в вашей домашней директории будет создан файл с именем safe.tar.gz.asc. Это зашифрованный архив формата tar, содержащий пять файлов, созданных ранее в директории ~/safe. После этого осуществляется очистка, заключающаяся в удалении каждого из файлов и последующем удалении директории ~/safe. Скорее всего, это отличный момент для осознания того, что архитектура будущего сценария предусматривает обработку отдельной директории с файлами. Для моих целей этого вполне достаточно. В том же случае, если вы желаете обрабатывать файлы из поддиректорий, вам придется произвести соответствующий рефакторинг сценария.

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

extract_safe() {
  [ -f $TAR_ENC ] || { "Файл отсутствует: $TAR_ENC"; exit 1; }
  gpg --batch -q -d $TAR_ENC | $TAR -zx
}

Если не вдаваться в детали, в данном случае утилиты GPG и tar всего лишь используются в противоположном порядке. После открытия безопасного хранилища для важных данных путем запуска сценария с параметром -x вы должны обнаружить директорию ~/safe.

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

list_safe() {
  [ -f $TAR_ENC ] || { "Файл отсутствует: $TAR_ENC"; exit 1; }
  gpg --batch -q -d $TAR_ENC | tar -zt
}

В данном фрагменте сценария нет ничего сложного, так как используется лишь стандартная функция утилиты tar, заключающаяся в выводе списка файлов из архива без их распаковки. На данном этапе вы можете попытаться немного сократить размер приведенной функции, переместив весь код для проверки доступности файлов и директорий в отдельную функцию. Вы даже можете реализовать несложную и полезную функцию создания резервных копий и передачи их с помощью утилиты scp на удаленный узел. В Листинге 1 приведена усовершенствованная версия созданного на данный момент сценария.

Листинг 1. Сценарий safe.sh

#!/bin/bash
#
# safe.sh - обертка для взаимодействия с зашифрованным файлом архива

CONF=${HOME}/.saferc
[ -f $CONF ] && . $CONF
[ -z "$SOURCE_DIR" ] && SOURCE_DIR=${HOME}/safe
SOURCE_BASE=$(basename $SOURCE_DIR)
TAR_ENC=$HOME/${SOURCE_BASE}.tar.gz.asc
TAR="tar -C $(dirname $SOURCE_DIR)"

usage() {
cat < /dev/null
  [ $? -eq 0 ] && echo OK || echo Failed
done

После параметра -b в качестве аргумента должно следовать имя узла. При передаче этого имени архив будет перемещаться с помощью утилиты scp на соответствующий узел. В качестве бонуса вы можете использовать параметр -b несколько раз с целью передачи файл резервной копии данных на множество узлов. Это означает, что у вас имеется возможность создания задачи планировщика cron с целью автоматизации процесса создания резервных копий, при этом вы также имеете возможность запускать процесс создания резервной копии "в ручном режиме". Разумеется, вам придется самостоятельно управлять своими ключами SSH и настраивать демон ssh-agent в том случае, если вы планируете автоматизировать процесс создания резервных копий своих данных. Также следует отметить, что недавно я перешел к использованию модуля pam_ssh() для запуска демона ssh-agent, но это уже отдельный разговор.

Вернемся к коду, в котором реализована небольшая функция is_or_die(), прекращающая работу сценария в случае передачи некорректного аргумента с последующим присваиванием значения переменной $TAR_ENC. Данная функция помогает сделать сценарий коротким и очевидным, так как, в зависимости от используемого параметра или параметров, перед выполнением основной операции может потребоваться проверка наличия одного или нескольких файлов или директорий.

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

Если бы вы были начинающим разработчиком сценариев командной оболочки, показалась бы вам интересной идея, заключающаяся в реализации функции вывода содержимого отдельного файла из безопасного хранилища важных данных? Все, что вам для этого понадобится - это проверить наличие файла в архиве и модифицировать параметры утилиты tar соответствующим образом. Фактически, у вас есть возможность избежать необходимости повторного изобретения колеса благодаря простому рефакторингу вашей функции extract_safe(). Обновленная функция будет работать с отдельными файлами при соответствующем вызове. В противном случае она будет работать со всем содержимым архива. По сути данное дополнительное улучшение пользовательских качеств сценария не будет стоить вам абсолютно ничего. В случае использования стандартного пути к директории ~/safe из переменной $SOURCE_DIR пользователь сможет передать либо строку safe/my_file, либо строку my_file в качестве значения параметра -o:

list_safe() {
  is_or_die
  gpg --batch -q -d $TAR_ENC | tar -zt | sort
}

search_safe() {
  is_or_die
  FILE=${1#*/}
  for f in $(list_safe); do
    ARCHIVE_FILE=${f#$SOURCE_BASE/}
    [ "$ARCHIVE_FILE" == "$FILE" ] && return
  done
  false
}

extract_safe() {
  is_or_die
  OPTS=" -zx"
  [ $# -eq 1 ] && OPTS+=" $SOURCE_BASE/${1#*/} -O"
  gpg --batch -q -d $TAR_ENC | $TAR $OPTS
}

Финальная версия сценария safe.sh хранится по адресу https://github.com/windowsrefund/safe. Она поддерживает множество других полезных в определенных ситуациях функций, к примеру, добавление и удаление отдельных файлов. При реализации данных функций я пытался избежать извлечения всех файлов из архива перед их модификацией. Это стремление не увенчалось успехом из-за того, что утилита GNU tar отказывалась читать данные из стандартного потока ввода STDIN при использовании параметра -r. Отличной альтернативой механизму интеграции утилит GPG и tar с помощью программного канала может оказаться бинарный файл gpg-zip из пакета GnuPG. Однако, разработчик дистрибутива Arch, сопровождающий соответствующий пакет программного обеспечения, включил в него лишь страницу руководства утилиты gpg-zip. Если говорить кратко, я предпочитаю делать рабочие инструменты "настолько простыми, насколько это возможно; но не проще". Если вы заинтересованны в усовершенствовании методов добавления и удаления файлов, вы можете в любой момент отправить запрос на добавление ваших наработок в мой репозиторий исходного кода. Это также относится к функции edit_safe(), хотя я и предвижу необходимость ее рефакторинга на определенном этапе исходя из недавней активности разработчиков плагина vim-gnupg.

Интеграция с Mutt

Моим любимым клиентом электронной почты является приложение mutt. Как и многие другие люди, я настроил свой клиент электронной почты для работы с множеством учетных записей по протоколу IMAP, причем для работы с каждой из учетных записей необходимо проходить процедуру аутентификации. В общем случае, данные для аутентификации могут быть просто записаны в одном или нескольких файлах конфигурации, но подобный подход может впоследствии вызвать такие неприятные чувства, как стыд и сожаление. Вместо этого давайте воспользуемся мудрым приемом от Aaron Toponce, позволяющим наделить приложение mutt возможностью расшифровки и подключения конфигурационных файлов с данными для доступа к учетным записям:

$ echo "set my_pass_imap = l@mepassw0rd" > /tmp/pass_mail
$ safe.sh -a /tmp/pass_mail

Теперь, когда ваше безопасное хранилище важных данных содержит файл pass_mail, вы должны указать приложению mutt на необходимость чтения данных из этого файла с помощью следующей строки в файле конфигурации ~/.muttrc:

source "safe.sh -o pass_mail |"

Читая данный файл, приложение mutt инициализирует переменную с именем my_pass_imap. Эта переменная может использоваться в других областях конфигурации mutt. К примеру, в другой области конфигурации mutt могут использоваться следующие строки:

set imap_user = "my_user_id"
set imap_pass = $my_pass_imap
set folder = "imaps://example.com"
set smtp_url = smtp://$imap_user:$imap_pass@example.com

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