Debian: создаем пакеты для узкого круга систем

от автора

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

Дабы не выдирать куски кода из существующей системы сборки, я решил оформить пост в виде отдельного небольшого 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 в данном случае выполняет две функции:

  1. s/^$/./;: замену пустых строк на точки, чтобы control файл корректно распарсился, и
  2. 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *