Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Pulp air-gapped: self-signed HTTPS and offline transfer

By updated

Improve this page

Pulp logo

This guide deploys Pulp in an isolated (air-gapped) network: serve the API over HTTPS with a self-signed certificate authority that you distribute and trust properly, then transfer content with no Internet access thanks to Pulp's export/import. Intended audience: administrators of partitioned zones (industry, defense, healthcare). Prerequisites: a Pulp instance (see Installing Pulp) and openssl. The core rule of this guide: you never disable TLS verification, you trust the authority.

An air-gapped environment raises two distinct problems. First, getting content in without a connection to public repositories: this is the job of Pulp's export/import. Second, serving the instance over HTTPS without a publicly recognized authority: this is the job of a self-signed CA that you trust on every client. Together, these two building blocks make an artifact chain fully sovereign.

What you will learn

  • Generate a self-signed authority and a server certificate for Pulp.
  • Trust that authority on the clients, without ever disabling TLS.
  • Export repositories from a connected instance.
  • Import that archive into the isolated instance.

Serving Pulp over HTTPS with a self-signed CA

With no public authority, since Let's Encrypt is not reachable offline, you create your own authority. This is perfectly safe as long as the CA key stays protected and the certificate is trusted by the clients.

  1. Create the authority and the server certificate for the public name of your instance (here pulp.interne):

    Terminal window
    # Internal certificate authority
    openssl req -x509 -newkey rsa:4096 -nodes -days 3650 \
    -keyout ca.key -out ca.crt -subj "/CN=Autorité interne Pulp"
    # Server certificate signed by this authority
    openssl req -newkey rsa:2048 -nodes -keyout pulp.key -out pulp.csr \
    -subj "/CN=pulp.interne"
    printf 'subjectAltName=DNS:pulp.interne' > san.cnf
    openssl x509 -req -in pulp.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
    -out pulp.crt -days 825 -extfile san.cnf

    The subjectAltName field is mandatory: modern clients reject a certificate without a SAN, even with a correct CN.

  2. Place this certificate on the reverse proxy that exposes Pulp (see Installing Pulp for Traefik). The proxy terminates TLS with pulp.crt / pulp.key and forwards to the container.

  3. Align CONTENT_ORIGIN with the HTTPS URL, in settings/settings.py:

    CONTENT_ORIGIN = "https://pulp.interne"
    TOKEN_SERVER = "https://pulp.interne/token/"
    TOKEN_SIGNATURE_ALGORITHM = "ES256"
    PUBLIC_KEY_PATH = "/etc/pulp/certs/token_public_key.pem"
    PRIVATE_KEY_PATH = "/etc/pulp/certs/token_private_key.pem"

Trusting the authority on the clients

This is the sticking point, and the temptation of --tls-verify=false is strong. Do not give in: disabling verification means accepting any certificate, and so opening the door to interception. The right method is to trust the CA.

The cleanest way is to add the CA to the system trust store, once, on each machine. All tools then inherit it.

Terminal window
sudo cp ca.crt /usr/local/share/ca-certificates/pulp-interne.crt
sudo update-ca-certificates

Once the CA is in the system store, curl, pip, dnf and most tools validate Pulp with no extra option. For tools that manage their own store, per-client configuration remains possible:

ToolTrusting the CA without touching the system
podmanCopy ca.crt to /etc/containers/certs.d/pulp.interne/ca.crt
pippip install --cert ca.crt ... or the PIP_CERT variable
cosign / skopeoEnvironment variable SSL_CERT_FILE=ca.crt
dnfsslcacert=/path/ca.crt in the .repo file

Getting content in with no Internet

An isolated instance cannot synchronize from PyPI or Docker Hub. The mechanism Pulp provides is export/import: a connected instance (upstream) exports repository versions into a .tar archive, which you transfer physically, and an isolated instance (downstream) then imports it.

  1. Allow the export/import directories, in the settings of both instances:

    ALLOWED_EXPORT_PATHS = ["/data/exports"]
    ALLOWED_IMPORT_PATHS = ["/data/imports"]
  2. On the connected instance, create an exporter then run the export of the desired repositories:

    Terminal window
    pulp exporter pulp create --name export-mensuel \
    --path /data/exports \
    --repository file:livrables --repository python:pypi-cache
    pulp export pulp run --exporter export-mensuel

    Two different subcommands, and this is a source of errors: pulp exporter manages the exporter object, creating and listing it, while pulp export runs the operation and expects --exporter, not --name.

    Pulp produces a .tar archive and a -toc.json file containing the artifacts and their metadata. The two travel together: one without the other restores nothing. You can split the archive with chunk_size to fit on removable media.

  3. Transfer the archive and the -toc.json to the isolated instance (physical media, network diode), into its /data/imports directory.

  4. On the isolated instance, create an importer. It carries the mapping between the repository name in the export and the local repository name, and this option takes two arguments, not JSON:

    Terminal window
    pulp importer pulp create --name import-mensuel \
    --repo-map livrables livrables

    The destination repository must exist and be empty before the import: the operation fills it, it does not create it.

  5. Run the import through the API, because the CLI cannot do it. Verified on pulp-cli 0.41.1: pulp importer pulp only exposes create, destroy, list, show and update, and pulp import does not exist. The Pulp documentation only shows the operation through the API. The path of the -toc.json is the one the container sees, not the host:

    Terminal window
    IMP=$(pulp --format json importer pulp show --name import-mensuel \
    | python3 -c "import sys,json;print(json.load(sys.stdin)['pulp_href'])")
    curl -u admin:MOT_DE_PASSE -X POST \
    -H 'Content-Type: application/json' \
    -d '{"toc": "/data/imports/export-....-toc.json"}' \
    "https://pulp.interne${IMP}imports/"

    The response returns a task_group and not a task: the import starts several tasks, one per imported resource, and monitoring written for a single task never sees the end. The import recreates the repository versions with their content; your internal clients then consume these repositories over HTTPS, exactly as on a connected instance.

Hands-on practice

The lab cuts the outbound access of the repository and requires that a client is served anyway. Two checks give it its value. The first measures the cut from the repository rather than deducing it from a rule present in the configuration. The second verifies that the client itself is not cut off: isolating the whole network would make the installation succeed without proving anything about the repository.

Key takeaways

  • An air-gapped Pulp combines two problems: serving over HTTPS without a public CA, and importing content without Internet.
  • You create a self-signed CA (with subjectAltName mandatory) and trust that authority on the clients.
  • The clean method is the system trust store (update-ca-trust / update-ca-certificates); failing that, per-tool configuration.
  • Never use --tls-verify=false, --allow-insecure-registry or sslverify=0: trust the CA, do not ignore verification.
  • Content comes in through export/import, on repositories synchronized with policy=immediate, through the ALLOWED_EXPORT_PATHS / ALLOWED_IMPORT_PATHS paths.
  • Export runs with pulp export pulp run --exporter; import, on the other hand, exists only in the API and returns a task_group.

Knowledge check

Eight questions on a repository cut off from the Internet: a self-signed authority properly trusted, and content brought in by export and import.

Knowledge check

Check what you have learned with this interactive quiz

8 questions
8 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn