Air-Gapped Mirrors and a Private CA
Stand up an internal Python index from a wheelhouse
Goal
On the connected side, collect Python packages and their dependencies as wheels and pin them by hash. On the air-gapped side, stand up a PEP 503 internal index, point to it with pip.conf, and confirm that installation works even with the outside blocked.
Why it matters
The most expensive mistakes in air-gapped imports are "we took it in but it was not enough, so it has to go out again" and "we took it in but it does not fit on the server". The first is prevented by resolving the dependencies completely outside and carrying them all in, and the second by recording the target server's Python version and platform when you download. Files altered on their way through the media are caught by hash-checking mode. This Pod can reach the internet (80/443) from the start, so it plays the download side, and the grader checks the air-gapped side with outbound HTTP(S) redirected to a closed proxy.
Steps
- Download
requests==2.32.3and its dependencies only as wheels for this Pod's Python (3.12) and collect them in/root/pip/wheelhouse/. - The air-gapped server runs Python 3.11 (x86_64, manylinux2014). Download the same requirement as wheels for that target and collect them in
/root/pip/wheelhouse-py311/. - Create
/root/pip/requirements.txt, listing every wheel in/root/pip/wheelhouse/as a line of the form이름==버전 --hash=sha256:<값>(the placeholders are the name, the version, and the hash value). - For each
/srv/pypi/simple/<정규화한 이름>/directory (the placeholder is the normalized name), put the matching wheel in it to build an index in the PEP 503 shape. - Add
127.0.0.1 pypi.airgap.internalto/etc/hosts, and startpython3 -m http.serverserving/srv/pypion port 8080. Keep the access log in/root/pip/index.log. - In
/etc/pip.conf, writeindex-url = http://pypi.airgap.internal:8080/simpletogether with a setting that trusts that address. The grader checks that requests can be downloaded with only this configuration while the outside is blocked. - Create the venv
/root/pip/venvand installrequirements.txtin hash-checking mode. - Assuming a place with no index, install the same requirements into the venv
/root/pip/venv-usbwith--no-index --find-links /root/pip/wheelhouse.
Notes
- Download only:
pip download -d <디렉터리> ...(the placeholder is the directory) · specify the target:--platform manylinux2014_x86_64 --python-version 3.11 --only-binary=:all: - One hash line:
pip hash <휠>(the placeholder is the wheel) · which configuration files were read:pip config debug - Name normalization: lowercase, and replace each run of
.,-, and_with a single-.charset_normalizer→charset-normalizer - Common mistake 1: leaving
trusted-hostout for anhttp://internal index. pip only leaves a warning and ignores the index. - Common mistake 2: installing directly into the system Python. Ubuntu 24.04 blocks it with PEP 668 — use a venv.
- Common mistake 3: closing the terminal where you started the server, or killing the server. Grading of steps 6–7 requires the server to be running.
Download side: fetch dependencies as wheels
Download requests==2.32.3 and its dependencies only as wheels for this Pod's Python and collect them in /root/pip/wheelhouse/.
pip has a subcommand that only collects files without installing them. If source distributions (.tar.gz) get mixed in, the air-gapped server will demand build tools, so also use the option that makes pip download binaries only.
Download for the target server's Python
Download the same requirement for a Python 3.11 (x86_64, manylinux2014) server and collect it in /root/pip/wheelhouse-py311/.
pip download can take the target's platform and Python version separately. There is one option that the documentation says must be used together with those options. After downloading, look at the tags in the file names (cp311 and so on).
Pin by version and hash
Create /root/pip/requirements.txt, listing every wheel in /root/pip/wheelhouse/ as a line of the form 이름==버전 --hash=sha256:<값> (the placeholders are the name, the version, and the hash value).
The first two fields of a wheel file name are the name and the version. There is a subcommand with which pip computes the hash itself, and it gives the same value as sha256sum. Write the requirement and --hash together on one line.
Build an internal index in the PEP 503 shape
For each /srv/pypi/simple/<정규화한 이름>/ directory (the placeholder is the normalized name), put the matching wheel in it.
The directory name must be the package name normalized according to the specification, or pip will not find it: lowercase, and each run of dots, hyphens, and underscores as a single hyphen. Do not confuse this with the underscores inside wheel file names.
Serve the index under the internal name
Add 127.0.0.1 pypi.airgap.internal to /etc/hosts and start a server that serves /srv/pypi on port 8080. Keep the access log in /root/pip/index.log.
The http.server in the Python standard library serves directory listings as file-name links. Pass the directory to serve and the port as arguments, and start it in the background so that it stays alive even if you close the terminal. This server writes its access log to standard error.
Point to the internal index with pip.conf
In /etc/pip.conf, write index-url = http://pypi.airgap.internal:8080/simple together with a setting that trusts that address.
By default pip does not trust an index that is not https (localhost is the only exception). There is a configuration key that makes it trust a host name. Use pip config debug to confirm that the file is actually being read.
Install into a venv in hash-checking mode
Create the venv /root/pip/venv and install /root/pip/requirements.txt in hash-checking mode.
You cannot install directly into the system Python on Ubuntu 24.04. Create a venv and then install the requirements file with the pip inside it. Hash-checking mode turns on when the file contains --hash, but there is also an option that states it explicitly.
Install from the wheelhouse alone, without an index
Create the venv /root/pip/venv-usb and install /root/pip/requirements.txt with --no-index --find-links /root/pip/wheelhouse.
Use together the option that makes pip not look at the index at all and the option that gives the directory in which to find files. If this installation succeeds, the wheelhouse is self-contained.