TT Lab
Get started
Learn Learning paths Courses

Air-Gapped Mirrors and a Private CA

Make every tool trust a private CA

Continue in TT Lab

Goal

Create a private root CA and a server certificate, serve HTTPS under an internal name, and put the CA into the trust stores of the operating system, Python, and Node one by one. You measure for yourself which tool looks at which store and leave the result as a table, and you also make the server send the intermediate CA chain.

Why it matters

The moment the internal mirror is HTTPS, every build tool must trust that CA. But trust stores are divided into the operating system, certifi, Node's built-in list, and the JVM, so if you put the CA in only one place, only some tools work. Switching verification off on the tools that do not work is the most common accident. This lab Pod cannot reach the outside (DNS only), because a private CA is a matter inside the air-gapped network.

Steps

  1. Create a root CA with CN Airgap Internal Root CA as /root/pki/ca.key and /root/pki/ca.crt (CA:TRUE, keyCertSign).
  2. Issue a repo.airgap.internal server certificate from that CA as /root/pki/server.key and /root/pki/server.crt (the DNS name in the SAN, usage serverAuth, not a CA). Keep the extensions you gave when signing in /root/pki/server.ext.
  3. Add 127.0.0.1 repo.airgap.internal to /etc/hosts, and start, as /root/pki/serve.py, a server that serves /root/pki/www/hello.txt (content hello-airgap) at https://repo.airgap.internal:8443/.
  4. Put the root CA in the operating system store under the name airgap-root.crt and update. curl must pass without --cacert.
  5. In /etc/profile.d/airgap-ca.sh, write the environment variables that make Python requests and httpx use the operating system bundle.
  6. In the same file, add the environment variable that makes Node trust the private root.
  7. Measure whether each tool passes without environment variables (with the operating system store alone), and write six lines in /root/pki/trust-matrix.txt, for curl, urllib, requests, httpx, node, and go, in the form 도구=os or 도구=env (the placeholder is the tool name).
  8. Issue an intermediate CA (/root/pki/int.crt, pathlen 0) from the root, and from that intermediate CA issue a mirror.airgap.internal certificate (/root/pki/mirror.crt). Add the name to /etc/hosts, and start the same server on port 9443 with /root/pki/mirror-chain.crt, the leaf followed by the intermediate.

Notes

Create a private root CA

Create a root CA with CN Airgap Internal Root CA as /root/pki/ca.key and /root/pki/ca.crt (CA:TRUE, keyCertSign).

If you give openssl req the -x509 option, it creates a self-signed certificate directly instead of a request. You can give extensions with -addext, and what a root CA needs are the basic constraints (CA) and the key usage (certificate signing).

Issue a server certificate with a SAN

Issue a repo.airgap.internal server certificate from the root CA as /root/pki/server.key and /root/pki/server.crt (SAN DNS, serverAuth, not a CA). Keep the extensions you gave when signing in /root/pki/server.ext.

After creating the key and the signing request (CSR), give subjectAltName and extendedKeyUsage through an extension file when signing with the CA key. Note that openssl x509 -req does not copy the request's extensions by default.

Serve HTTPS under the internal name

Add 127.0.0.1 repo.airgap.internal to /etc/hosts, and start, as /root/pki/serve.py, a server that serves /root/pki/www/hello.txt (hello-airgap) at https://repo.airgap.internal:8443/.

If you wrap the http.server of the Python standard library in an ssl context, it becomes an HTTPS server. Give the certificate file and the key file to load_cert_chain. Start it in the background, and check with curl, specifying the root with --cacert.

Put it in the operating system store

Put the root CA in the operating system store as airgap-root.crt and update. curl must pass without --cacert.

On Debian-family systems, place it in the designated directory with a .crt extension and run the update command, and the bundle concatenated into one file is rebuilt. The output of the update command shows how many were added.

Make Python requests and httpx trust it

In /etc/profile.d/airgap-ca.sh, write the environment variables that make requests and httpx use the operating system bundle.

requests and httpx use the certifi bundle, not the operating system store. The environment variables the two libraries follow are different from each other — see the table in the reading. The value is the bundle file the operating system created.

Make Node trust it

In /etc/profile.d/airgap-ca.sh, add the environment variable that makes Node trust the private root.

Node uses the list built into it, and there is an environment variable that adds certificates to that list. It is read only once when the process starts, so check in a new shell.

Measure the per-tool trust table

Measure whether each tool passes without environment variables, and write curl, urllib, requests, httpx, node, and go in /root/pki/trust-matrix.txt as 도구=os or 도구=env (the placeholder is the tool name).

If you empty the environment with env -i, the variables from profile.d are gone too. If a tool passes in that state, the operating system store alone is enough (os); if it fails, it needs an environment variable (env). For Go, run a small program with go run.

Intermediate CA and sending the chain

Create an intermediate CA /root/pki/int.crt (pathlen 0) and a mirror.airgap.internal certificate /root/pki/mirror.crt issued by that CA, and start a server on port 9443 with /root/pki/mirror-chain.crt, the leaf followed by the intermediate.

An intermediate CA is also a CA, so it needs basic constraints, and pathlen prevents any CA below it. In the server's certificate file, the intermediate CA certificate is appended after the leaf certificate. The client knows only the root.