Skip to content

jfrog/package-reroute

Repository files navigation

Certificate installation scripts

Scripts to install a CA certificate, configure Node/npm and Python (pip, uv, Hugging Face Hub, and related TLS clients), and clear Docker Hub credentials that can break redirected Docker Hub pulls.

This document describes the certificate installation and validation scripts for macOS, Linux (Debian/Ubuntu), and Windows.

Script Platform Purpose
install_certs_macos.sh macOS Install cert, set env vars (Node/Python), and clear Docker Hub credentials
validate_install_macos.sh macOS Validate PEM and env config
install_certs_debian_ubuntu.sh Debian/Ubuntu Install cert into system trust + profile.d + user shell rc + Docker cleanup
validate_certs_debian_ubuntu.sh Debian/Ubuntu Validate PEM and env config
install_certs_windows.ps1 Windows Install cert, set env vars (Node/Python), and clear Docker Hub credentials
validate_install_windows.ps1 Windows Validate PEM and env config

Environment variables by platform (see each section for details):

Variable Typical use Notes
NODE_USE_SYSTEM_CA=1 Node/npm macOS, Debian, Windows when npm is configured
NODE_EXTRA_CA_CERTS=<path> Node/npm PEM path (bundle allowed)
UV_NATIVE_TLS=true / 1 Python uv macOS uses true; Windows uses 1; not set by the Debian/Ubuntu script
UV_SYSTEM_CERTS=true Python uv Set by macOS for python, huggingface, or all
REQUESTS_CA_BUNDLE=<path> Python requests / many HTTPS stacks PEM or bundle path
SSL_CERT_FILE=<path> OpenSSL-backed tools Set on Debian/Ubuntu for python, huggingface, or all to the system CA bundle
HF_HUB_DISABLE_XET=1 Python huggingface_hub Set when huggingface or all: disables XET (not supported with typical MITM / Artifactory redirect flows)
HF_HUB_ETAG_TIMEOUT=86400 Python huggingface_hub Set when huggingface or all: ETag check timeout (seconds); reduces spurious failures on slow paths
HF_HUB_DOWNLOAD_TIMEOUT=86400 Python huggingface_hub Set when huggingface or all: download timeout (seconds)

Docker Hub credential cleanup

Each install script includes a best-effort cleanup for Docker Hub credentials. This fixes environments where corporate egress redirects registry-1.docker.io to JFrog Artifactory: locally stored Docker Hub credentials can otherwise be sent to JFrog's token endpoint and produce Bad Credentials.

The cleanup attempts docker logout for these Docker Hub key forms:

https://index.docker.io/v1/
index.docker.io
docker.io
https://registry-1.docker.io/
registry-1.docker.io

If Docker CLI is unavailable, the scripts skip this step. The cleanup is idempotent, logs warnings on logout failures, and never aborts the certificate install.

The cleanup runs in the user context where possible because Docker credentials and credential stores are user-scoped:

  • macOS: uses SUDO_USER when present; under JAMF/root execution, falls back to the console user from /dev/console and runs through launchctl asuser.
  • Debian/Ubuntu: uses SUDO_USER, then logname, then a best-effort active loginctl session fallback.
  • Windows: runs for the current PowerShell user. If the script runs as SYSTEM, Docker cleanup is skipped with a warning because the user's Docker credential store is not accessible from that context.

Known limitation: Docker Desktop GUI sign-in can recreate CLI credentials later. If a user signed in through Docker Desktop, sign out in the Docker Desktop UI as well.


macOS: install_certs_macos.sh

Overview

