Manual Air Gap Software Delivery

Rob Mengert
Defense Unicorns
Published in
9 min readApr 11, 2023

Deploying software to an air-gapped environment is the most challenging way to install software. What usually doesn’t help is that most software assumes internet connectivity to install it and for its day two operations. This post will walk through the manual air gap installation of Only Office onto a Fedora VM. This will be a longer-than-average blog post and includes all of the pain and skinned knees that went into getting this software installed, and that’s the point. Manual Air gap software delivery is difficult. Proceed with caution!

Jennifer Lawrence as Katniss Everdeen: May the odds be ever in your favor

Feel free to skip the painful details and scroll down to the Tell Me There’s a Better Way section.

The Setup

An explanation of the topology is first needed to understand how the “air gap” is being set up.

Local Topology

The layout is super simple, but there’s an essential item to highlight. The air gap VM attaching to a local bridge interface in the laptop means it uses the laptop’s network interface to reach the outside world under normal operating conditions. Cool, no surprise there. This VM will have its network connectivity disabled but in a way that doesn’t shut down the VM’s virtual NIC. Instead, its network will be downed by injecting a default route to a dummy interface, making anything internet-hosted unreachable. However, the VM still has a connected route to the bridge interface on the host, which means that network connectivity to the host is still working. This posture means that data can be transferred from the host/laptop to the VM via SCP, even with the default route on the VM black holing all traffic that isn’t destined for the local bridge subnet. This type of transfer is analogous to carrying data across the air gap and will be used throughout this post.

The Installation

The software that will be installed on the VM again is Only Office. The actual software is less important than the process, and Only Office wasn’t selected to pick on them or highlight any shortcomings on their part. Most software is hard to install in an air gap, and something had to be chosen.

The first step is to offline the network in the VM, using the below script to do so:

Script to down network

The IP address of the VM needs to be obtained to be able to SCP data to it. On Fedora, the main interface is enp0s5. To display its IP information, run ip addr show enp0s5:

IP address of the VM

The relevant output above appears after inet on line 4, and the IP address for the VM is, which can be used to transfer data from the host to the VM.

Based on the Only Office documentation, the docs community edition can be installed as a Docker container or as an RPM package. To get the install script onto the VM, pull down the installation script on the host and SCP it over the “air gap” to the VM. The IP address of the VM needs to be obtained to be able to SCP data to it.

Download docs-install.sh from the Only Office site and SCP to the VM

Note on the laptop where this was tested; the VM was assigned an IP address of 10.211.55.5 for its interface on the bridge.

From the VM now, let’s make a directory to mess around with Only Office and move the installation script there. The script isn’t executable after downloading it, so let’s update that as well. The install script requires root privileges, so let’s get a root shell. Finally, the recommended installation is via Docker, which we will select.

VM docs-install.sh modification and installation attempt #1 (foreshadowing…)

As expected, the installation fails because Docker isn’t installed. The following section will work through getting Docker installed.

Docker Installation

We’ll need to work through the manual installation method to install Docker on Fedora. Digging into the Fedora repositories, they have both ARM RPMs and the AMD64 RPMs. From the host machine, download the latest version of all the packages from the appropriate link corresponding to your workstation CPU architecture. The laptop used to run this installation is an M1 Mac, so the aarch64 RPMs will be downloaded.

Download Docker RPMs for Fedora

Create a dedicated folder on the VM for the Docker RPMs:

Create a Folder for Docker RPMs

Then transfer all of the RPMs over the air gap via SCP to the VM:

Transfer Docker RPMs to VM

Now install the RPMs on the VM:

Docker RPMs Install Attempt

Ugh… Lines 14–15 show a dependency, libcgroup, that needs to be installed before installing the Docker packages. Found the appropriate package after some Googling. Download it on the host and then SCP it to the VM.

Download libcgroup to host and SCP it to the VM

Now install it on the VM:

Install libcgroup

Now the Docker RPMs should install cleanly:

Installation of Docker RPMs

Sweet! Now let’s start Docker and make sure it’s running:

Start and enable Docker

For those following along at home, this next step is optional but helpful; let’s add the current user to the docker group on the VM.

Add the current user to the ‘docker’ group

Back to Only Office

Now the Only Office container needs to be ported over to the VM. Starting with the docs-install.sh script from the Only Office site, this chunk looks for another script to download:

Source script here

Line 5 above shows the other script. Let’s download it and take a look.

Download install.sh and look for the container image being used

The install.sh script is a 1000+ line bash script that performs some checks on the environment to ensure enough resources to run the application. The information we want from this script is in line 8 above, the container image. This refers to the name and location of the image installed by this script, which can be found on Docker Hub here. Download this image to the host (note if you do not have Docker installed on your laptop/host machine, follow the installation instruction here first and then return to the course guide).

Pull Only Office image to host

Now that the image has been pulled, the docker save command can save the container as a tarball.

Save Only Office image and transfer it to the VM

Now the docker load command can be used to make Docker on the VM aware of the image:

Load Only Office Docker image on VM

The container is finally ready to run. The docs-install.sh script supports looking for other installation scripts locally via a flag. Let’s try using that and see what happens:

Only Office install attempt using docs-install.sh and ls flag

Ultimately, no container was started. The script logic cannot detect that the container image already exists locally and is trying to pull it. Let’s take a look at the help available (note unclear why, but this command took minutes to run during testing, the joys of the air gap!):

Help output from docs-install.sh

The -di flag will accept a path to a tar file to specify a container image. That should be what is needed here.

