Дабы не выдирать куски кода из существующей системы сборки, я решил оформить пост в виде отдельного небольшого HOWTO, в рамках которого будет рассмотрена сборка с нуля абстрактного пакета, содержащего в себе зашифрованные данные. Типичный use-case, кроме корпоративного распространения конфигов, например, в возможности быстро и безопасно установить себе свои любимые алиасы/конфиги в новую систему из публично доступного пакета на deb://example.com/myrepo.
Иными словами, пост о том, как можно использовать openssl в postinst.
Постановка задачи
Представим себе гипотетический пакет foopkg, который устанавливает в систему три файла:
- /opt/foopkg/clear.text: не требующий защиты текст
- /opt/foopkg/sensitive.config: конфиг с двумя параметрами, один из которых секретный
- /opt/foopkg/topsecret: секретный файл с ответом на Главный Вопрос
Допустим, он распространяется в виде foopkg.deb, который доступен во внутреннем репозитории, и может установиться на любой сервер просредством apt-get install foopkg. Допустим, мы ленивые (так и есть), и мы хотим, с одной стороны, держать все в репозитории и делать только aptitude update && aptitude safe-upgrade, и после этого ничего не править руками. С другой стороны, допустим, мы немножко параноики (так и есть), и мы не хотим чтобы наши секретные настройки/конфиги кроме нас кто-то себе установил. Мы конечно знаем и про puppet, но зачем нам открывать отдельный канал к системам, когда все и так автоматически (но это тема для отдельного цикла постов) собирается в нужном нам виде?
Решение
- Шифруем файлы (или куски файлов) до сборки пакета
- Собираем пакет
- ???
- PROFIT
Оставим вопросы подписи пакетов за рамками данного HOWTO, а для сборки пакетов будем использовать equivs.
Подготавливаем среду для сборки:
mkdir -p foobuild/root_dir/opt/foopkg && cd foobuild touch {clear.txt,sensitive.config,topsecret} echo 'Package: foopkg' > control echo 'Pre-Depends: openssl' >> control # minimal control file, see man equivs-control echo -n 'ThisIsOurDeploymentMegaPassphrase' > pass
Самый простой вариант — шифровать весь файл целиком:
openssl aes-256-cbc -e -kfile ./pass -a -A -in $infile -out $outfile
Маленькая тонкость во флаге -A: с ним зашифрованный вывод не бьется на блоки по 64 символа, а идет одной строкой. Это делает процесс расшифровки в postinst, имея только #!/bin/dash, несколько проще.
Но весь файл шифровать иногда бывает не интересно. Ок, значит шифруем подстроки. Для этого сначала определяем маркеры, по которым будут определяться границы шифрования. Я для простоты использовал маркеры вида ‘___encrypt{‘ и ‘}___’, но YMMV, подойдет любой маркер, однозначно разбивающий строку, содержащуюся в файле, на подстроки.
Соответственно, тестовые файлы, уже готовые к шифрованию, выглядят примерно так:
clear.txt:
This is clear text, free to view, nothing special.
sensitive.config:
clear_param=foo secret_param=___encrypt{bar}___
topsecret:
___encrypt{this is multiline and 42}___
А сама процедура шифрования, например, вот так:
#!/usr/bin/python import re import subprocess pass_file='./pass' def _encrypt(string): encre = re.compile('___encrypt{(.*?)}___', re.S) # non-greedy enc_string = string for el in encre.findall(string): # use openssl for encryption pipe = subprocess.Popen( ['openssl', 'aes-256-cbc', '-e', '-kfile', pass_file, '-a', '-A'], stdout = subprocess.PIPE, stdin = subprocess.PIPE, stderr = subprocess.PIPE, ) enc_el = pipe.communicate(input='%s' %el)[0] # Note: -A # add decryption markers enc_string = enc_string.replace('___encrypt{%s}___'%el, '___encrypted{%s}___'%enc_el) return enc_string # just for wrapper import sys print _encrypt( open(sys.argv[1], 'r').read() )
Сделал на питоне, потому-что нагляднее и быстрее (работа со строками в sh, сложнее чем сложение или извлечение фиксированной подстроки — исключительно для сильных духом и упорных мужчин). Алгоритм самый простой: читаем файл в строку, ищем (не жадно, чтобы не захватить лишнего) все подстроки по маркерам начала и конца, шифруем (вызывая openssl для простоты), и вставляем обратно, заменив маркер начала на маркер, который понимает дешифровщик.
Последние две строчки добавлены для того, чтобы можно было пройтись по списку файлов простым скриптом, который подготовит зашифрованные файлы и добавит их в список для включения в пакет:
echo -n "Files: " >> control for file in clear.txt sensitive.config topsecret ; do ./encrypt.py $file > root_dir/opt/foopkg/$file; echo " root_dir/opt/foopkg/$file /opt/foopkg" >> control #note space! done
Пробелы в начале строк важны не только в питоне, но и в control файлах deb-пакетов, да-да.
И наконец, финальная часть, дешифровка на стороне клиента, который будет устанавливать данный пакет. Для этого создадим файл postinst, содержащий:
#!/bin/sh -e set -e PKG=foopkg ELIST="/opt/foopkg/topsecret /opt/foopkg/sensitive.config" warning() { echo "*************************************************" echo "*** WARNING! This is a protected package, ***" echo "*** please contact the maintainer, blah blah. ***" echo "*************************************************" } decrypt() { file="${1}" keyfile="${2}" for line in `grep -o -z -P '___encrypted{(.*?)}___' "${file}"`; do l=`echo $line | sed 's/___encrypted{\(.*\)}___/\1/'` d=`echo $l | openssl aes-256-cbc -d -kfile ${keyfile} -a -A` sed -i.encbackup "s@___encrypted{${l}}___@`echo "${d}"|awk '{printf("%s\\\\n", $0);}'|sed -e 's/\\\n$//'`@g" "${file}" done } # common key source PASSFILE='/root/deployment-password' if [ "$1" = configure ]; then # decrypt all encrypted stuff if [ ! -f ${PASSFILE} ]; then warning exit 1 fi for file in ${ELIST}; do decrypt "${file}" "${PASSFILE}" done fi #DEBHELPER# exit 0
Тут тоже все просто. На этапе создания пакета мы уже знаем какие файлы шифрованные, а какие нет — добавляем их в скрипт, облегчая ему жизнь. Алгоритм расшифровки аналогичен: ищем все строки по маркеру, для каждой (помните ключ -A? вот тут без него все было бы сложнее) вытаскиваем шифр и скармливаем openssl, после чего меняем переносы строк на их представление (\n), чтобы sed не ругался, делаем замену, и восстанавливаем (с добавлением в конец файла в том числе) переносы строк. К сожалению, здесь питон мы использовать уже не можем — решение проектировалось в том числе и для минимальных установок, где питона нет или еще нет (netinstall, например). Я не стал раскрывать код расшифровки на страницу, прошу прощения если кому-то он кажется нечитаемым.
Создание файла бекапа опционально и не всегда полезно, но в данном случае я его добавил для иллюстрации момента рашифровки ниже.
Добавляем postinst в control:
echo "File: postinst 755" >> control # inline postinst file header cat postinst | sed 's/^$/./;s/^/ /;' >> control # inline postinst file body
Sed в данном случае выполняет две функции:
- s/^$/./;: замену пустых строк на точки, чтобы control файл корректно распарсился, и
- s/^/ /;: добавление в пробела в начале каждой строки по той-же причине.
Собираем пакет командой equivs-build control, получаем в рабочей директории (наконец-то, да?) наш foopkg_1.0_all.deb.
Пробуем установить (разумеется, используя ваш любимый метод повышения привелегий до root):
dpkg -i foopkg_1.0_all.deb Selecting previously unselected package foopkg. (Reading database ... 32032154537392375672 files and directories currently installed.) Unpacking foopkg (from .../foopkg/foopkg_1.0_all.deb) ... Setting up foopkg (1.0) ... ************************************************* *** WARNING! This is a protected package, *** *** please contact the maintainer, blah blah. *** ************************************************* dpkg: error processing foopkg (--install): subprocess installed post-installation script returned error exit status 1 Errors were encountered while processing: foopkg grep '' /opt/foopkg/* # package now in unconfigured state, lets see what installed /opt/foopkg/clear.txt:This is clear text, free to view, nothing special. /opt/foopkg/clear.txt: /opt/foopkg/sensitive.config:clear_param=foo /opt/foopkg/sensitive.config:secret_param=___encrypted{U2FsdGVkX19P9SiUFkMBPmoe9JKkngTi24rcwWCJ9gs=}___ /opt/foopkg/sensitive.config: /opt/foopkg/topsecret:___encrypted{U2FsdGVkX18wjp/ArVbp5v7yHazykiX3C2VDM9xavGrECXduajGmSmTipNpSRhZ5}___ /opt/foopkg/topsecret: echo -n 'ThisIsOurDeploymentMegaPassphrase' > /root/deployment-password # ok, lets enable decryption dpkg --configure -a # retry install Setting up foopkg (1.0) ... grep '' /opt/foopkg/* # lets see again -- yep, backups and decrypted files are in place. /opt/foopkg/clear.txt:This is clear text, free to view, nothing special. /opt/foopkg/clear.txt: /opt/foopkg/sensitive.config:clear_param=foo /opt/foopkg/sensitive.config:secret_param=bar /opt/foopkg/sensitive.config: /opt/foopkg/sensitive.config.encbackup:clear_param=foo /opt/foopkg/sensitive.config.encbackup:secret_param=___encrypted{U2FsdGVkX19P9SiUFkMBPmoe9JKkngTi24rcwWCJ9gs=}___ /opt/foopkg/sensitive.config.encbackup: /opt/foopkg/topsecret:this /opt/foopkg/topsecret:is /opt/foopkg/topsecret:multiline /opt/foopkg/topsecret:and /opt/foopkg/topsecret:42 /opt/foopkg/topsecret: /opt/foopkg/topsecret.encbackup:___encrypted{U2FsdGVkX18wjp/ArVbp5v7yHazykiX3C2VDM9xavGrECXduajGmSmTipNpSRhZ5}___ /opt/foopkg/topsecret.encbackup:
Теперь мы можем распространять наш пакет в торрентах и выкладывать на файлообменники, не боясь что его содержимое станет доступно там, где не предполагалось (разумеется, при достаточно криптостойком пароле).
Потенциальная польза и применение
- можно рассматривать данную методику в качестве примера практической реализации — в свое время гугл не дал нужных результатов;
- IANAC (я не криптограф), у меня нет оснований не доверять openssl. Если кто-то укажет, почему подобный метод может не работать как задумано — буду очень признателен;
- не предполагается использование на гигантских файлах, конечно;
- я лично использую этот метод для распространения конфигов и первичных ключей для настройки IPSec на машинах not-so-tech-savvy родственников/знакомых;
- если это будет кому-то важно, то портирование на rpm-based не должно вызвать проблем;
- шифрование с разными ключами для разных клиентов, подпись пакета и настройку репозитория я не включал, старался сократить пост.
Спасибо всем кто дочитал до конца, надеюсь пригодится. Все в public domain, разумеется.
ссылка на оригинал статьи http://habrahabr.ru/post/195150/
Добавить комментарий