Air-Gapped Mirrors and a Private CA
Make every tool trust a private CA
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
- Create a root CA with CN
Airgap Internal Root CAas/root/pki/ca.keyand/root/pki/ca.crt(CA:TRUE, keyCertSign). - Issue a
repo.airgap.internalserver certificate from that CA as/root/pki/server.keyand/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. - Add
127.0.0.1 repo.airgap.internalto/etc/hosts, and start, as/root/pki/serve.py, a server that serves/root/pki/www/hello.txt(contenthello-airgap) athttps://repo.airgap.internal:8443/. - Put the root CA in the operating system store under the name
airgap-root.crtand update.curlmust pass without--cacert. - In
/etc/profile.d/airgap-ca.sh, write the environment variables that make Python requests and httpx use the operating system bundle. - In the same file, add the environment variable that makes Node trust the private root.
- Measure whether each tool passes without environment variables (with the operating system store alone), and write six lines in
/root/pki/trust-matrix.txt, forcurl,urllib,requests,httpx,node, andgo, in the form도구=osor도구=env(the placeholder is the tool name). - Issue an intermediate CA (
/root/pki/int.crt, pathlen 0) from the root, and from that intermediate CA issue amirror.airgap.internalcertificate (/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
- Looking inside a certificate:
openssl x509 -in <파일> -noout -text(the placeholder is the file) · checking the name:openssl x509 -in <파일> -noout -checkhost <이름>(the placeholders are the file and the name) - The chain the server sends:
openssl s_client -connect 127.0.0.1:8443 -servername <이름> -showcerts </dev/null(the placeholder is the name) - Checking that profile.d applies in a login shell:
env -i bash -lc 'echo $REQUESTS_CA_BUNDLE' - Common mistake 1: putting the name only in CN. Current tools look only at the SAN.
- Common mistake 2: putting it in with a
.pemextension. update-ca-certificates reads only.crt. - Common mistake 3: switching verification off on a tool that does not work (
verify=False,NODE_TLS_REJECT_UNAUTHORIZED=0). The grader in this lab measures only with verification on.
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.