You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adds a Linux client-side installer + validator that wires a corporate CA
into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only —
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_debian_ubuntu.sh if you need those flows.
Based on the published research wiki (DFLOW-136):
https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931
Two lab-verified paths, auto-detected:
Path A — update-ca-trust
RHEL/Fedora/CentOS/Amazon-Linux when a JDK whose lib/security/cacerts
symlinks to /etc/pki/ca-trust/extracted/java/cacerts is on PATH
(Red Hat OpenJDK). Drops the CA into /etc/pki/ca-trust/source/anchors/
and runs `update-ca-trust extract`. No env var.
Path B — JKS + JAVA_TOOL_OPTIONS
Everything else (Debian/Ubuntu, Amazon Corretto, Eclipse Temurin,
SDKMAN, manual .tar.gz installs). Builds a JKS at
/etc/ssl/package-route-jvm/truststore.jks containing only the customer
CA; writes JAVA_TOOL_OPTIONS to /etc/environment plus the developer
user's .bashrc/.zshrc. JDK-version-agnostic by construction.
Detection rule (detect_mode):
1. No update-ca-trust on PATH -> Path B
2. /etc/pki/ca-trust/extracted/java/cacerts absent -> Path B
3. No `java` on PATH -> Path A (assumes Red Hat
OpenJDK will follow via dnf; emits a loud end-of-run warning)
4. `java`'s cacerts symlinks to RHEL store -> Path A; else Path B
--mode java-tool-options / --mode update-ca-trust overrides detection.
Files:
install_certs_jvm_linux.sh Installer (mirrors install_certs_debian_ubuntu.sh patterns).
validate_certs_jvm_linux.sh Companion validator; subject-substring match.
_jvm_linux_paths.sh Shared constants sourced by both scripts so they cannot drift.
testing/test_install_certs_jvm_linux.sh Docker matrix runner (4 distros x 10 invariants).
.github/workflows/ci.yml New test-linux-jvm job runs the matrix runner.
README.md New "Linux (JVM)" section with the full design + caveats.
Hardening fixes folded in from two rounds of pr-review-toolkit review:
Critical:
- --cert-name actually propagates to the Path B JKS alias and the validator
accepts --cert-name so Path A's anchor file can be located when a
non-default basename was used.
- validate_pem rejects expired certs, leaf certs (CA:FALSE), warns on
bundles (keytool -importcert -noprompt only imports the first cert).
- install_via_update_ca_trust and validate_keystore_contains_subject
capture keytool output explicitly; the previous pipefail+SIGPIPE
pattern (... | grep -qi "$subject") silently turned real keytool
failures into "subject not found" misdiagnoses AND real positive
matches into false negatives.
- validator detect_mode "both paths present" branch now routes the
whole warn block to stderr; previously the warn lines leaked into
the command-substitution capture and broke the case dispatch,
yielding a silent exit-0 with NO checks run.
Important:
- validator FAILs (not OKs) when keytool is missing on the JKS path.
- --all-users root check moved into parse_args (fast-fail exit 1).
- update_user_shell_rc tracks RC_UPDATED and the final summary prints
a loud WARNING when the per-user rc step was skipped.
- --mode jto -> --mode java-tool-options for self-documenting CLI.
- mktemp is -p $(dirname target) so `mv` is rename(2) (atomic), and an
empty-awk-output check refuses to clobber /etc/environment.
- --cert-name regex-validated to [A-Za-z0-9._-]+ to prevent path
traversal in the anchor file path.
- chown failure on the user's rc file captured + warned.
- get_target_user verifies the picked username exists in /etc/passwd.
Smoke matrix (testing/test_install_certs_jvm_linux.sh): Ubuntu 22.04,
Debian 12, RHEL UBI 9, Amazon Linux 2023, in parallel. Per-container
invariants: positive install+validate, subject mismatch -> exit 1,
idempotent re-install (no duplicate lines/aliases), custom --cert-name
round-trips, path-traversal rejected, malformed PEM rejected, expired
CA rejected (skipped when openssl can't produce a verifiably-expired
test cert), leaf cert (CA:FALSE) rejected, forced --mode update-ca-trust
(Path A) on hosts with update-ca-trust, dual-artifact validator
regression test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|**Node / npm or Python**| Linux (Debian / Ubuntu) |[Linux (Debian/Ubuntu)](#linux-debianubuntu-install_certs_debian_ubuntush)|
14
+
|**Node / npm or Python**| Windows |[Windows](#windows-install_certs_windowsps1)|
15
+
16
+
Reference: research wiki [Maven Support in package-reroute (DFLOW-136 / DFLOW-116)](https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931/).
17
+
18
+
## Script index
19
+
7
20
| Script | Platform | Purpose |
8
21
|--------|----------|---------|
9
22
|**install_certs_macos.sh**| macOS | Install cert, set env vars (Node/Python), and clear Docker Hub credentials |
10
23
|**validate_install_macos.sh**| macOS | Validate PEM and env config |
11
24
|**install_certs_debian_ubuntu.sh**| Debian/Ubuntu | Install cert into system trust + profile.d + user shell rc + Docker cleanup |
12
25
|**validate_certs_debian_ubuntu.sh**| Debian/Ubuntu | Validate PEM and env config |
26
+
|**install_certs_jvm_linux.sh**| Linux (JVM) | Install CA for Maven/Gradle/sbt/Ivy: RHEL family → `update-ca-trust extract` into system anchors; others → per-host JKS + `JAVA_TOOL_OPTIONS` in `/etc/environment`|
27
+
|**validate_certs_jvm_linux.sh**| Linux (JVM) | Validate JVM truststore install (auto-detects Path A vs B; checks anchor file or JKS subject + `/etc/environment` + shell-rc) |
28
+
|**_jvm_linux_paths.sh**| Linux (JVM) | Shared constants dot-sourced by installer + validator. Not directly executable. |
13
29
|**install_certs_windows.ps1**| Windows | Install cert, set env vars (Node/Python), and clear Docker Hub credentials |
14
30
|**validate_install_windows.ps1**| Windows | Validate PEM and env config |
Tests live in **testing/**. Automated tests cover **macOS** and **Windows** only (not Debian/Ubuntu).
193
+
Tests live in **testing/**. Automated tests cover **macOS**, **Windows**, and **Linux JVM** (not Debian/Ubuntu).
178
194
179
195
**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.
`install_certs_jvm_linux.sh` wires a custom CA certificate into the JVM trust path so Maven, Gradle, sbt, and Apache Ivy traffic redirected through `package-reroute` validates correctly. **JVM trust only** — does not configure Node/npm or Python, and does not touch Docker credentials. Pair with `install_certs_debian_ubuntu.sh` if you need the Node/Python flows or Docker Hub credential cleanup.
371
+
372
+
The script auto-detects between two lab-verified paths:
373
+
374
+
-**Path A — `update-ca-trust`** for RHEL/Fedora/CentOS/Amazon-Linux when a JDK whose `lib/security/cacerts` is symlinked to `/etc/pki/ca-trust/extracted/java/cacerts` is on `PATH` (Red Hat OpenJDK, and on some images Corretto). Drops the CA into `/etc/pki/ca-trust/source/anchors/` and runs `update-ca-trust extract`. **No env var is set.**
375
+
-**Path B — JKS + `JAVA_TOOL_OPTIONS`** for everything else (Debian/Ubuntu, manual JDK installs that don't symlink to the system store, SDKMAN, snap-confined JDKs). Builds a JKS truststore at `/etc/ssl/package-route-jvm/truststore.jks` containing only the customer CA, then sets `JAVA_TOOL_OPTIONS` in `/etc/environment`. JDK-version-agnostic by construction — one env var serves all current and future JDKs.
376
+
377
+
Auto-detection logic (`detect_mode` in the script):
378
+
379
+
1. If `update-ca-trust` is not on `PATH` → **Path B**.
380
+
2. If `/etc/pki/ca-trust/extracted/java/cacerts` does not exist → **Path B**.
381
+
3. If no `java` is on `PATH` → **Path A** (assumes Red Hat OpenJDK will be installed via `dnf`; the installer prints a loud end-of-run warning instructing the user to re-run with `--mode java-tool-options` if they install Corretto/Temurin/SDKMAN instead).
382
+
4. If the resolved `java`'s `lib/security/cacerts` symlinks to the RHEL system store → **Path A**; otherwise **Path B**.
383
+
384
+
`--mode java-tool-options` or `--mode update-ca-trust` overrides the detection.
385
+
386
+
Both scripts source a small shared file `_jvm_linux_paths.sh` for the constants block (CA basename default, anchor dir, JKS path, password, env file) so the installer and validator cannot drift.
387
+
388
+
### Requirements
389
+
390
+
-**Linux** (Debian/Ubuntu family OR RHEL/Fedora/CentOS/Amazon-Linux family).
391
+
-**Root** (`sudo`).
392
+
-**`openssl`** on `PATH`.
393
+
-**`keytool`** on `PATH` (provided by any JDK) **for Path B**. On Path A the verification step uses `keytool` opportunistically but the install itself does not need it; the installer emits a warning if `keytool` is missing on Path A.
394
+
395
+
### Options
396
+
397
+
| Option | Required | Description |
398
+
|--------|----------|-------------|
399
+
|`--use-cert <path>`|**Yes**| Path to an existing PEM/CRT certificate file. Validated: must be a parseable X.509, not expired, with `CA:TRUE` in basicConstraints. Bundles emit a warning (only the first cert imports). |
400
+
|`--mode auto\|java-tool-options\|update-ca-trust`| No (default: **auto**) | Override path detection. |
401
+
|`--cert-name <name>`| No (default: `package-route-custom-ca`) | Base name applied to the Path A anchor file (`/etc/pki/ca-trust/source/anchors/<name>.crt`) AND the Path B JKS alias. Must match `[A-Za-z0-9._-]+`. Pass the same value to the validator. |
402
+
|`-h`, `--help`| — | Usage. |
403
+
404
+
### Examples
405
+
406
+
```bash
407
+
# Auto-detect; works on both RHEL family and Debian/Ubuntu
**`--expected-subject` is required.** The validator auto-detects which path was used (by checking for the JKS file or the anchor file), then asserts the customer CA is present by case-insensitive subject substring match. `--all-users` iterates `/home/*` and is Path B-specific (requires root; checked at `parse_args` time, fails fast). Pass `--cert-name <name>` if the installer was invoked with a non-default value.
Exit code 0 if all checks pass, 1 otherwise. Missing `JAVA_TOOL_OPTIONS` in a user rc file is a **warning** (not a failure) — `/etc/environment` is the authoritative source for system-wide config. Missing `keytool` while a keystore exists is a **failure** (cannot verify the core invariant).
428
+
429
+
### Caveats
430
+
431
+
-**`/etc/environment` activation** (Path B). GUI-launched apps (IntelliJ from the GNOME/KDE launcher) inherit `/etc/environment` via the session manager at login. Existing sessions need a logoff/login to pick up the new env var. The script updates the SUDO_USER's `.bashrc`/`.zshrc` so the *current* shell session has it without re-login.
432
+
-**Gradle Daemon caching** (Path B). A Gradle Daemon started before the env var was set still uses its captured environment. Run `gradle --stop` after onboarding.
433
+
-**`Picked up JAVA_TOOL_OPTIONS:` banner** (Path B). Every JVM prints this to stderr at startup. CI log parsers that strict-match empty-stderr need to tolerate it.
434
+
-**`changeit` truststore password.** The JKS at `/etc/ssl/package-route-jvm/truststore.jks` uses the OpenJDK convention password `changeit`. This is **not** a secret — JKS truststores protect file integrity, not contents, and the trust anchor inside is a public CA certificate. The password is persisted in `/etc/environment` via the `-Djavax.net.ssl.trustStorePassword` flag so unattended JVMs can open the store.
435
+
-**Path B truststore extends the JDK's bundled cacerts.**`-Djavax.net.ssl.trustStore=…` in OpenJDK *replaces* the JVM trust source — a JKS containing only the corporate CA would break every public-CA TLS handshake (Maven Central, Gradle plugin portal, Let's Encrypt-fronted mirrors). The installer therefore copies `$JAVA_HOME/lib/security/cacerts` to `/etc/ssl/package-route-jvm/truststore.jks` first, then `keytool -importcert` appends the corporate CA. The resulting store has ~150 public roots **plus** the corporate one. Path A is unaffected — `update-ca-trust extract` already builds the system-wide Java cacerts by merging system anchors with the JDK's defaults.
436
+
-**Mixed-distro auto-detection.** On RHEL family with a non-Red-Hat JDK on PATH (Corretto, Temurin, SDKMAN), auto-detection lands on Path B because the JDK's `lib/security/cacerts` is not symlinked to the RHEL system store. On RHEL family with **no** JDK on PATH yet, auto-detection picks Path A on the assumption that Red Hat OpenJDK will follow via `dnf` — the installer emits a loud end-of-run warning naming this assumption.
437
+
-**Snap-confined JDKs** are not configured by Path A (read-only squashfs). Auto-detection lands on Path B because the snap cacerts is not symlinked to the system one. Path B works because `JAVA_TOOL_OPTIONS` is read by the JVM at startup regardless of how the JDK was installed.
438
+
-**Container-internal JDKs.** Maven/Gradle running inside Docker on a developer machine need the CA wired into the *container* image — host-side install does not propagate. Use a `RUN` step in the Dockerfile or pass `JAVA_TOOL_OPTIONS` via `docker run -e`.
439
+
-**`MAVEN_OPTS` clobbering.** If your shell or `~/.mavenrc` sets `MAVEN_OPTS`, those args land AFTER `JAVA_TOOL_OPTIONS` and can override the trust store flags. If `mvn` fails TLS after install, check `env | grep -E '^(JAVA_TOOL_OPTIONS|MAVEN_OPTS)='` — if both are set, ensure `MAVEN_OPTS` does NOT also include `-Djavax.net.ssl.trustStore`.
440
+
-**IntelliJ per-IDE SSL store.**`~/.config/JetBrains/<IDE>/ssl/cacerts` is a separate trust store used by the IDE for the plugin marketplace and VCS integration — NOT by Maven/Gradle runs spawned from IntelliJ (those use the JBR's truststore which the env var path covers). If `mvn` works in Terminal but IntelliJ Maven sync fails, add the CA via Settings → Tools → Server Certificates.
441
+
-**Idempotent re-runs.** Path B re-creates the JKS each run (single alias guaranteed) and replaces (not appends) the env var line in `/etc/environment`. Path A re-copies the anchor and re-runs `update-ca-trust extract` (with fingerprint-compare to surface a deliberate replacement vs. an idempotent re-run). Running the script twice produces the same final state.
442
+
-**Existing anchor file replacement (Path A).** A file at `/etc/pki/ca-trust/source/anchors/<cert-name>.crt` placed by other tooling is replaced if its SHA-256 fingerprint differs from `--use-cert`. The installer prints a `[warn] Replacing existing anchor` line with both fingerprints so the swap is auditable.
443
+
444
+
### Testing
445
+
446
+
`./testing/test_install_certs_jvm_linux.sh` runs the Docker smoke matrix across four distro × JDK combinations in parallel from any host with Docker. Each container builds a self-signed lab CA, runs the installer, runs the validator, then exercises negative subject, idempotency, custom `--cert-name`, path-traversal rejection, malformed-PEM rejection, expired-CA rejection, and leaf-cert rejection.
447
+
448
+
```bash
449
+
# From repo root (any host with Docker)
450
+
./testing/test_install_certs_jvm_linux.sh
451
+
```
452
+
453
+
Exit code 0 if all containers pass, 1 if any test fails.
454
+
455
+
The same matrix runs on every push and pull request via `.github/workflows/ci.yml` (`test-linux-jvm` job).
456
+
457
+
### Summary (Linux JVM)
458
+
459
+
-**One run as root**, single cert source via `--use-cert`.
460
+
-**Path A**: anchor in `/etc/pki/ca-trust/source/anchors/<cert-name>.crt`; `update-ca-trust extract` updates the system Java trust store. No env var.
461
+
-**Path B**: JKS at `/etc/ssl/package-route-jvm/truststore.jks` with alias `<cert-name>`; `JAVA_TOOL_OPTIONS` in `/etc/environment` and in the developer user's `.bashrc`/`.zshrc`.
462
+
-**Idempotent** (both paths), **re-runnable**, **JDK-version-agnostic** across currently-supported JDKs — JKS format is still read by JDK 8–25 (Path B); Path A piggy-backs on the OS trust store that Red Hat OpenJDK already symlinks. A future JDK that drops JKS support would require a Path B format bump.
463
+
- Users must open a new login shell (or `source /etc/environment`) for env changes to take effect. `gradle --stop` to refresh the Gradle Daemon.
464
+
465
+
---
466
+
350
467
## Windows: install_certs_windows.ps1
351
468
352
469
### Overview
@@ -448,5 +565,6 @@ On **push** and **pull request** to `main` or `master`, GitHub Actions runs:
448
565
|-----|--------|---------|
449
566
| Test (macOS) |`macos-latest`|`sudo ./testing/test_install_certs_macos.sh`|
450
567
| Test (Windows) |`windows-latest`|`./testing/test_install_certs_windows.ps1` (PowerShell) |
568
+
| Test (Linux JVM) |`ubuntu-latest`|`./testing/test_install_certs_jvm_linux.sh`|
451
569
452
570
There is no CI job for the Debian/Ubuntu scripts in this workflow.
0 commit comments