install_certs_macos.sh configures Node/npm and/or Python on macOS to use a custom CA certificate (e.g. for corporate proxy or package routing). It:

  • Runs only as root (e.g. sudo).
  • Either exports a full PEM bundle from macOS system Keychains, or uses an existing PEM file you provide.
  • For each user in /Users/*, writes or updates the certificate file and sets environment variables in that user’s ~/.zshrc so Node and Python use the certificate.
  • Clears Docker Hub credentials for the target non-root user, when Docker is installed.

With Keychain export, each user gets package-route.pem under ~/<extract-path>/. The bundle includes Apple's system roots and enterprise CAs from /Library/Keychains/System.keychain. With --use-cert, the install uses your PEM path as-is for every user.

Requirements

  • macOS (script uses security for Keychain export when --extract-path is used).
  • Root (script exits with an error and suggests sudo if not root).
  • openssl on PATH. Optional: use --install-dependencies to install it via Homebrew in the same run if missing.
  • When using --extract-path: the security (Keychain) tool must be available (system tool; /usr/bin is prepended to PATH by the script).

How to use

Basic usage

sudo ./install_certs_macos.sh [OPTIONS]

Options

Option Required Description
--package <npm|python|huggingface|all> No (default: all) npm (Node only), python (Python TLS: uv, requests—no Hugging Face Hub vars), huggingface (Python TLS + HF_HUB_*), all (npm + python + Hugging Face Hub).
--extract-path <path> Yes* Directory under each user’s home where package-route.pem is written: ~/<path with leading / stripped>/package-route.pem (e.g. opt/certs~/opt/certs/..., certs~/certs/...).
--use-cert <path> Yes* Use this existing PEM file instead of exporting from Keychain. Cannot be used with --extract-path.
--install-dependencies No If openssl is missing, install it via Homebrew and continue in the same run.
-h, --help Print usage and exit.

* You must use either --extract-path or --use-cert, not both and not neither.

Examples

1. Export Keychain bundle and configure npm + Python for all users

PEM is written under each user’s home (e.g. ~/opt/certs/package-route.pem for --extract-path /opt/certs) and each user’s .zshrc is updated:

sudo ./install_certs_macos.sh \
  --package all \
  --extract-path /opt/certs

2. Use an existing PEM file (e.g. from IT)

No Keychain access; same PEM path is set for every user:

sudo ./install_certs_macos.sh \
  --package all \
  --use-cert /opt/certs/company-ca.pem

3. Only configure Python TLS (UV_NATIVE_TLS, REQUESTS_CA_BUNDLE; no HF_HUB_*)

sudo ./install_certs_macos.sh \
  --package python \
  --extract-path certs

With a relative --extract-path, each user gets their own file, e.g. /Users/jane/certs/package-route.pem.

4. Python TLS + Hugging Face Hub (huggingface; same TLS vars plus HF_HUB_*)

sudo ./install_certs_macos.sh \
  --package huggingface \
  --extract-path certs

5. Install openssl if missing, then run (single run)

sudo ./install_certs_macos.sh \
  --install-dependencies \
  --package all \
  --extract-path /opt/certs

Install validation

validate_install_macos.sh checks that the certificate installation succeeded: PEM file(s) exist and are valid (via openssl x509). --expected-subject is required for every invocation. It does not require root unless you use --all-users.

Option Description
--expected-subject <pattern> Required. At least one cert in each PEM file (bundle) must have a subject matching <pattern> (case-insensitive).
(default scope) Read NODE_EXTRA_CA_CERTS and REQUESTS_CA_BUNDLE from the current user’s ~/.zshrc (with ~ expanded), then validate each referenced PEM file. UV_NATIVE_TLS is not validated.
--all-users (Root only.) For each user in /Users/*, read their ~/.zshrc, resolve cert paths, and validate each PEM. Use: sudo ./validate_install_macos.sh --expected-subject <pattern> --all-users.

Requirements: openssl on PATH (same paths as the install script are prepended).

Exit code: 0 if all checks pass, 1 if any check fails.

# After install: validate current user’s config and cert path(s)
./validate_install_macos.sh --expected-subject Zscaler

# Validate every user’s config (run as root)
sudo ./validate_install_macos.sh --expected-subject Zscaler --all-users

Testing

Tests live in testing/. Automated tests cover macOS and Windows only (not Debian/Ubuntu).

test_install_certs_macos.sh runs automated tests for install_certs_macos.sh (CLI and argument validation) and validate_install_macos.sh (validation with a temp PEM and mock home). No root required for the default test run.

Requirements: openssl on PATH (for generating a temporary cert in tests).

# From repo root
./testing/test_install_certs_macos.sh

# Or from repo root with testing as current dir
cd testing && ./test_install_certs_macos.sh

Exit code 0 if all tests pass, 1 otherwise.

Test coverage

Area Covered Not covered
install_certs_macos.sh CLI and pre-root: --help; unknown option; invalid --package; no cert source; --use-cert + --extract-path conflict; --use-cert with missing file; non-root exit and message. --use-cert: valid PEM path and --package npm/python/huggingface (non-root → run as root); invalid PEM content rejected with "Invalid or missing PEM" when run as root (tested when passwordless sudo available). Post-root: PATH/openssl, --install-dependencies (Homebrew); Keychain export; per-user loop; Docker credential cleanup; writing PEM and updating .zshrc. Requires root and/or Keychain; not run in CI.
validate_install_macos.sh CLI: unknown option (exit 1); missing --expected-subject (exit 1). Main paths: default with mock HOME and .zshrc; missing PEM in .zshrc (exit 1); --all-users without root (exit 1). Covers validate_pem, get_export_path, validate_user_config. Multi-cert bundle in validate_pem; --all-users as root.

Tests are black-box (exit codes and stderr).

Windows tests

test_install_certs_windows.ps1 runs automated tests for install_certs_windows.ps1 (CLI and parameter validation; -UseCert -Package python sets Python TLS only and leaves existing HF_HUB_* unchanged; -Package huggingface adds HF_HUB_*; -Package all sets npm + TLS + HF; when run as admin) and validate_install_windows.ps1 (-ExpectedSubject required, env-based validation: valid PEM, missing file, invalid PEM, subject match and no-match, and system-level env when run as admin). Run the test script as Administrator so install script tests and system-level validate tests execute; the script uses a temp directory and an embedded PEM.

Requirements: Windows with PowerShell. The install and validate scripts must be in the parent of testing/ (repo root).

# From repo root (PowerShell on Windows, as Administrator)
powershell -NoProfile -ExecutionPolicy Bypass -File testing/test_install_certs_windows.ps1

From a non-Windows host you can run the tests on a Windows VM via SSH (e.g. copy the scripts and invoke the same command over ssh jump-windows).

Exit code 0 if all tests pass, 1 otherwise. Output shows pass/fail per test and a final count.

Area Covered
install_certs_windows.ps1 When run as admin: script passes admin check (no "must run as Administrator" error). No cert source (parameter set error); invalid -Package; -CertName without -ExtractPath (and reverse); -UseCert and -CertName together; -UseCert with nonexistent file; -UseCert with invalid PEM; -UseCert with valid PEM (no "not a file" or "Invalid PEM" error). Packages: -UseCert -Package python sets TLS-only Machine vars and does not remove pre-existing HF_HUB_*; -Package huggingface adds HF_HUB_*; -Package all sets npm + TLS + HF.
validate_install_windows.ps1 -ExpectedSubject required (exit 1 if missing); current user env (no paths → exit 0); env path to valid PEM (exit 0), missing file (exit 1), invalid PEM (exit 1); subject mismatch (exit 1, FAIL message); system-level (Machine) env when run as admin.

Tests are black-box (exit codes and stdout/stderr). Paths are passed to the validate script via a temp file when invoking as a child process to avoid command-line parsing issues with backslashes.


Logic in detail

1. Argument handling

  • --package defaults to all if omitted; must be npm, python, huggingface, or all (all = npm + Python TLS + Hugging Face Hub).
  • Cert source is one of:
    • Keychain export: --extract-path set; --use-cert must not be set.
    • Use file: --use-cert set; --extract-path must not be set.
  • Script exits with an error if:
    • Both --extract-path and --use-cert are used, or
    • Neither cert source is provided.

2. Root and dependencies

  • Script must run as root; otherwise it prints an error and suggests sudo $0 [options].
  • Prepends /usr/bin and common Homebrew paths to PATH so openssl and security are found.
  • If --install-dependencies is set and openssl is not on PATH:
    • Tries Homebrew (/opt/homebrew/bin/brew or /usr/local/bin/brew).
    • Runs brew install openssl, then adds the new openssl to PATH and continues in the same run.
  • If openssl is still missing after that (or without the flag), script exits with an error.
  • If cert source is Keychain export (--extract-path), script checks that security is available; if not, it exits (system tool, cannot be installed).

3. Certificate source

  • --use-cert: Validates the file with openssl x509 -noout and uses it as the certificate for all users. No Keychain access.
  • --extract-path:
    • Exports all trusted root CAs from SystemRootCertificates.keychain and System.keychain.
    • Writes the full bundle to each user's package-route.pem.

4. Per-user loop

For each directory in /Users/* (skipping Shared and non-directories):

  • Cert file path:
    • If --use-cert: use that path for every user.
    • If --extract-path: use <homedir>/<extract-path with leading / stripped>/package-route.pem (e.g. /opt/certs~/opt/certs/package-route.pem, certs~/certs/package-route.pem). Script creates the directory, writes the PEM, and chowns to that user.
  • For each user, the script creates ~/.zshrc if needed, then calls add_exports_to_file with that file and the user’s cert path. If ~/.zshrc is a directory, it skips that user with a warning.

5. Docker Hub credential cleanup

After cert and shell config updates, the script runs Docker Hub credential cleanup as the target non-root user (SUDO_USER, or the console user under JAMF). It uses docker logout when Docker is available; otherwise it skips the cleanup.

6. add_exports_to_file (per user, per shell file)

For npm (if --package is npm or all):

  • Ensure NODE_USE_SYSTEM_CA=1.
  • Add or replace NODE_EXTRA_CA_CERTS so it points at the selected cert path.

For Python TLS (if --package is python, huggingface, or all):

  • Ensure UV_NATIVE_TLS=true and UV_SYSTEM_CERTS=true.
  • Add or replace REQUESTS_CA_BUNDLE so it points at the selected cert path.

For Hugging Face Hub (if --package is huggingface or all):

  • Ensure HF_HUB_DISABLE_XET=1, HF_HUB_ETAG_TIMEOUT=86400, HF_HUB_DOWNLOAD_TIMEOUT=86400 (same add/replace/leave-as-is behavior via ensure_export). With python only, those lines are not added or updated; any existing exports in .zshrc are left unchanged (so user or prior-run values are not stripped).

Flowchart

install_certs_macos.sh flowchart


Summary (macOS)

  • One run as root (optionally with --install-dependencies to install openssl).
  • One cert source: either Keychain export (--extract-path) or existing file (--use-cert).
  • Per user: PEM at ~/<extract-path>/package-route.pem for each user (leading / on --extract-path is stripped); env vars in ~/.zshrc point to that path. With --use-cert, the same PEM path is used for every user.
  • If user already had a different env path: script replaces it with the selected cert path.
  • Docker: clears Docker Hub credentials for the target user if Docker is installed.

Users must open a new terminal (or source ~/.zshrc) for the new environment variables to take effect.


Linux (Debian/Ubuntu): install_certs_debian_ubuntu.sh

Overview

install_certs_debian_ubuntu.sh installs a PEM/CRT into the Debian/Ubuntu system trust store (update-ca-certificates), writes a managed file under /etc/profile.d/package-route-certs.sh, and updates the target non-root user’s shell rc (~/.zshrc or ~/.bashrc, depending on their login shell). It only supports an existing certificate file (--use-cert); there is no Keychain or cert-store extraction on Linux in this repo.

  • npm: NODE_USE_SYSTEM_CA=1 and NODE_EXTRA_CA_CERTS pointing at the installed cert under /usr/local/share/ca-certificates/ (default basename package-route-custom-ca.crt, overridable with --cert-name).
  • Python TLS: REQUESTS_CA_BUNDLE and SSL_CERT_FILE point at the system CA bundle (/etc/ssl/certs/ca-certificates.crt), which includes your CA after update-ca-certificates. UV_NATIVE_TLS is not set (unlike macOS/Windows Python flows). HF_HUB_* are set only for huggingface or all; with python only, existing HF_HUB_* lines in the user’s ~/.bashrc / ~/.zshrc are not removed.
  • Docker: best-effort Docker Hub credential cleanup runs for the target non-root user.

Requirements

  • Debian or Ubuntu (script checks /etc/os-release).
  • Root (sudo).
  • openssl and update-ca-certificates on PATH.
  • Optional: Docker CLI for Docker Hub credential-store cleanup. If Docker CLI is missing, the cleanup step is skipped.

Options

Option Required Description
--use-cert <path> Yes Path to an existing PEM/CRT file.
--package npm|python|huggingface|all No (default: all) What to configure.
--cert-name <name> No (default: package-route-custom-ca) Base name for the file installed under /usr/local/share/ca-certificates/<name>.crt (not a Keychain/subject pattern).
-h, --help Usage.

Examples

sudo ./install_certs_debian_ubuntu.sh --use-cert /tmp/company-ca.pem
sudo ./install_certs_debian_ubuntu.sh --use-cert /tmp/company-ca.pem --package npm
sudo ./install_certs_debian_ubuntu.sh --use-cert /tmp/company-ca.pem --package huggingface
sudo ./install_certs_debian_ubuntu.sh --use-cert /tmp/company-ca.pem --cert-name my-org-ca

Validation: validate_certs_debian_ubuntu.sh

--expected-subject is required. Checks PEM paths from the current user’s ~/.bashrc / ~/.zshrc and, when present, /etc/profile.d/package-route-certs.sh (NODE_EXTRA_CA_CERTS, REQUESTS_CA_BUNDLE, SSL_CERT_FILE). With --all-users (root only), validates /home/* users’ rc files.

./validate_certs_debian_ubuntu.sh --expected-subject "O=Example"
sudo ./validate_certs_debian_ubuntu.sh --all-users --expected-subject "O=Example"

Windows: install_certs_windows.ps1

Overview

install_certs_windows.ps1 configures Node/npm and/or Python on Windows to use a custom CA certificate. It must be run as Administrator (or SYSTEM); the script exits with an error otherwise.

  • Either extracts a certificate from the Windows cert store (LocalMachine\Root) by subject substring (-CertName), or uses an existing PEM file you provide (-UseCert). If multiple certs match the pattern, the script logs a warning and picks one (prefers a subject containing Root, otherwise the first match).
  • With -CertName and -ExtractPath: writes package-route.pem per user under each user’s profile and sets User-level env vars in the registry for each user. npm: NODE_USE_SYSTEM_CA, NODE_EXTRA_CA_CERTS. Python TLS (python, huggingface, or all): UV_NATIVE_TLS, REQUESTS_CA_BUNDLE. Hugging Face Hub (huggingface or all): HF_HUB_DISABLE_XET, HF_HUB_ETAG_TIMEOUT, HF_HUB_DOWNLOAD_TIMEOUT.
  • With -UseCert: does not write a PEM file; sets Machine-level env vars. The script deletes overlapping User-level vars so they do not override Machine (User wins over Machine on Windows). Which vars are set or cleared depends on -Package (see env table above).
  • Clears Docker Hub credentials for the current PowerShell user. When running as SYSTEM, this cleanup is skipped with a warning because it must run in the user's Windows session.

Re-runs merge certs: if the target file already exists, the script saves its content, overwrites with the new cert, then appends other certs from the saved copy (dedupe by SHA-256 fingerprint). So running with a second cert adds it to the bundle instead of replacing it.

Requirements

  • Windows with PowerShell.
  • Run as Administrator (or SYSTEM). The script checks and exits with an error if not elevated.
  • When using -CertName: at least one certificate in LocalMachine\Root must match; if several match, the script warns and selects one (see Overview).
  • Optional: Docker Desktop / Docker CLI for Docker Hub credential-store cleanup. If Docker CLI is missing, the cleanup step is skipped.

How to use

Run from a directory that contains the script (or use full path):

powershell -ExecutionPolicy Bypass -File install_certs_windows.ps1 -Package all -CertName Zscaler -ExtractPath certs\npm
# Or use an existing PEM:
powershell -ExecutionPolicy Bypass -File install_certs_windows.ps1 -Package all -UseCert C:\path\to\ca.pem
Parameter Required Description
-Package No (default: all) npm, python, huggingface, or all (all = npm + Python TLS + Hugging Face Hub).
-CertName Yes* Substring used to match cert Subject in the store (*CertName* wildcard). Requires -ExtractPath. Cannot be used with -UseCert.
-ExtractPath Yes* Directory under each user’s profile for package-route.pem (rooted paths are normalized to a folder under the profile, same idea as macOS). Requires -CertName.
-UseCert Yes* Path to an existing PEM file. Cannot be used with -CertName / -ExtractPath.

* Use either (-CertName and -ExtractPath) or -UseCert.

Examples

Extract from store and configure all users (run as admin):

.\install_certs_windows.ps1 -Package all -CertName Zscaler -ExtractPath certs\npm

Use an existing PEM (Machine-level env; User-level cert vars are deleted):

.\install_certs_windows.ps1 -Package all -UseCert C:\Users\Administrator\other-ca\company-ca.pem

Only npm:

.\install_certs_windows.ps1 -Package npm -CertName "Amazon Root CA 1" -ExtractPath certs\npm

Summary (Windows)

  • Admin required. The script must be run as Administrator (or SYSTEM).
  • Cert source: either store (-CertName + -ExtractPath) or file (-UseCert).
  • Extract path: per-user package-route.pem and User-level env per -Package (npm / Python TLS / Hugging Face as above); re-runs merge and dedupe by fingerprint. Machine-level cert vars are cleared so only User applies (avoids duplication if you previously used -UseCert).
  • UseCert: no PEM written; Machine-level env set per -Package; python does not set HF_HUB_* and does not clear pre-existing HF_HUB_* on the target scope. User-level cert vars are deleted so only Machine applies when using -UseCert as admin.
  • Docker: clears Docker Hub credentials for the current PowerShell user. Run the script in the user's session for this step; SYSTEM cannot clean up the user's Docker credential store.

Users must start a new terminal for env changes to take effect.


Windows: validate_install_windows.ps1

validate_install_windows.ps1 checks that the certificate installation is valid: PEM file(s) exist and are valid (same validation as the install script). -ExpectedSubject is required for every invocation. It does not require admin unless you use -AllUsers.

Parameter Description
-ExpectedSubject <pattern> Required. At least one cert in each PEM file (bundle) must have a subject matching <pattern> (case-insensitive).
(default scope) Read NODE_EXTRA_CA_CERTS and REQUESTS_CA_BUNDLE from the current user's environment (User then Machine), then validate each referenced PEM file.
-AllUsers (Admin only.) For each user in C:\Users\*, read their User registry env, resolve cert paths, and validate each PEM.

Exit code: 0 if all checks passed, 1 if any check failed.

# After install: validate current user's env and cert path(s)
.\validate_install_windows.ps1 -ExpectedSubject Zscaler

# Validate every user's config (run as Administrator)
.\validate_install_windows.ps1 -ExpectedSubject Zscaler -AllUsers

Continuous integration

On push and pull request to main or master, GitHub Actions runs:

Job Runner Command
Test (macOS) macos-latest sudo ./testing/test_install_certs_macos.sh
Test (Windows) windows-latest ./testing/test_install_certs_windows.ps1 (PowerShell)

There is no CI job for the Debian/Ubuntu scripts in this workflow.

About

No description, website, or topics provided.

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors