Installing packages on air-gapped/caged Debian machines

28 Apr '16

Air-gapped systems are great for security, but a huge pain to manage. I recently set up a server that - while not strictly air-gapped - was on a subnet without internet access. The initial server was installed using a Debian network install image, because who wants to download 3 DVDs to get outdated packages? Besides, I like servers with minimal bloat.

The netinst page isn’t joking when it says that the ISO “contains just the minimal amount of software to start the installation and fetch the remaining packages over the Internet.”. Not even an SSH server is installed. So let’s get some packages on this machine.

Various sites will tell you to use dynamic port forwarding with SSH:

$ ssh -ND 1080 <user>@<remote>
$ http_proxy=socks://localhost:1080/ apt-get update

Spoiler: This doesn’t work. With -D, SSH acts as a SOCKS proxy, and while there’s speculation that apt supports SOCKS proxies, if you try it out it doesn’t. You can use tsocks, which works well… but how to install it?

N.B.: If you’re doing this often, you probably want to set up some kind of internal apt mirror, but downloading several GiB is a bit heavy-weight for once or twice. There’s probably also a way to bake packages into an install CD.

The simplest solution I can come up with is to bootstrap the air-gapped machine with packages downloaded from a VM running the same Debian version.

I wrote a script that given some package names will determine the dependencies iteratively, download all packages, and create a tarball that can be transferred to the air-gapped system securely.

#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

tarfile="packages.tar"
deps="$*"
count=0

echo "-> Gathering dependencies"
for i in `seq 1 10`
do
  for pkg in "$deps"
  do
    deps+=$'\n'
    deps+=$(apt-cache depends -i $pkg | \
            awk '/Depends:/ && !/</ {print $2}')
  done
  deps=$(echo "$deps" | sort | uniq)
  new_count=$(echo "$deps" | wc -l)
  # have we found any more dependencies since last time?
  if [ "$new_count" -le "$count" ]
  then
    break
  fi
  count="$new_count"
done

echo "-> Downloading packages"
mkdir packages
pushd packages >/dev/null
for pkg in "$deps"
do
  apt-get download $pkg
done

echo "-> Creating archive"
tar -cf "../$tarfile" *.deb
popd >/dev/null
rm -r packages/
echo "-> Done"

In my case, I used Python to serve the tarball and wget to download it (curl wasn’t installed). Then, untar and use dpkg to install. Simple.

sysadmin, infosec

Newer Older