📜 ⬆️ ⬇️

Debian: create packages for a narrow range of systems

In this post, I will talk about a small crutch method that I used to create deb-packages that could be installed only on a specific list of servers. The solution allowed storing these packages in the central repository along with all other assemblies, without fear of leakage of the data contained in them.

In order not to rip out pieces of code from the existing build system, I decided to design the post as a separate small HOWTO, within which the build from scratch of an abstract package containing encrypted data will be considered. A typical use-case, in addition to the corporate distribution of configs, for example, is the ability to quickly and securely set your favorite aliases / configs to a new system from a publicly available package at deb: //example.com/myrepo.

In other words, a post on how openssl can be used in postinst.

Formulation of the problem

Imagine a hypothetical package foopkg, which installs three files into the system:

Suppose it is distributed as foopkg.deb, which is available in the internal repository, and can be installed on any server through apt-get install foopkg . Suppose we are lazy (that is), and we want, on the one hand, to keep everything in the repository and do only aptitude update && aptitude safe-upgrade , and then do nothing to rule. On the other hand, suppose we are a little paranoid (that is), and we don’t want our secret settings / configs to be set apart from us. Of course, we also know about puppet, but why do we need to open a separate channel to systems, when everything is automatically (but this is a topic for a separate cycle of posts) going in the form we need?
')
Decision


Leaving the package signing issues outside of this HOWTO, we’ll use equivs to build packages.

Prepare the build environment:
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 


The easiest option is to encrypt the entire file:
 openssl aes-256-cbc -e -kfile ./pass -a -A -in $infile -out $outfile 

A little subtlety in the -A flag: with it, the encrypted output does not break into blocks of 64 characters each, but goes in one line. This makes the decryption process in postinst, having only #! / Bin / dash, somewhat easier.

But the whole file is sometimes not interesting to encrypt. Ok, so we encrypt the substrings. To do this, we first define the markers by which the encryption boundaries will be determined. For simplicity, I used markers of the form '___ encrypt {' and '} ___', but YMMV, any marker that uniquely breaks a string contained in a file into substrings will do.

Accordingly, test files, already ready for encryption, look like this:

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}___ 


And the encryption procedure itself, for example, like this:
 #!/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() ) 

I did it on python, because it is clearer and faster (working with strings in sh is harder than adding or extracting a fixed substring - only for strong-willed and stubborn men). The algorithm is the simplest: we read the file into a string, look for (not greedily, so as not to capture too much) all substrings by start and end markers, encrypt (calling openssl for simplicity), and paste it back, replacing the start marker with a marker that the decoder understands.
The last two lines are added so that you can go through the list of files with a simple script that will prepare the encrypted files and add them to the list for inclusion in the package:
 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 

Spaces at the beginning of lines are important not only in python, but also in the control files of deb-packages, yes, yes.

And finally, the final part, decryption on the client side, which will install this package. To do this, create a postinst file containing:
 #!/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 


Here, too, everything is simple. At the stage of package creation, we already know which files are encrypted and which ones are not - we add them to the script, making life easier for him. The decryption algorithm is similar: looking for all the lines by the marker, for each (remember the -A key? Here, everything would be harder without it) pull out the cipher and feed openssl, then change the line breaks to their presentation ( \ n ) so that sed does not swear , we do the replacement, and restore (with the addition to the end of the file including) line breaks. Unfortunately, we can no longer use python here - the design was also designed for minimal installations where there is no python or not yet (netinstall, for example). I did not disclose the decryption code to the page, I apologize if it seems unreadable to someone.
Creating a backup file is optional and not always useful, but in this case I added it to illustrate the decryption moment below.

Add postinst to control:
 echo "File: postinst 755" >> control # inline postinst file header cat postinst | sed 's/^$/./;s/^/ /;' >> control # inline postinst file body 


Sed in this case performs two functions:
  1. s /^$/./; : replacing empty lines with dots, so that the control file can parse correctly, and
  2. s / ^ / /; : add to space at the beginning of each line for the same reason.


We build the package with the equivs-build control command , we get our foopkg_1.0_all.deb in the working directory (finally, yes?).

We are trying to install (of course, using your favorite method of increasing privileges to 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: 


Now we can distribute our package in torrents and upload it to file sharing sites without fear that its contents will be available where it was not intended (of course, with a sufficiently strong password).

Potential benefits and applications



Thanks to everyone who read to the end, I hope it will come in handy. Everything in the public domain, of course.

Source: https://habr.com/ru/post/195150/


All Articles