Only Office installation attempt using docs-install.sh and ls and di flags

So it’s expecting an image tag even though a path to the image was provided. Let’s pass an image tag, then. Referencing the help output above, the -dv flag to the script is needed.

Only Office installation attempt using docs-install.sh and ls, di, and dv flags

The CTRL-C was the air gap equivalent of rage quitting, but it already confirmed it couldn’t find the latest image. Let’s confirm we’re not crazy and see what images the VM has:

Output for docker image ls from the VM

Ok, not crazy. Let’s try to run it manually, then. Digging back into the installation script, there are some parameters that need to be passed to the container. Here’s the relevant block:

Relevant variables in install.sh

Starting with the last command, it looks like a network gets referenced to run the container. Then a bunch of arguments get passed to docker run in the form of “${args[@]}”, which are a bunch of volume mounts as defined above the docker run command. We need to dig through this script and find all of the variable definitions that are in the arguments to reconstruct the command that can be passed to the VM:

Variable values

Let’s export these in the VM so that they’re available.

Export variabels in VM

The arguments that get passed into the container mount several volumes. Let’s create those on the VM:

Commands to make directories on the VM to be mounted into the container

Cool. Next, let’s create the network that gets referenced in the docker run command:

Command to create the docker network

Let’s try starting the container:

Command to start the Only Office container

Success! Now, how to test it? We first need to grab the IP address of the container. Let’s try docker ps:

Output to the docker ps command

There is no IP address info in there, but the output shows that the container is listening on 80 and 443. Note the container's name as well (nifty_bassi in this case). We’ll need that information shortly. Since each Docker network runs its own IPAM, the network itself should contain this information:

Commands to get Only Office container IP address

Note the onlyoffice network ID is at the beginning of line 6 and used in the second command. Using jq to parse through the output, we see that the IP of the nifty_bassi container is 172.19.0.2. Let’s try connecting to that endpoint from a browser on the VM:

Only Office WebUI

Finally! Even though the screenshot above shows the UI, it took a while to display. The likely reason why is also the main point of this section: the vast majority of software assumes internet access. And to be fair to Only Office, this is usually a reasonable assumption. This is part of the reason that air gap software delivery is so difficult.

Tell Me There’s a Better Way…

As you were warned, it was painful to get to this point. The software was installed, but it took some hoops to jump through. Now imagine that instead of transferring data via SCP between a host and a VM, you must enter a Sensitive Compartmented Information Facility (SCIF, pronounced “skiff”) or drive for miles to transport the bits across the air gap. This same installation could have taken hours, if not days or weeks. And again, this post isn’t meant to pick on Only Office. Most software is not designed to be installed in an air-gapped environment.

This is why Defense Unicorns developed Zarf, an open-source project to make deploying software to air-gapped environments easy. To get this same software installed on the same system using Zarf, the following needs to be done:

  • Install Zarf on a system outside of the air gap (laptop/host system in this case)
  • Create a Zarf package for Only Office
  • Transport the Zarf binary, Zarf init package, and Zarf Only Office package across the air gap
  • Run two commands on the air gap system to install software
  • Profit

Install Zarf

The process to install Zarf on MacOS is delightfully simple, as per the docs:

$ brew tap defenseunicorns/tap && brew install zarf

Refer to the docs for similarly easy installation methods on other platforms.

Only Office Zarf Package

An existing package to install games was modified a bit for Only Office. Here’s the package structure on the filesystem:

Zarf package structure

The zarf.yaml file defines the package:

Only Office zarf.yaml

Notice that there are references to two manifests in this file on lines 13 and 14, which correspond to the other two files on the filesystem. Here is the content of those files:

deployment.yaml

And finally, service.yaml:

service.yaml

To create the Zarf package, run zarf package create in the directory where the zarf.yaml is located:

zarf package create

The output of this command is to create a .zst package on disk on line four below:

Zarf Package Created

This is one of the files that must be transported across the air gap.

Transport Bits Across the Air Gap

As listed in a previous section, these are the files that need to be transported across the air gap:

  • The Zarf binary
  • The Only Office Zarf package that was just created
  • The Zarf init package

The init package can be downloaded directly from Github. This walkthrough was done with Zarf version v0.25.1, so the init package was downloaded from this page. Transfer all the bits across from the host to the VM!

Transport the bits across the air gap

Installation on the Air Gap System

All subsequent steps happen on the air gap system. The first of which is to run a zarf init. This command produces a lot of output and requires some user interaction (which could be alleviated by providing additional command line flags). It must be run as root to install the k3s component, and it’s done executing in a minute or so. The full output can be viewed in this gist. It’s been removed for readability in the post. Enjoy a gif of Zarf floating before moving on to the package installation.

Next, deploy the Only Office Zarf package:

Output for the zarf package deploy command

As line 55 suggests, the zarf connect only-office will open a tunnel to the application:

Output for the zarf connect only-office command

Now, connect via a browser to the URL in line 6 above:

Only Office UI for the Zarf package

Profit.

Conclusion

Zarf takes a declarative approach to solve the software delivery problem across the air gap. It took much longer to write the section of this post to install Only Office via Zarf than it did to install Only Office via Zarf. The Only Office Zarf package could easily be tested on a system to ensure it’s working correctly before transporting it across the air gap. This way, the transfer only has to happen once.

Feel free to reach out to us to see a demo at hello@defenseunicorns.com.

--

--

Rob Mengert
Defense Unicorns

I'm just a dude in the world. I enjoy cloud computing, good beer and spending quality time with my family.